diff --git a/.drone.star b/.drone.star index fff80f1d63..42e158f24b 100644 --- a/.drone.star +++ b/.drone.star @@ -75,6 +75,7 @@ def main(ctx): return [ buildAndPublishDocker(), buildOnly(), + testIntegration(), release(), litmusOcisOldWebdav(), litmusOcisNewWebdav(), @@ -256,6 +257,39 @@ def buildOnly(): ] } +def testIntegration(): + return { + "kind": "pipeline", + "type": "docker", + "name": "test-integration", + "platform": { + "os": "linux", + "arch": "amd64", + }, + "trigger": { + "event": { + "include": [ + "pull_request", + ], + }, + }, + "steps": [ + { + "name": "test", + "image": "registry.cern.ch/docker.io/library/golang:1.13", + "commands": [ + "make test-integration", + ], + "environment": { + "REDIS_ADDRESS": "redis:6379", + }, + } + ], + "services": [ + redisService(), + ], + } + def release(): return { "kind": "pipeline", diff --git a/Makefile b/Makefile index 374b0e756d..90d93adbcd 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,10 @@ build-reva: imports go build -ldflags ${BUILD_FLAGS} -o ./cmd/reva/reva ./cmd/reva test: off - go test -race ./... + go test -race $$(go list ./... | grep -v /tests/integration) + +test-integration: build-ci + cd tests/integration && go test -race ./... lint: go run tools/check-license/check-license.go diff --git a/changelog/unreleased/add-grpc-testsuite.md b/changelog/unreleased/add-grpc-testsuite.md new file mode 100644 index 0000000000..c3671432af --- /dev/null +++ b/changelog/unreleased/add-grpc-testsuite.md @@ -0,0 +1,6 @@ +Enhancement: Add grpc test suite for the storage provider + +A new test suite has been added which tests the grpc interface to the storage +provider. It currently runs against the ocis and the owncloud storage drivers. + +https://github.com/cs3org/reva/pull/1514 \ No newline at end of file diff --git a/grpc-tests/userprovider_test.go b/grpc-tests/userprovider_test.go deleted file mode 100644 index 979f29c2c9..0000000000 --- a/grpc-tests/userprovider_test.go +++ /dev/null @@ -1,220 +0,0 @@ -// 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 grpctests - -import ( - "context" - "errors" - "net" - "os" - "os/exec" - "testing" - "time" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/stretchr/testify/assert" -) - -const grpcAddress = "localhost:19000" -const timeoutMs = 30000 - -func Test_service_GetUser(t *testing.T) { - providers := []struct { - name string - existingIdp string - }{ - { - name: "json", - existingIdp: "localhost:20080", - }, - { - name: "demo", - existingIdp: "http://localhost:9998", - }, - } - - for _, tt := range providers { - t.Run(tt.name, func(t *testing.T) { - // start revad with the specific provider - cmd := exec.Command("../cmd/revad/revad", "-c", "userproviders/"+tt.name+".toml") - err := cmd.Start() - - if err != nil { - t.Fatalf("Could not start revad! ERROR: %v", err) - } - - // wait till port is open - _ = waitForPort("open") - - // even the port is open the service might not be available yet - time.Sleep(1 * time.Second) - - GetUser(t, tt.existingIdp) - - // kill revad - err = cmd.Process.Signal(os.Kill) - if err != nil { - t.Fatalf("Could not kill revad! ERROR: %v", err) - } - _ = waitForPort("close") - }) - } -} - -func GetUser(t *testing.T, existingIdp string) { - tests := []struct { - name string - userID *userpb.UserId - want *userpb.GetUserResponse - }{ - { - name: "simple", - userID: &userpb.UserId{ - Idp: existingIdp, - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - }, - want: &userpb.GetUserResponse{ - Status: &v1beta11.Status{ - Code: 1, - }, - User: &userpb.User{ - Username: "marie", - Mail: "marie@example.org", - DisplayName: "Marie Curie", - Groups: []string{ - "radium-lovers", - "polonium-lovers", - "physics-lovers", - }, - }, - }, - }, - { - name: "not-existing opaqueId", - userID: &userpb.UserId{ - Idp: existingIdp, - OpaqueId: "doesnote-xist-4376-b307-cf0a8c2d0d9c", - }, - want: &userpb.GetUserResponse{ - Status: &v1beta11.Status{ - Code: 15, - }, - }, - }, - { - name: "no opaqueId", - userID: &userpb.UserId{ - Idp: existingIdp, - OpaqueId: "", - }, - want: &userpb.GetUserResponse{ - Status: &v1beta11.Status{ - Code: 15, - }, - }, - }, - { - name: "not-existing idp", - userID: &userpb.UserId{ - Idp: "http://does-not-exist:12345", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - }, - want: &userpb.GetUserResponse{ - Status: &v1beta11.Status{ - Code: 15, - }, - }, - }, - { - name: "no idp", - userID: &userpb.UserId{ - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - }, - want: &userpb.GetUserResponse{ - Status: &v1beta11.Status{ - Code: 1, - }, - User: &userpb.User{ - Username: "marie", - Mail: "marie@example.org", - DisplayName: "Marie Curie", - Groups: []string{ - "radium-lovers", - "polonium-lovers", - "physics-lovers", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - - serviceClient, err := pool.GetUserProviderServiceClient(grpcAddress) - if err != nil { - t.Fatalf("cannot get UserProviderServiceClient! ERROR: %v", err) - } - - userResp, err := serviceClient.GetUser(ctx, &userpb.GetUserRequest{ - UserId: tt.userID, - }) - if err != nil { - t.Fatalf("cannot get user! ERROR: %v", err) - } - assert.Equal(t, tt.want.Status.Code, userResp.Status.Code) - if tt.want.User == nil { - assert.Nil(t, userResp.User) - } else { - // make sure not to run into a nil pointer error - if userResp.User == nil { - t.Fatalf("no user in response %v", userResp) - } - assert.Equal(t, tt.want.User.Username, userResp.User.Username) - assert.Equal(t, tt.want.User.Mail, userResp.User.Mail) - assert.Equal(t, tt.want.User.DisplayName, userResp.User.DisplayName) - assert.Equal(t, tt.want.User.Groups, userResp.User.Groups) - } - }) - } -} - -func waitForPort(expectedStatus string) error { - if expectedStatus != "open" && expectedStatus != "close" { - return errors.New("status can only be 'open' or 'close'") - } - timoutCounter := 0 - for timoutCounter <= timeoutMs { - conn, err := net.Dial("tcp", grpcAddress) - if err == nil { - _ = conn.Close() - if expectedStatus == "open" { - break - } - } else if expectedStatus == "close" { - break - } - - time.Sleep(1 * time.Millisecond) - timoutCounter++ - } - return nil -} diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index a7a0f47152..78e378a45f 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -1814,6 +1814,9 @@ func (fs *ocfs) listShareFolderRoot(ctx context.Context, sp string, mdKeys []str mds, err := ioutil.ReadDir(ip) if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } return nil, errors.Wrap(err, "ocfs: error listing shadow_files") } diff --git a/pkg/storage/utils/ace/ace.go b/pkg/storage/utils/ace/ace.go index 7408e5e497..0123bce377 100644 --- a/pkg/storage/utils/ace/ace.go +++ b/pkg/storage/utils/ace/ace.go @@ -187,10 +187,11 @@ func (e *ACE) Grant() *provider.Grant { }, Permissions: e.grantPermissionSet(), } + id := e.principal[2:] if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_GROUP { - g.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: e.principal}} + g.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: id}} } else if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_USER { - g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: e.principal}} + g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id}} } return g } @@ -324,7 +325,7 @@ func unmarshalKV(s string) (*ACE, error) { func getACEPerm(set *provider.ResourcePermissions) string { var b strings.Builder - if set.Stat || set.InitiateFileDownload || set.ListContainer { + if set.Stat || set.InitiateFileDownload || set.ListContainer || set.GetPath { b.WriteString("r") } if set.InitiateFileUpload || set.Move { diff --git a/pkg/storage/utils/ace/ace_suite_test.go b/pkg/storage/utils/ace/ace_suite_test.go new file mode 100644 index 0000000000..62778e0ba3 --- /dev/null +++ b/pkg/storage/utils/ace/ace_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ace_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestAce(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Ace Suite") +} diff --git a/pkg/storage/utils/ace/ace_test.go b/pkg/storage/utils/ace/ace_test.go new file mode 100644 index 0000000000..724cbe1c87 --- /dev/null +++ b/pkg/storage/utils/ace/ace_test.go @@ -0,0 +1,230 @@ +// 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 ace_test + +import ( + "fmt" + + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/storage/utils/ace" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("ACE", func() { + + var ( + userGrant = &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + OpaqueId: "foo", + }, + }, + }, + Permissions: &provider.ResourcePermissions{}, + } + + groupGrant = &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &grouppb.GroupId{ + OpaqueId: "foo", + }, + }, + }, + Permissions: &provider.ResourcePermissions{}, + } + ) + + Describe("FromGrant", func() { + It("creates an ACE from a user grant", func() { + ace := ace.FromGrant(userGrant) + Expect(ace.Principal()).To(Equal("u:foo")) + }) + + It("creates an ACE from a group grant", func() { + ace := ace.FromGrant(groupGrant) + Expect(ace.Principal()).To(Equal("g:foo")) + }) + }) + + Describe("Grant", func() { + It("returns a proper Grant", func() { + ace := ace.FromGrant(userGrant) + grant := ace.Grant() + Expect(grant).To(Equal(userGrant)) + }) + }) + + Describe("marshalling", func() { + It("works", func() { + a := ace.FromGrant(userGrant) + + marshalled, principal := a.Marshal() + unmarshalled, err := ace.Unmarshal(marshalled, principal) + Expect(err).ToNot(HaveOccurred()) + + Expect(unmarshalled).To(Equal(a)) + }) + }) + + Describe("converting permissions", func() { + It("converts r", func() { + userGrant.Permissions.Stat = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.Stat = false + Expect(newGrant.Permissions.Stat).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.ListContainer = true + newGrant = ace.FromGrant(userGrant).Grant() + userGrant.Permissions.ListContainer = false + Expect(newGrant.Permissions.ListContainer).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.InitiateFileDownload = true + newGrant = ace.FromGrant(userGrant).Grant() + userGrant.Permissions.InitiateFileDownload = false + Expect(newGrant.Permissions.InitiateFileDownload).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.GetPath = true + newGrant = ace.FromGrant(userGrant).Grant() + fmt.Println(newGrant.Permissions) + userGrant.Permissions.GetPath = false + Expect(newGrant.Permissions.GetPath).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts w", func() { + userGrant.Permissions.InitiateFileUpload = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.InitiateFileUpload = false + Expect(newGrant.Permissions.InitiateFileUpload).To(BeTrue()) + Expect(newGrant.Permissions.Move).To(BeFalse()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.InitiateFileUpload = true + userGrant.Permissions.InitiateFileDownload = true + newGrant = ace.FromGrant(userGrant).Grant() + userGrant.Permissions.InitiateFileUpload = false + Expect(newGrant.Permissions.InitiateFileUpload).To(BeTrue()) + Expect(newGrant.Permissions.Move).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts a", func() { + userGrant.Permissions.CreateContainer = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.CreateContainer = false + Expect(newGrant.Permissions.CreateContainer).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts d", func() { + userGrant.Permissions.Delete = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.Delete = false + Expect(newGrant.Permissions.Delete).To(BeTrue()) + Expect(newGrant.Permissions.Move).To(BeFalse()) + }) + + It("converts C", func() { + userGrant.Permissions.AddGrant = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.AddGrant = false + Expect(newGrant.Permissions.AddGrant).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.RemoveGrant = true + newGrant = ace.FromGrant(userGrant).Grant() + userGrant.Permissions.RemoveGrant = false + Expect(newGrant.Permissions.RemoveGrant).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + + userGrant.Permissions.UpdateGrant = true + newGrant = ace.FromGrant(userGrant).Grant() + userGrant.Permissions.UpdateGrant = false + Expect(newGrant.Permissions.UpdateGrant).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts c", func() { + userGrant.Permissions.ListGrants = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.ListGrants = false + Expect(newGrant.Permissions.ListGrants).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts u", func() { + userGrant.Permissions.ListRecycle = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.ListRecycle = false + Expect(newGrant.Permissions.ListRecycle).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts U", func() { + userGrant.Permissions.RestoreRecycleItem = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.RestoreRecycleItem = false + Expect(newGrant.Permissions.RestoreRecycleItem).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts P", func() { + userGrant.Permissions.PurgeRecycle = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.PurgeRecycle = false + Expect(newGrant.Permissions.PurgeRecycle).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts v", func() { + userGrant.Permissions.ListFileVersions = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.ListFileVersions = false + Expect(newGrant.Permissions.ListFileVersions).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts V", func() { + userGrant.Permissions.RestoreFileVersion = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.RestoreFileVersion = false + Expect(newGrant.Permissions.RestoreFileVersion).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + + It("converts q", func() { + userGrant.Permissions.GetQuota = true + newGrant := ace.FromGrant(userGrant).Grant() + userGrant.Permissions.GetQuota = false + Expect(newGrant.Permissions.GetQuota).To(BeTrue()) + Expect(newGrant.Permissions.Delete).To(BeFalse()) + }) + }) +}) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs_test.go b/pkg/storage/utils/decomposedfs/decomposedfs_test.go index 2f9043dccd..118bb1f87a 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs_test.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs_test.go @@ -40,7 +40,7 @@ var _ = Describe("Decomposed", func() { BeforeEach(func() { ref = &provider.Reference{ Spec: &provider.Reference_Path{ - Path: "dir1", + Path: "/dir1", }, } }) diff --git a/pkg/storage/utils/decomposedfs/grants_test.go b/pkg/storage/utils/decomposedfs/grants_test.go new file mode 100644 index 0000000000..e8f93cbe89 --- /dev/null +++ b/pkg/storage/utils/decomposedfs/grants_test.go @@ -0,0 +1,147 @@ +// 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 decomposedfs_test + +import ( + "path" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" + "github.com/pkg/xattr" + "github.com/stretchr/testify/mock" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Grants", func() { + var ( + env *helpers.TestEnv + + ref *provider.Reference + grant *provider.Grant + ) + + BeforeEach(func() { + ref = &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/dir1", + }, + } + + grant = &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + }, + }, + }, + Permissions: &provider.ResourcePermissions{ + Stat: true, + Move: true, + Delete: false, + }, + } + }) + + JustBeforeEach(func() { + var err error + env, err = helpers.NewTestEnv() + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if env != nil { + env.Cleanup() + } + }) + + Context("with insufficient permissions", func() { + JustBeforeEach(func() { + env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(false, nil) + }) + + Describe("AddGrant", func() { + It("adds grants", func() { + err := env.Fs.AddGrant(env.Ctx, ref, grant) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + }) + + Context("with sufficient permissions", func() { + JustBeforeEach(func() { + env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) + }) + + Describe("AddGrant", func() { + It("adds grants", func() { + n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1") + Expect(err).ToNot(HaveOccurred()) + + err = env.Fs.AddGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + localPath := path.Join(env.Root, "nodes", n.ID) + attr, err := xattr.Get(localPath, xattrs.GrantPrefix+xattrs.UserAcePrefix+grant.Grantee.GetUserId().OpaqueId) + Expect(err).ToNot(HaveOccurred()) + Expect(string(attr)).To(Equal("\x00t=A:f=:p=rw")) + }) + }) + + Describe("ListGrants", func() { + It("lists existing grants", func() { + err := env.Fs.AddGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + grants, err := env.Fs.ListGrants(env.Ctx, ref) + Expect(err).ToNot(HaveOccurred()) + Expect(len(grants)).To(Equal(1)) + + g := grants[0] + Expect(g.Grantee.GetUserId().OpaqueId).To(Equal(grant.Grantee.GetUserId().OpaqueId)) + Expect(g.Permissions.Stat).To(BeTrue()) + Expect(g.Permissions.Move).To(BeTrue()) + Expect(g.Permissions.Delete).To(BeFalse()) + }) + }) + + Describe("RemoveGrants", func() { + It("removes the grant", func() { + err := env.Fs.AddGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + grants, err := env.Fs.ListGrants(env.Ctx, ref) + Expect(err).ToNot(HaveOccurred()) + Expect(len(grants)).To(Equal(1)) + + err = env.Fs.RemoveGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + grants, err = env.Fs.ListGrants(env.Ctx, ref) + Expect(err).ToNot(HaveOccurred()) + Expect(len(grants)).To(Equal(0)) + }) + }) + }) +}) diff --git a/pkg/storage/utils/decomposedfs/lookup.go b/pkg/storage/utils/decomposedfs/lookup.go index 7b1024d426..0de4106b73 100644 --- a/pkg/storage/utils/decomposedfs/lookup.go +++ b/pkg/storage/utils/decomposedfs/lookup.go @@ -101,6 +101,7 @@ func (lu *Lookup) Path(ctx context.Context, n *node.Node) (p string, err error) return } } + p = filepath.Join("/", p) return } diff --git a/pkg/storage/utils/decomposedfs/lookup_test.go b/pkg/storage/utils/decomposedfs/lookup_test.go new file mode 100644 index 0000000000..e6283abf56 --- /dev/null +++ b/pkg/storage/utils/decomposedfs/lookup_test.go @@ -0,0 +1,55 @@ +// 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 decomposedfs_test + +import ( + helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Lookup", func() { + var ( + env *helpers.TestEnv + ) + + JustBeforeEach(func() { + var err error + env, err = helpers.NewTestEnv() + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if env != nil { + env.Cleanup() + } + }) + + Describe("Path", func() { + It("returns the path including a leading slash", func() { + n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1") + Expect(err).ToNot(HaveOccurred()) + + path, err := env.Lookup.Path(env.Ctx, n) + Expect(err).ToNot(HaveOccurred()) + Expect(path).To(Equal("/dir1/file1")) + }) + }) +}) diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index f75cee3ef7..13565a2543 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -62,7 +62,7 @@ var _ = Describe("Node", func() { Describe("ReadNode", func() { It("reads the blobID from the xattrs", func() { - lookupNode, err := env.Lookup.NodeFromPath(env.Ctx, "dir1/file1") + lookupNode, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1") Expect(err).ToNot(HaveOccurred()) n, err := node.ReadNode(env.Ctx, env.Lookup, lookupNode.ID) @@ -73,7 +73,7 @@ var _ = Describe("Node", func() { Describe("WriteMetadata", func() { It("writes all xattrs", func() { - n, err := env.Lookup.NodeFromPath(env.Ctx, "dir1/file1") + n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1") Expect(err).ToNot(HaveOccurred()) blobsize := 239485734 @@ -87,7 +87,7 @@ var _ = Describe("Node", func() { err = n.WriteMetadata(owner) Expect(err).ToNot(HaveOccurred()) - n2, err := env.Lookup.NodeFromPath(env.Ctx, "dir1/file1") + n2, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1") Expect(err).ToNot(HaveOccurred()) Expect(n2.Name).To(Equal("TestName")) Expect(n2.BlobID).To(Equal("TestBlobID")) @@ -97,7 +97,7 @@ var _ = Describe("Node", func() { Describe("Parent", func() { It("returns the parent node", func() { - child, err := env.Lookup.NodeFromPath(env.Ctx, "dir1/subdir1") + child, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/subdir1") Expect(err).ToNot(HaveOccurred()) Expect(child).ToNot(BeNil()) @@ -115,7 +115,7 @@ var _ = Describe("Node", func() { BeforeEach(func() { var err error - parent, err = env.Lookup.NodeFromPath(env.Ctx, "dir1") + parent, err = env.Lookup.NodeFromPath(env.Ctx, "/dir1") Expect(err).ToNot(HaveOccurred()) Expect(parent).ToNot(BeNil()) }) diff --git a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go index f7d950ca71..808ef0215d 100644 --- a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go +++ b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go @@ -53,9 +53,9 @@ type TestEnv struct { // NewTestEnv prepares a test environment on disk // The storage contains some directories and a file: // -// dir1/ -// dir1/file1 -// dir1/subdir1/ +// /dir1/ +// /dir1/file1 +// /dir1/subdir1/ func NewTestEnv() (*TestEnv, error) { tmpRoot, err := ioutil.TempDir("", "reva-unit-tests-*-root") if err != nil { diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index 1fc1ac835b..eae66faea6 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -60,7 +60,7 @@ var _ = Describe("Tree", func() { JustBeforeEach(func() { var err error - n, err = env.Lookup.NodeFromPath(env.Ctx, "dir1/file1") + n, err = env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1") Expect(err).ToNot(HaveOccurred()) }) @@ -90,7 +90,7 @@ var _ = Describe("Tree", func() { trashPath := path.Join(env.Root, "trash", env.Owner.Id.OpaqueId, n.ID) attr, err := xattr.Get(trashPath, xattrs.TrashOriginAttr) Expect(err).ToNot(HaveOccurred()) - Expect(string(attr)).To(Equal("dir1/file1")) + Expect(string(attr)).To(Equal("/dir1/file1")) }) It("does not delete the blob from the blobstore", func() { diff --git a/pkg/user/manager/demo/demo.go b/pkg/user/manager/demo/demo.go index 77447f71cc..3eadeab718 100644 --- a/pkg/user/manager/demo/demo.go +++ b/pkg/user/manager/demo/demo.go @@ -80,9 +80,9 @@ func extractClaim(u *userpb.User, claim string) (string, error) { return "", errors.New("demo: invalid field") } -// TODO(jfd) search Opaque? compare sub? +// TODO(jfd) compare sub? func userContains(u *userpb.User, query string) bool { - return strings.Contains(u.Username, query) || strings.Contains(u.DisplayName, query) || strings.Contains(u.Mail, query) + return strings.Contains(u.Username, query) || strings.Contains(u.DisplayName, query) || strings.Contains(u.Mail, query) || strings.Contains(u.Id.OpaqueId, query) } func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { diff --git a/tests/integration/grpc/fixtures/storageprovider-ocis.toml b/tests/integration/grpc/fixtures/storageprovider-ocis.toml new file mode 100644 index 0000000000..126a9d9aed --- /dev/null +++ b/tests/integration/grpc/fixtures/storageprovider-ocis.toml @@ -0,0 +1,12 @@ +[grpc] +address = "{{grpc_address}}" + +[grpc.services.storageprovider] +driver = "ocis" + +[grpc.services.storageprovider.drivers.ocis] +root = "{{root}}" +treetime_accounting = true +treesize_accounting = true +enable_home = true +userprovidersvc = "localhost:18000" \ No newline at end of file diff --git a/tests/integration/grpc/fixtures/storageprovider-owncloud.toml b/tests/integration/grpc/fixtures/storageprovider-owncloud.toml new file mode 100644 index 0000000000..a65851c005 --- /dev/null +++ b/tests/integration/grpc/fixtures/storageprovider-owncloud.toml @@ -0,0 +1,11 @@ +[grpc] +address = "{{grpc_address}}" + +[grpc.services.storageprovider] +driver = "owncloud" + +[grpc.services.storageprovider.drivers.owncloud] +enable_home = true +datadirectory = "{{root}}" +userprovidersvc = "{{users_address}}" +redis = "{{redis_address}}" diff --git a/grpc-tests/userproviders/demo.toml b/tests/integration/grpc/fixtures/userprovider-demo.toml similarity index 64% rename from grpc-tests/userproviders/demo.toml rename to tests/integration/grpc/fixtures/userprovider-demo.toml index 1c3276e6da..840829b703 100644 --- a/grpc-tests/userproviders/demo.toml +++ b/tests/integration/grpc/fixtures/userprovider-demo.toml @@ -1,5 +1,5 @@ [grpc] -address = "0.0.0.0:19000" +address = "{{grpc_address}}" [grpc.services.userprovider] driver = "demo" diff --git a/grpc-tests/userproviders/json.toml b/tests/integration/grpc/fixtures/userprovider-json.toml similarity index 59% rename from grpc-tests/userproviders/json.toml rename to tests/integration/grpc/fixtures/userprovider-json.toml index 13e2ea7599..fc542ee9c8 100644 --- a/grpc-tests/userproviders/json.toml +++ b/tests/integration/grpc/fixtures/userprovider-json.toml @@ -1,8 +1,8 @@ [grpc] -address = "0.0.0.0:19000" +address = "{{grpc_address}}" [grpc.services.userprovider] driver = "json" [grpc.services.userprovider.drivers.json] -users = "userproviders/users.demo.json" +users = "fixtures/users.demo.json" diff --git a/grpc-tests/userproviders/users.demo.json b/tests/integration/grpc/fixtures/users.demo.json similarity index 89% rename from grpc-tests/userproviders/users.demo.json rename to tests/integration/grpc/fixtures/users.demo.json index d13a252b9b..cf3ef4b2c8 100644 --- a/grpc-tests/userproviders/users.demo.json +++ b/tests/integration/grpc/fixtures/users.demo.json @@ -8,7 +8,15 @@ "secret": "relativity", "mail": "einstein@example.org", "display_name": "Albert Einstein", - "groups": ["sailing-lovers", "violin-haters", "physics-lovers"] + "groups": ["sailing-lovers", "violin-haters", "physics-lovers"], + "opaque": { + "map": { + "uid": { + "decoder": "plain", + "value": "MTIz" + } + } + } }, { "id": { diff --git a/tests/integration/grpc/grpc_suite_test.go b/tests/integration/grpc/grpc_suite_test.go new file mode 100644 index 0000000000..b387e390a6 --- /dev/null +++ b/tests/integration/grpc/grpc_suite_test.go @@ -0,0 +1,183 @@ +// 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 grpc_test + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "os/exec" + "path" + "strings" + "sync" + "testing" + "time" + + "github.com/pkg/errors" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const timeoutMs = 30000 + +var mutex = sync.Mutex{} +var port = 19000 + +func TestGprc(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Gprc Suite") +} + +type cleanupFunc func(bool) error + +// Revad represents a running revad process +type Revad struct { + TmpRoot string // Temporary directory on disk. Will be cleaned up by the Cleanup func. + GrpcAddress string // Address of the grpc service + Cleanup cleanupFunc // Function to kill the process and cleanup the temp. root. If the given parameter is true the files will be kept to make debugging failures easier. +} + +// stardRevads takes a list of revad configuration files plus a map of +// variables that need to be substituted in them and starts them. +// +// A unique port is assigned to each spawned instance. +// Placeholders in the config files can be replaced the variables from the +// `variables` map, e.g. the config +// +// redis = "{{redis_address}}" +// +// and the variables map +// +// variables = map[string]string{"redis_address": "localhost:6379"} +// +// will result in the config +// +// redis = "localhost:6379" +// +// Special variables are created for the revad addresses, e.g. having a +// `storage` and a `users` revad will make `storage_address` and +// `users_address` available wit the dynamically assigned ports so that +// the services can be made available to each other. +func startRevads(configs map[string]string, variables map[string]string) (map[string]*Revad, error) { + mutex.Lock() + defer mutex.Unlock() + + revads := map[string]*Revad{} + addresses := map[string]string{} + for name := range configs { + addresses[name] = fmt.Sprintf("localhost:%d", port) + port++ + } + + for name, config := range configs { + ownAddress := addresses[name] + + // Create a temporary root for this revad + tmpRoot, err := ioutil.TempDir("", "reva-grpc-integration-tests-*-root") + if err != nil { + return nil, errors.Wrapf(err, "Could not create tmpdir") + } + newCfgPath := path.Join(tmpRoot, "config.toml") + rawCfg, err := ioutil.ReadFile(path.Join("fixtures", config)) + if err != nil { + return nil, errors.Wrapf(err, "Could not read config file") + } + cfg := string(rawCfg) + cfg = strings.ReplaceAll(cfg, "{{root}}", tmpRoot) + cfg = strings.ReplaceAll(cfg, "{{grpc_address}}", ownAddress) + for v, value := range variables { + cfg = strings.ReplaceAll(cfg, "{{"+v+"}}", value) + } + for name, address := range addresses { + cfg = strings.ReplaceAll(cfg, "{{"+name+"_address}}", address) + } + err = ioutil.WriteFile(newCfgPath, []byte(cfg), 0600) + if err != nil { + return nil, errors.Wrapf(err, "Could not write config file") + } + + // Run revad + cmd := exec.Command("../../../cmd/revad/revad", "-c", newCfgPath) + + outfile, err := os.Create(path.Join(tmpRoot, name+"-out.log")) + if err != nil { + panic(err) + } + defer outfile.Close() + cmd.Stdout = outfile + cmd.Stderr = outfile + + err = cmd.Start() + if err != nil { + return nil, errors.Wrapf(err, "Could not start revad") + } + + err = waitForPort(ownAddress, "open") + if err != nil { + return nil, err + } + + //even the port is open the service might not be available yet + time.Sleep(1 * time.Second) + + revad := &Revad{ + TmpRoot: tmpRoot, + GrpcAddress: ownAddress, + Cleanup: func(keepLogs bool) error { + err := cmd.Process.Signal(os.Kill) + if err != nil { + return errors.Wrap(err, "Could not kill revad") + } + _ = waitForPort(ownAddress, "close") + if keepLogs { + fmt.Println("Test failed, keeping root", tmpRoot, "around for debugging") + } else { + os.RemoveAll(tmpRoot) + } + return nil + }, + } + revads[name] = revad + } + return revads, nil +} + +func waitForPort(grpcAddress, expectedStatus string) error { + if expectedStatus != "open" && expectedStatus != "close" { + return errors.New("status can only be 'open' or 'close'") + } + timoutCounter := 0 + for timoutCounter <= timeoutMs { + conn, err := net.Dial("tcp", grpcAddress) + if err == nil { + _ = conn.Close() + if expectedStatus == "open" { + break + } + } else if expectedStatus == "close" { + break + } + + time.Sleep(1 * time.Millisecond) + timoutCounter++ + } + return nil +} diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go new file mode 100644 index 0000000000..cce656b3af --- /dev/null +++ b/tests/integration/grpc/storageprovider_test.go @@ -0,0 +1,565 @@ +// 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 grpc_test + +import ( + "bytes" + "context" + "io/ioutil" + "os" + + "google.golang.org/grpc/metadata" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + storagep "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/storage/fs/ocis" + "github.com/cs3org/reva/pkg/storage/fs/owncloud" + "github.com/cs3org/reva/pkg/token" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" + ruser "github.com/cs3org/reva/pkg/user" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +// This test suite tests the gprc storageprovider interface using different +// storage backends +// +// It uses the `startRevads` helper to spawn the according reva daemon and +// other dependencies like a userprovider if needed. +// It also sets up an authenticated context and a service client to the storage +// provider to be used in the assertion functions. +var _ = Describe("storage providers", func() { + var ( + dependencies = map[string]string{} + variables = map[string]string{} + revads = map[string]*Revad{} + + ctx context.Context + serviceClient storagep.ProviderAPIClient + user = &userpb.User{ + Id: &userpb.UserId{ + Idp: "0.0.0.0:19000", + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + }, + } + + homeRef = &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: "/"}, + } + filePath = "/file" + fileRef = &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: filePath}, + } + versionedFilePath = "/versionedFile" + versionedFileRef = &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: versionedFilePath}, + } + subdirPath = "/subdir" + subdirRef = &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: subdirPath}, + } + sharesPath = "/Shares" + sharesRef = &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: sharesPath}, + } + ) + + JustBeforeEach(func() { + var err error + ctx = context.Background() + + // Add auth token + tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) + Expect(err).ToNot(HaveOccurred()) + t, err := tokenManager.MintToken(ctx, user) + Expect(err).ToNot(HaveOccurred()) + ctx = token.ContextSetToken(ctx, t) + ctx = metadata.AppendToOutgoingContext(ctx, token.TokenHeader, t) + ctx = ruser.ContextSetUser(ctx, user) + + revads, err = startRevads(dependencies, variables) + Expect(err).ToNot(HaveOccurred()) + serviceClient, err = pool.GetStorageProviderServiceClient(revads["storage"].GrpcAddress) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + for _, r := range revads { + Expect(r.Cleanup(CurrentGinkgoTestDescription().Failed)).To(Succeed()) + } + }) + + assertCreateHome := func() { + It("creates a home directory", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: homeRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + + res, err := serviceClient.CreateHome(ctx, &storagep.CreateHomeRequest{}) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(err).ToNot(HaveOccurred()) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: homeRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + ghRes, err := serviceClient.GetHome(ctx, &storagep.GetHomeRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(ghRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + } + + assertCreateContainer := func() { + It("creates a new directory", func() { + newRef := &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: "/newdir"}, + } + + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: newRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + + res, err := serviceClient.CreateContainer(ctx, &storagep.CreateContainerRequest{Ref: newRef}) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(err).ToNot(HaveOccurred()) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: newRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + } + + assertListContainer := func() { + It("lists a directory", func() { + listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: homeRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(listRes.Infos)).To(Equal(1)) + + info := listRes.Infos[0] + Expect(info.Type).To(Equal(storagep.ResourceType_RESOURCE_TYPE_CONTAINER)) + Expect(info.Path).To(Equal(subdirPath)) + Expect(info.Owner.OpaqueId).To(Equal("f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c")) + }) + } + + assertFileVersions := func() { + It("lists file versions", func() { + listRes, err := serviceClient.ListFileVersions(ctx, &storagep.ListFileVersionsRequest{Ref: versionedFileRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(listRes.Versions)).To(Equal(1)) + Expect(listRes.Versions[0].Size).To(Equal(uint64(1))) + }) + + It("restores a file version", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: versionedFileRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(statRes.Info.Size).To(Equal(uint64(2))) // second version contains 2 bytes + + listRes, err := serviceClient.ListFileVersions(ctx, &storagep.ListFileVersionsRequest{Ref: versionedFileRef}) + Expect(err).ToNot(HaveOccurred()) + restoreRes, err := serviceClient.RestoreFileVersion(ctx, + &storagep.RestoreFileVersionRequest{ + Ref: versionedFileRef, + Key: listRes.Versions[0].Key, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(restoreRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: versionedFileRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(statRes.Info.Size).To(Equal(uint64(1))) // initial version contains 1 byte + }) + } + + assertDelete := func() { + It("deletes a directory", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + res, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{Ref: subdirRef}) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(err).ToNot(HaveOccurred()) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + }) + } + + assertMove := func() { + It("moves a directory", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + targetRef := &storagep.Reference{ + Spec: &storagep.Reference_Path{Path: "/new_subdir"}, + } + res, err := serviceClient.Move(ctx, &storagep.MoveRequest{Source: subdirRef, Destination: targetRef}) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(err).ToNot(HaveOccurred()) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: targetRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + } + + assertGetPath := func() { + It("gets the path to an ID", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + + res, err := serviceClient.GetPath(ctx, &storagep.GetPathRequest{ResourceId: statRes.Info.Id}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Path).To(Equal(subdirPath)) + }) + } + + assertGrants := func() { + It("lists, adds and removes grants", func() { + By("there are no grants initially") + listRes, err := serviceClient.ListGrants(ctx, &storagep.ListGrantsRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(listRes.Grants)).To(Equal(0)) + + By("adding a grant") + grant := &storagep.Grant{ + Grantee: &storagep.Grantee{ + Type: storagep.GranteeType_GRANTEE_TYPE_USER, + Id: &storagep.Grantee_UserId{ + UserId: &userpb.UserId{ + OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + }, + }, + }, + Permissions: &storagep.ResourcePermissions{ + Stat: true, + Move: true, + Delete: false, + }, + } + addRes, err := serviceClient.AddGrant(ctx, &storagep.AddGrantRequest{Ref: subdirRef, Grant: grant}) + Expect(err).ToNot(HaveOccurred()) + Expect(addRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("listing the new grant") + listRes, err = serviceClient.ListGrants(ctx, &storagep.ListGrantsRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(listRes.Grants)).To(Equal(1)) + readGrant := listRes.Grants[0] + Expect(readGrant.Permissions.Stat).To(BeTrue()) + Expect(readGrant.Permissions.Move).To(BeTrue()) + Expect(readGrant.Permissions.Delete).To(BeFalse()) + + By("updating the grant") + grant.Permissions.Delete = true + updateRes, err := serviceClient.UpdateGrant(ctx, &storagep.UpdateGrantRequest{Ref: subdirRef, Grant: grant}) + Expect(err).ToNot(HaveOccurred()) + Expect(updateRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("listing the update grant") + listRes, err = serviceClient.ListGrants(ctx, &storagep.ListGrantsRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(listRes.Grants)).To(Equal(1)) + readGrant = listRes.Grants[0] + Expect(readGrant.Permissions.Stat).To(BeTrue()) + Expect(readGrant.Permissions.Move).To(BeTrue()) + Expect(readGrant.Permissions.Delete).To(BeTrue()) + + By("deleting a grant") + delRes, err := serviceClient.RemoveGrant(ctx, &storagep.RemoveGrantRequest{Ref: subdirRef, Grant: readGrant}) + Expect(err).ToNot(HaveOccurred()) + Expect(delRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("the grant is gone") + listRes, err = serviceClient.ListGrants(ctx, &storagep.ListGrantsRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(listRes.Grants)).To(Equal(0)) + }) + } + + assertUploads := func() { + It("returns upload URLs for simple and tus", func() { + res, err := serviceClient.InitiateFileUpload(ctx, &storagep.InitiateFileUploadRequest{Ref: fileRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(res.Protocols)).To(Equal(2)) + }) + } + + assertDownloads := func() { + It("returns 'simple' download URLs", func() { + res, err := serviceClient.InitiateFileDownload(ctx, &storagep.InitiateFileDownloadRequest{Ref: fileRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(res.Protocols)).To(Equal(1)) + }) + } + + assertRecycle := func() { + It("lists and restores resources", func() { + By("deleting an item") + res, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("listing the recycle items") + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + Expect(len(listRes.RecycleItems)).To(Equal(1)) + item := listRes.RecycleItems[0] + Expect(item.Path).To(Equal(subdirPath)) + + By("restoring a recycle item") + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + + restoreRes, err := serviceClient.RestoreRecycleItem(ctx, + &storagep.RestoreRecycleItemRequest{ + Ref: subdirRef, + Key: item.Key, + }, + ) + Expect(err).ToNot(HaveOccurred()) + Expect(restoreRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + It("purges recycle items resources", func() { + By("deleting an item") + res, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("listing recycle items") + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(listRes.RecycleItems)).To(Equal(1)) + + By("purging a recycle item") + purgeRes, err := serviceClient.PurgeRecycle(ctx, &storagep.PurgeRecycleRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(purgeRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + listRes, err = serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(listRes.RecycleItems)).To(Equal(0)) + }) + } + + assertReferences := func() { + It("creates references", func() { + listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: sharesRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) + Expect(len(listRes.Infos)).To(Equal(0)) + + res, err := serviceClient.CreateReference(ctx, &storagep.CreateReferenceRequest{Path: "/Shares/reference", TargetUri: "scheme://target"}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + listRes, err = serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: sharesRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(len(listRes.Infos)).To(Equal(1)) + }) + } + + assertMetadata := func() { + It("sets and unsets metadata", func() { + statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(statRes.Info.ArbitraryMetadata.Metadata["foo"]).To(BeEmpty()) + + By("setting arbitrary metadata") + samRes, err := serviceClient.SetArbitraryMetadata(ctx, &storagep.SetArbitraryMetadataRequest{ + Ref: subdirRef, + ArbitraryMetadata: &storagep.ArbitraryMetadata{Metadata: map[string]string{"foo": "bar"}}, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(samRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(statRes.Info.ArbitraryMetadata.Metadata["foo"]).To(Equal("bar")) + + By("unsetting arbitrary metadata") + uamRes, err := serviceClient.UnsetArbitraryMetadata(ctx, &storagep.UnsetArbitraryMetadataRequest{ + Ref: subdirRef, + ArbitraryMetadataKeys: []string{"foo"}, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(uamRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(statRes.Info.ArbitraryMetadata.Metadata["foo"]).To(BeEmpty()) + }) + } + + Describe("ocis", func() { + BeforeEach(func() { + dependencies = map[string]string{ + "storage": "storageprovider-ocis.toml", + } + }) + + assertCreateHome() + + Context("with a home and a subdirectory", func() { + JustBeforeEach(func() { + res, err := serviceClient.CreateHome(ctx, &storagep.CreateHomeRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + subdirRes, err := serviceClient.CreateContainer(ctx, &storagep.CreateContainerRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(subdirRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + assertCreateContainer() + assertListContainer() + assertGetPath() + assertDelete() + assertMove() + assertGrants() + assertUploads() + assertDownloads() + assertRecycle() + assertReferences() + assertMetadata() + }) + + Context("with an existing file /versioned_file", func() { + JustBeforeEach(func() { + fs, err := ocis.New(map[string]interface{}{ + "root": revads["storage"].TmpRoot, + "enable_home": true, + }) + Expect(err).ToNot(HaveOccurred()) + + content1 := ioutil.NopCloser(bytes.NewReader([]byte("1"))) + content2 := ioutil.NopCloser(bytes.NewReader([]byte("22"))) + + ctx := ruser.ContextSetUser(context.Background(), user) + + err = fs.CreateHome(ctx) + Expect(err).ToNot(HaveOccurred()) + err = fs.Upload(ctx, versionedFileRef, content1) + Expect(err).ToNot(HaveOccurred()) + err = fs.Upload(ctx, versionedFileRef, content2) + Expect(err).ToNot(HaveOccurred()) + }) + + assertFileVersions() + }) + }) + + Describe("owncloud", func() { + BeforeEach(func() { + dependencies = map[string]string{ + "users": "userprovider-json.toml", + "storage": "storageprovider-owncloud.toml", + } + + redisAddress := os.Getenv("REDIS_ADDRESS") + if redisAddress == "" { + Fail("REDIS_ADDRESS not set") + } + variables = map[string]string{ + "redis_address": redisAddress, + } + }) + + assertCreateHome() + + Context("with a home and a subdirectory", func() { + JustBeforeEach(func() { + res, err := serviceClient.CreateHome(ctx, &storagep.CreateHomeRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + subdirRes, err := serviceClient.CreateContainer(ctx, &storagep.CreateContainerRequest{Ref: subdirRef}) + Expect(err).ToNot(HaveOccurred()) + Expect(subdirRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + assertCreateContainer() + assertListContainer() + assertGetPath() + assertDelete() + assertMove() + assertGrants() + assertUploads() + assertDownloads() + assertRecycle() + assertReferences() + assertMetadata() + }) + + Context("with an existing file /versioned_file", func() { + JustBeforeEach(func() { + fs, err := owncloud.New(map[string]interface{}{ + "datadirectory": revads["storage"].TmpRoot, + "userprovidersvc": revads["users"].GrpcAddress, + "enable_home": true, + }) + Expect(err).ToNot(HaveOccurred()) + + content1 := ioutil.NopCloser(bytes.NewReader([]byte("1"))) + content2 := ioutil.NopCloser(bytes.NewReader([]byte("22"))) + + ctx := ruser.ContextSetUser(context.Background(), user) + + err = fs.CreateHome(ctx) + Expect(err).ToNot(HaveOccurred()) + err = fs.Upload(ctx, versionedFileRef, content1) + Expect(err).ToNot(HaveOccurred()) + err = fs.Upload(ctx, versionedFileRef, content2) + Expect(err).ToNot(HaveOccurred()) + }) + + assertFileVersions() + }) + }) +}) diff --git a/tests/integration/grpc/userprovider_test.go b/tests/integration/grpc/userprovider_test.go new file mode 100644 index 0000000000..3af9633786 --- /dev/null +++ b/tests/integration/grpc/userprovider_test.go @@ -0,0 +1,262 @@ +// 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 grpc_test + +import ( + "context" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "google.golang.org/grpc/metadata" + + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/token" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" + ruser "github.com/cs3org/reva/pkg/user" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("user providers", func() { + var ( + dependencies map[string]string + revads map[string]*Revad + + existingIdp string + + ctx context.Context + serviceClient userpb.UserAPIClient + ) + + JustBeforeEach(func() { + var err error + ctx = context.Background() + + // Add auth token + user := &userpb.User{ + Id: &userpb.UserId{ + Idp: existingIdp, + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + }, + } + tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) + Expect(err).ToNot(HaveOccurred()) + t, err := tokenManager.MintToken(ctx, user) + Expect(err).ToNot(HaveOccurred()) + ctx = token.ContextSetToken(ctx, t) + ctx = metadata.AppendToOutgoingContext(ctx, token.TokenHeader, t) + ctx = ruser.ContextSetUser(ctx, user) + + revads, err = startRevads(dependencies, map[string]string{}) + Expect(err).ToNot(HaveOccurred()) + serviceClient, err = pool.GetUserProviderServiceClient(revads["users"].GrpcAddress) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + for _, r := range revads { + Expect(r.Cleanup(CurrentGinkgoTestDescription().Failed)) + } + }) + + var assertGetUserByClaimResponses = func() { + It("gets users as expected", func() { + tests := map[string]string{ + "mail": "einstein@example.org", + "username": "einstein", + "uid": "123", + } + + for claim, value := range tests { + user, err := serviceClient.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{Claim: claim, Value: value}) + Expect(err).ToNot(HaveOccurred()) + Expect(user.User.Mail).To(Equal("einstein@example.org")) + } + }) + } + + var assertGetUserResponses = func() { + It("gets users as expected", func() { + tests := []struct { + name string + userID *userpb.UserId + want *userpb.GetUserResponse + }{ + { + name: "simple", + userID: &userpb.UserId{ + Idp: existingIdp, + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + }, + want: &userpb.GetUserResponse{ + Status: &rpc.Status{ + Code: 1, + }, + User: &userpb.User{ + Username: "marie", + Mail: "marie@example.org", + DisplayName: "Marie Curie", + Groups: []string{ + "radium-lovers", + "polonium-lovers", + "physics-lovers", + }, + }, + }, + }, + { + name: "not-existing opaqueId", + userID: &userpb.UserId{ + Idp: existingIdp, + OpaqueId: "doesnote-xist-4376-b307-cf0a8c2d0d9c", + }, + want: &userpb.GetUserResponse{ + Status: &rpc.Status{ + Code: 15, + }, + }, + }, + { + name: "no opaqueId", + userID: &userpb.UserId{ + Idp: existingIdp, + OpaqueId: "", + }, + want: &userpb.GetUserResponse{ + Status: &rpc.Status{ + Code: 15, + }, + }, + }, + { + name: "not-existing idp", + userID: &userpb.UserId{ + Idp: "http://does-not-exist:12345", + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + }, + want: &userpb.GetUserResponse{ + Status: &rpc.Status{ + Code: 15, + }, + }, + }, + { + name: "no idp", + userID: &userpb.UserId{ + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + }, + want: &userpb.GetUserResponse{ + Status: &rpc.Status{ + Code: 1, + }, + User: &userpb.User{ + Username: "marie", + Mail: "marie@example.org", + DisplayName: "Marie Curie", + Groups: []string{ + "radium-lovers", + "polonium-lovers", + "physics-lovers", + }, + }, + }, + }, + } + + for _, t := range tests { + userResp, err := serviceClient.GetUser(ctx, &userpb.GetUserRequest{ + UserId: t.userID, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(t.want.Status.Code).To(Equal(userResp.Status.Code)) + if t.want.User == nil { + Expect(userResp.User).To(BeNil()) + } else { + //make sure not to run into a nil pointer error + Expect(userResp.User).ToNot(BeNil()) + Expect(t.want.User.Username).To(Equal(userResp.User.Username)) + Expect(t.want.User.Mail).To(Equal(userResp.User.Mail)) + Expect(t.want.User.DisplayName).To(Equal(userResp.User.DisplayName)) + Expect(t.want.User.Groups).To(Equal(userResp.User.Groups)) + } + } + }) + } + + var assertFindUsersResponses = func() { + It("finds users by email", func() { + res, err := serviceClient.FindUsers(ctx, &userpb.FindUsersRequest{Filter: "marie@example.org"}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Users)).To(Equal(1)) + user := res.Users[0] + Expect(user.DisplayName).To(Equal("Marie Curie")) + }) + + It("finds users by displayname", func() { + res, err := serviceClient.FindUsers(ctx, &userpb.FindUsersRequest{Filter: "Marie Curie"}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Users)).To(Equal(1)) + user := res.Users[0] + Expect(user.Mail).To(Equal("marie@example.org")) + }) + + It("finds users by username", func() { + res, err := serviceClient.FindUsers(ctx, &userpb.FindUsersRequest{Filter: "marie"}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Users)).To(Equal(1)) + user := res.Users[0] + Expect(user.Mail).To(Equal("marie@example.org")) + }) + + It("finds users by id", func() { + res, err := serviceClient.FindUsers(ctx, &userpb.FindUsersRequest{Filter: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Users)).To(Equal(1)) + user := res.Users[0] + Expect(user.Mail).To(Equal("marie@example.org")) + }) + } + + Describe("the json userprovider", func() { + BeforeEach(func() { + dependencies = map[string]string{ + "users": "userprovider-json.toml", + } + existingIdp = "localhost:20080" + }) + + assertFindUsersResponses() + assertGetUserResponses() + assertGetUserByClaimResponses() + }) + + Describe("the demo userprovider", func() { + BeforeEach(func() { + dependencies = map[string]string{ + "users": "userprovider-demo.toml", + } + existingIdp = "http://localhost:9998" + }) + + assertGetUserResponses() + assertFindUsersResponses() + assertGetUserByClaimResponses() + }) +})