From 22a563b78954dd6fa09aad129ba2e0278024ce49 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 28 May 2021 15:53:08 +0200 Subject: [PATCH 01/23] Assign and consume user type when setting/reading users --- changelog/unreleased/user-type.md | 4 + cmd/reva/ocm-share-create.go | 12 +-- cmd/reva/share-create.go | 5 +- cmd/reva/transfer-create.go | 11 ++- .../usershareprovider/usershareprovider.go | 2 +- internal/http/services/ocmd/invites.go | 1 + internal/http/services/ocmd/shares.go | 1 + .../services/owncloud/ocs/conversions/main.go | 13 --- .../handlers/apps/sharing/shares/remote.go | 2 +- pkg/group/manager/json/json_test.go | 8 +- pkg/group/manager/ldap/ldap.go | 1 + pkg/ocm/invite/token/token_test.go | 2 + pkg/storage/fs/owncloud/upload.go | 3 + pkg/storage/registry/static/static_test.go | 91 ++++++++----------- pkg/storage/utils/decomposedfs/metadata.go | 3 +- pkg/storage/utils/decomposedfs/node/node.go | 24 ++++- .../utils/decomposedfs/node/node_test.go | 1 + .../utils/decomposedfs/testhelpers/helpers.go | 1 + pkg/storage/utils/decomposedfs/tree/tree.go | 7 ++ pkg/storage/utils/decomposedfs/upload.go | 3 + pkg/storage/utils/decomposedfs/upload_test.go | 1 + .../utils/decomposedfs/xattrs/xattrs.go | 9 +- pkg/storage/utils/localfs/localfs.go | 9 +- pkg/storage/utils/localfs/upload.go | 3 + pkg/user/manager/demo/demo.go | 3 + pkg/user/manager/demo/demo_test.go | 2 +- pkg/user/manager/json/json_test.go | 6 +- pkg/user/manager/ldap/ldap.go | 3 + pkg/utils/utils.go | 44 +++++++++ .../integration/grpc/storageprovider_test.go | 1 + tests/integration/grpc/userprovider_test.go | 5 + 31 files changed, 179 insertions(+), 102 deletions(-) create mode 100644 changelog/unreleased/user-type.md diff --git a/changelog/unreleased/user-type.md b/changelog/unreleased/user-type.md new file mode 100644 index 0000000000..24edc9b75f --- /dev/null +++ b/changelog/unreleased/user-type.md @@ -0,0 +1,4 @@ +Enhancement: Assign and consume user type when setting/reading users + +https://github.com/cs3org/reva/pull/1744 +https://github.com/cs3org/cs3apis/pull/120 diff --git a/cmd/reva/ocm-share-create.go b/cmd/reva/ocm-share-create.go index 3ec838fef8..686a1b4861 100644 --- a/cmd/reva/ocm-share-create.go +++ b/cmd/reva/ocm-share-create.go @@ -31,6 +31,7 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/utils" "github.com/jedib0t/go-pretty/table" "github.com/pkg/errors" ) @@ -42,10 +43,11 @@ func ocmShareCreateCommand() *command { grantType := cmd.String("type", "user", "grantee type (user or group)") grantee := cmd.String("grantee", "", "the grantee") idp := cmd.String("idp", "", "the idp of the grantee, default to same idp as the user triggering the action") + userType := cmd.String("user-type", "primary", "the type of user account, defaults to primary") rol := cmd.String("rol", "viewer", "the permission for the share (viewer or editor)") cmd.ResetFlags = func() { - *grantType, *grantee, *idp, *rol = "user", "", "", "viewer" + *grantType, *grantee, *idp, *rol, *userType = "user", "", "", "viewer", "primary" } cmd.Action = func(w ...io.Writer) error { @@ -77,8 +79,9 @@ func ocmShareCreateCommand() *command { return err } + u := &userpb.UserId{OpaqueId: *grantee, Idp: *idp, Type: utils.UserTypeMap(*userType)} remoteUserRes, err := client.GetAcceptedUser(ctx, &invitepb.GetAcceptedUserRequest{ - RemoteUserId: &userpb.UserId{OpaqueId: *grantee, Idp: *idp}, + RemoteUserId: u, }) if err != nil { return err @@ -109,10 +112,7 @@ func ocmShareCreateCommand() *command { Type: gt, // For now, we only support user shares. // TODO (ishank011): To be updated once this is decided. - Id: &provider.Grantee_UserId{UserId: &userpb.UserId{ - Idp: *idp, - OpaqueId: *grantee, - }}, + Id: &provider.Grantee_UserId{UserId: u}, }, } diff --git a/cmd/reva/share-create.go b/cmd/reva/share-create.go index 1513569cef..131cee2dbd 100644 --- a/cmd/reva/share-create.go +++ b/cmd/reva/share-create.go @@ -28,6 +28,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/utils" "github.com/jedib0t/go-pretty/table" "github.com/pkg/errors" ) @@ -40,9 +41,10 @@ func shareCreateCommand() *command { grantee := cmd.String("grantee", "", "the grantee") idp := cmd.String("idp", "", "the idp of the grantee, default to same idp as the user triggering the action") rol := cmd.String("rol", "viewer", "the permission for the share (viewer or editor)") + userType := cmd.String("user-type", "primary", "the type of user account, defaults to primary") cmd.ResetFlags = func() { - *grantType, *grantee, *idp, *rol = "user", "", "", "viewer" + *grantType, *grantee, *idp, *rol, *userType = "user", "", "", "viewer", "primary" } cmd.Action = func(w ...io.Writer) error { @@ -94,6 +96,7 @@ func shareCreateCommand() *command { grant.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{ Idp: *idp, OpaqueId: *grantee, + Type: utils.UserTypeMap(*userType), }} case "group": grant.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{ diff --git a/cmd/reva/transfer-create.go b/cmd/reva/transfer-create.go index f72d49c336..0a853a38be 100644 --- a/cmd/reva/transfer-create.go +++ b/cmd/reva/transfer-create.go @@ -32,6 +32,7 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/utils" "github.com/jedib0t/go-pretty/table" "github.com/pkg/errors" ) @@ -43,6 +44,7 @@ func transferCreateCommand() *command { grantee := cmd.String("grantee", "", "the grantee, receiver of the transfer") granteeType := cmd.String("granteeType", "user", "the grantee type, one of: user, group") idp := cmd.String("idp", "", "the idp of the grantee, default to same idp as the user triggering the action") + userType := cmd.String("user-type", "primary", "the type of user account, defaults to primary") cmd.Action = func(w ...io.Writer) error { if cmd.NArg() < 1 { @@ -65,9 +67,11 @@ func transferCreateCommand() *command { return err } + u := &userpb.UserId{OpaqueId: *grantee, Idp: *idp, Type: utils.UserTypeMap(*userType)} + // check if invitation has been accepted acceptedUserRes, err := client.GetAcceptedUser(ctx, &invitepb.GetAcceptedUserRequest{ - RemoteUserId: &userpb.UserId{OpaqueId: *grantee, Idp: *idp}, + RemoteUserId: u, }) if err != nil { return err @@ -127,10 +131,7 @@ func transferCreateCommand() *command { Grantee: &provider.Grantee{ Type: gt, Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: *idp, - OpaqueId: *grantee, - }, + UserId: u, }, }, Permissions: resourcePermissions, diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index a4997d405b..2df9180c81 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -112,7 +112,7 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar u := user.ContextMustGetUser(ctx) if req.Grant.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && req.Grant.Grantee.GetUserId().Idp == "" { // use logged in user Idp as default. - g := &userpb.UserId{OpaqueId: req.Grant.Grantee.GetUserId().OpaqueId, Idp: u.Id.Idp} + g := &userpb.UserId{OpaqueId: req.Grant.Grantee.GetUserId().OpaqueId, Idp: u.Id.Idp, Type: userpb.UserType_USER_TYPE_PRIMARY} req.Grant.Grantee.Id = &provider.Grantee_UserId{UserId: g} } share, err := s.sm.Share(ctx, req.ResourceInfo, req.Grant) diff --git a/internal/http/services/ocmd/invites.go b/internal/http/services/ocmd/invites.go index 2a1f4a7df1..60928f010e 100644 --- a/internal/http/services/ocmd/invites.go +++ b/internal/http/services/ocmd/invites.go @@ -227,6 +227,7 @@ func (h *invitesHandler) acceptInvite(w http.ResponseWriter, r *http.Request) { Id: &userpb.UserId{ OpaqueId: userID, Idp: recipientProvider, + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Mail: email, DisplayName: name, diff --git a/internal/http/services/ocmd/shares.go b/internal/http/services/ocmd/shares.go index 6be1158068..60ebfa9f6d 100644 --- a/internal/http/services/ocmd/shares.go +++ b/internal/http/services/ocmd/shares.go @@ -158,6 +158,7 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { ownerID := &userpb.UserId{ OpaqueId: owner, Idp: meshProvider, + Type: userpb.UserType_USER_TYPE_PRIMARY, } createShareReq := &ocmcore.CreateOCMCoreShareRequest{ Name: resource, diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index eff6129444..ffd714fded 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -252,19 +252,6 @@ func LocalGroupIDToString(groupID *grouppb.GroupId) string { return groupID.OpaqueId } -// UserIDToString transforms a cs3api user id into an ocs data model -// TODO This should be used instead of LocalUserIDToString bit it requires interpreting an @ on the client side -// TODO An alternative would be to send the idp / iss as an additional attribute. might be less intrusive -func UserIDToString(userID *userpb.UserId) string { - if userID == nil || userID.OpaqueId == "" { - return "" - } - if userID.Idp == "" { - return userID.OpaqueId - } - return userID.OpaqueId + "@" + userID.Idp -} - // GetUserManager returns a connection to a user share manager func GetUserManager(manager string, m map[string]map[string]interface{}) (user.Manager, error) { if f, ok := usermgr.NewFuncs[manager]; ok { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index f77db99cf6..ab7f38ce1c 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -59,7 +59,7 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque } remoteUserRes, err := c.GetAcceptedUser(ctx, &invitepb.GetAcceptedUserRequest{ - RemoteUserId: &userpb.UserId{OpaqueId: shareWithUser, Idp: shareWithProvider}, + RemoteUserId: &userpb.UserId{OpaqueId: shareWithUser, Idp: shareWithProvider, Type: userpb.UserType_USER_TYPE_PRIMARY}, }) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) diff --git a/pkg/group/manager/json/json_test.go b/pkg/group/manager/json/json_test.go index 581fb4d8d2..b79f1c2762 100644 --- a/pkg/group/manager/json/json_test.go +++ b/pkg/group/manager/json/json_test.go @@ -68,7 +68,7 @@ func TestUserManager(t *testing.T) { os.Remove(file.Name()) // json object with user meta data - userJSON = `[{"id":{"opaque_id":"sailing-lovers"},"group_name":"sailing-lovers","mail":"sailing-lovers@example.org","display_name":"Sailing Lovers","gid_number":1234,"members":[{"idp":"localhost","opaque_id":"einstein"},{"idp":"localhost","opaque_id":"marie"}]}]` + userJSON = `[{"id":{"opaque_id":"sailing-lovers"},"group_name":"sailing-lovers","mail":"sailing-lovers@example.org","display_name":"Sailing Lovers","gid_number":1234,"members":[{"idp":"localhost","opaque_id":"einstein","type":1},{"idp":"localhost","opaque_id":"marie","type":1}]}]` // get file handler for temporary file file, err = ioutil.TempFile(tempdir, "json_test") @@ -91,8 +91,8 @@ func TestUserManager(t *testing.T) { // setup test data gid := &grouppb.GroupId{OpaqueId: "sailing-lovers"} - uidEinstein := &userpb.UserId{Idp: "localhost", OpaqueId: "einstein"} - uidMarie := &userpb.UserId{Idp: "localhost", OpaqueId: "marie"} + uidEinstein := &userpb.UserId{Idp: "localhost", OpaqueId: "einstein", Type: userpb.UserType_USER_TYPE_PRIMARY} + uidMarie := &userpb.UserId{Idp: "localhost", OpaqueId: "marie", Type: userpb.UserType_USER_TYPE_PRIMARY} members := []*userpb.UserId{uidEinstein, uidMarie} group := &grouppb.Group{ Id: gid, @@ -143,7 +143,7 @@ func TestUserManager(t *testing.T) { } // negative test HasMember - resMemberNegative, _ := manager.HasMember(ctx, gid, &userpb.UserId{Idp: "localhost", OpaqueId: "fake-user"}) + resMemberNegative, _ := manager.HasMember(ctx, gid, &userpb.UserId{Idp: "localhost", OpaqueId: "fake-user", Type: userpb.UserType_USER_TYPE_PRIMARY}) if resMemberNegative != false { t.Fatalf("result differs: expected=%v got=%v", false, resMemberNegative) } diff --git a/pkg/group/manager/ldap/ldap.go b/pkg/group/manager/ldap/ldap.go index 9905a1a74f..a41cec3625 100644 --- a/pkg/group/manager/ldap/ldap.go +++ b/pkg/group/manager/ldap/ldap.go @@ -353,6 +353,7 @@ func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*user users = append(users, &userpb.UserId{ OpaqueId: entry.GetEqualFoldAttributeValue(m.c.Schema.CN), Idp: m.c.Idp, + Type: userpb.UserType_USER_TYPE_PRIMARY, }) } diff --git a/pkg/ocm/invite/token/token_test.go b/pkg/ocm/invite/token/token_test.go index 4c65c30398..1938c73d82 100644 --- a/pkg/ocm/invite/token/token_test.go +++ b/pkg/ocm/invite/token/token_test.go @@ -31,6 +31,7 @@ func TestCreateToken(t *testing.T) { Id: &userpb.UserId{ Idp: "http://localhost:20080", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "", Mail: "", @@ -63,6 +64,7 @@ func TestCreateTokenCollision(t *testing.T) { Id: &userpb.UserId{ Idp: "http://localhost:20080", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "", Mail: "", diff --git a/pkg/storage/fs/owncloud/upload.go b/pkg/storage/fs/owncloud/upload.go index 181f86e643..c45ec3120f 100644 --- a/pkg/storage/fs/owncloud/upload.go +++ b/pkg/storage/fs/owncloud/upload.go @@ -40,6 +40,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" "github.com/pkg/xattr" @@ -212,6 +213,7 @@ func (fs *ocfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tusd. "Idp": usr.Id.Idp, "UserId": usr.Id.OpaqueId, + "UserType": utils.UserTypeToString(usr.Id.Type), "UserName": usr.Username, "LogLevel": log.GetLevel().String(), @@ -285,6 +287,7 @@ func (fs *ocfs) GetUpload(ctx context.Context, id string) (tusd.Upload, error) { Id: &userpb.UserId{ Idp: info.Storage["Idp"], OpaqueId: info.Storage["UserId"], + Type: utils.UserTypeMap(info.Storage["UserType"]), }, Username: info.Storage["UserName"], } diff --git a/pkg/storage/registry/static/static_test.go b/pkg/storage/registry/static/static_test.go index 1872ba8aab..f96bd55d77 100644 --- a/pkg/storage/registry/static/static_test.go +++ b/pkg/storage/registry/static/static_test.go @@ -104,23 +104,26 @@ var _ = Describe("Static", func() { }) }) + home00 := ®istrypb.ProviderInfo{ + ProviderPath: "/home", + Address: "home-00-home", + } + home01 := ®istrypb.ProviderInfo{ + ProviderPath: "/home", + Address: "home-01-home", + } + Describe("GetHome", func() { It("get the home provider for user alice", func() { home, err := handler.GetHome(ctxAlice) Expect(err).ToNot(HaveOccurred()) - Expect(home).To(Equal(®istrypb.ProviderInfo{ - ProviderPath: "/home", - Address: "home-00-home", - })) + Expect(home).To(Equal(home00)) }) It("get the home provider for user robert", func() { home, err := handler.GetHome(ctxRobert) Expect(err).ToNot(HaveOccurred()) - Expect(home).To(Equal(®istrypb.ProviderInfo{ - ProviderPath: "/home", - Address: "home-01-home", - })) + Expect(home).To(Equal(home01)) }) }) @@ -130,96 +133,80 @@ var _ = Describe("Static", func() { It("finds all providers for user alice for a home ref", func() { providers, err := handler.FindProviders(ctxAlice, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/home", - Address: "home-00-home", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{home00})) }) It("finds all providers for user robert for a home ref", func() { providers, err := handler.FindProviders(ctxRobert, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/home", - Address: "home-01-home", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{home01})) }) }) Describe("FindProviders for eos reference", func() { ref := &provider.Reference{Path: "/eos/user/b/bob/xyz"} + eosUserB := ®istrypb.ProviderInfo{ + ProviderPath: "/eos/user/b", + Address: "home-00-eos", + } It("finds all providers for user alice for an eos ref", func() { providers, err := handler.FindProviders(ctxAlice, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/eos/user/b", - Address: "home-00-eos", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{eosUserB})) }) It("finds all providers for user robert for an eos ref", func() { providers, err := handler.FindProviders(ctxRobert, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/eos/user/b", - Address: "home-00-eos", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{eosUserB})) }) }) Describe("FindProviders for project reference", func() { ref := &provider.Reference{Path: "/eos/project/pqr"} + eosProject := ®istrypb.ProviderInfo{ + ProviderPath: "/eos/project", + Address: "project-00", + } It("finds all providers for user alice for a project ref", func() { providers, err := handler.FindProviders(ctxAlice, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/eos/project", - Address: "project-00", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{eosProject})) }) It("finds all providers for user robert for a project ref", func() { providers, err := handler.FindProviders(ctxRobert, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderPath: "/eos/project", - Address: "project-00", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{eosProject})) }) }) Describe("FindProviders for virtual references", func() { - ref1 := &provider.Reference{Path: "/eos"} - ref2 := &provider.Reference{Path: "/"} + refEos := &provider.Reference{Path: "/eos"} + refRoot := &provider.Reference{Path: "/"} It("finds all providers for user alice for a virtual eos ref", func() { - providers, err := handler.FindProviders(ctxAlice, ref1) + providers, err := handler.FindProviders(ctxAlice, refEos) Expect(err).ToNot(HaveOccurred()) Expect(len(providers)).To(Equal(eosProviders)) }) It("finds all providers for user robert for a virtual eos ref", func() { - providers, err := handler.FindProviders(ctxRobert, ref1) + providers, err := handler.FindProviders(ctxRobert, refEos) Expect(err).ToNot(HaveOccurred()) Expect(len(providers)).To(Equal(eosProviders)) }) It("finds all providers for user alice for a virtual root ref", func() { - providers, err := handler.FindProviders(ctxAlice, ref2) + providers, err := handler.FindProviders(ctxAlice, refRoot) Expect(err).ToNot(HaveOccurred()) Expect(len(providers)).To(Equal(rootProviders)) }) It("finds all providers for user robert for a virtual root ref", func() { - providers, err := handler.FindProviders(ctxRobert, ref2) + providers, err := handler.FindProviders(ctxRobert, refRoot) Expect(err).ToNot(HaveOccurred()) Expect(len(providers)).To(Equal(rootProviders)) }) @@ -231,25 +218,21 @@ var _ = Describe("Static", func() { StorageId: "123e4567-e89b-12d3-a456-426655440000", }, } + home00ID := ®istrypb.ProviderInfo{ + ProviderId: "123e4567-e89b-12d3-a456-426655440000", + Address: "home-00-home", + } It("finds all providers for user alice for ref containing ID", func() { providers, err := handler.FindProviders(ctxAlice, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderId: "123e4567-e89b-12d3-a456-426655440000", - Address: "home-00-home", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{home00ID})) }) It("finds all providers for user robert for ref containing ID", func() { providers, err := handler.FindProviders(ctxRobert, ref) Expect(err).ToNot(HaveOccurred()) - Expect(providers).To(Equal([]*registrypb.ProviderInfo{ - ®istrypb.ProviderInfo{ - ProviderId: "123e4567-e89b-12d3-a456-426655440000", - Address: "home-00-home", - }})) + Expect(providers).To(Equal([]*registrypb.ProviderInfo{home00ID})) }) }) }) diff --git a/pkg/storage/utils/decomposedfs/metadata.go b/pkg/storage/utils/decomposedfs/metadata.go index f16f87e168..f6806cbf5a 100644 --- a/pkg/storage/utils/decomposedfs/metadata.go +++ b/pkg/storage/utils/decomposedfs/metadata.go @@ -29,6 +29,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" "github.com/pkg/xattr" ) @@ -153,7 +154,7 @@ func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provide if u, ok := user.ContextGetUser(ctx); ok { // the favorite flag is specific to the user, so we need to incorporate the userid if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", xattrs.FavPrefix, uid.GetOpaqueId(), uid.GetIdp()) + fa := fmt.Sprintf("%s:%s:%s@%s", xattrs.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) if err := xattr.Remove(nodePath, fa); err != nil { sublog.Error().Err(err). Interface("user", u). diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index 660165ef32..4b8a1f9f18 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -47,6 +47,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/ace" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" ) // Define keys and values used in the node metadata @@ -123,6 +124,9 @@ func (n *Node) WriteMetadata(owner *userpb.UserId) (err error) { if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte("")); err != nil { return errors.Wrap(err, "Decomposedfs: could not set empty owner idp attribute") } + if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte("")); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set empty owner type attribute") + } } else { if err = xattr.Set(nodePath, xattrs.OwnerIDAttr, []byte(owner.OpaqueId)); err != nil { return errors.Wrap(err, "Decomposedfs: could not set owner id attribute") @@ -130,6 +134,9 @@ func (n *Node) WriteMetadata(owner *userpb.UserId) (err error) { if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte(owner.Idp)); err != nil { return errors.Wrap(err, "Decomposedfs: could not set owner idp attribute") } + if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte(utils.UserTypeToString(owner.Type))); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set owner idp attribute") + } } return } @@ -274,7 +281,7 @@ func (n *Node) Owner() (o *userpb.UserId, err error) { nodePath := n.InternalPath() // lookup parent id in extended attributes var attrBytes []byte - // lookup name in extended attributes + // lookup ID in extended attributes if attrBytes, err = xattr.Get(nodePath, xattrs.OwnerIDAttr); err == nil { if n.owner == nil { n.owner = &userpb.UserId{} @@ -283,7 +290,7 @@ func (n *Node) Owner() (o *userpb.UserId, err error) { } else { return } - // lookup name in extended attributes + // lookup IDP in extended attributes if attrBytes, err = xattr.Get(nodePath, xattrs.OwnerIDPAttr); err == nil { if n.owner == nil { n.owner = &userpb.UserId{} @@ -292,6 +299,15 @@ func (n *Node) Owner() (o *userpb.UserId, err error) { } else { return } + // lookup type in extended attributes + if attrBytes, err = xattr.Get(nodePath, xattrs.OwnerTypeAttr); err == nil { + if n.owner == nil { + n.owner = &userpb.UserId{} + } + n.owner.Type = utils.UserTypeMap(string(attrBytes)) + } else { + return + } return n.owner, err } @@ -410,7 +426,7 @@ func (n *Node) SetEtag(ctx context.Context, val string) (err error) { func (n *Node) SetFavorite(uid *userpb.UserId, val string) error { nodePath := n.lu.InternalPath(n.ID) // the favorite flag is specific to the user, so we need to incorporate the userid - fa := fmt.Sprintf("%s%s@%s", xattrs.FavPrefix, uid.GetOpaqueId(), uid.GetIdp()) + fa := fmt.Sprintf("%s:%s:%s@%s", xattrs.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) return xattr.Set(nodePath, fa, []byte(val)) } @@ -516,7 +532,7 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi if u, ok := user.ContextGetUser(ctx); ok { // the favorite flag is specific to the user, so we need to incorporate the userid if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", xattrs.FavPrefix, uid.GetOpaqueId(), uid.GetIdp()) + fa := fmt.Sprintf("%s:%s:%s@%s", xattrs.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) if val, err := xattr.Get(nodePath, fa); err == nil { sublog.Debug(). Str("favorite", fa). diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index 1b0a379cbf..84796bb8a6 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -85,6 +85,7 @@ var _ = Describe("Node", func() { owner := &userpb.UserId{ Idp: "testidp", OpaqueId: "testuserid", + Type: userpb.UserType_USER_TYPE_PRIMARY, } err = n.WriteMetadata(owner) diff --git a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go index 73015d9251..ac2396bbff 100644 --- a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go +++ b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go @@ -79,6 +79,7 @@ func NewTestEnv() (*TestEnv, error) { Id: &userpb.UserId{ Idp: "idp", OpaqueId: "userid", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "username", } diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index 1724c6175d..9e0c6d6ae0 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -35,6 +35,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" "github.com/pkg/xattr" @@ -743,6 +744,12 @@ func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.N } else { return } + // lookup ownerType in extended attributes + if attrBytes, err = xattr.Get(deletedNodePath, xattrs.OwnerTypeAttr); err == nil { + owner.Type = utils.UserTypeMap(string(attrBytes)) + } else { + return + } n = node.New(nodeID, "", "", 0, "", owner, t.lookup) // lookup blobID in extended attributes diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index fd72a4dae2..63d2e71838 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -42,6 +42,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -262,6 +263,7 @@ func (fs *Decomposedfs) NewUpload(ctx context.Context, info tusd.FileInfo) (uplo "Idp": usr.Id.Idp, "UserId": usr.Id.OpaqueId, + "UserType": utils.UserTypeToString(usr.Id.Type), "UserName": usr.Username, "OwnerIdp": owner.Idp, @@ -332,6 +334,7 @@ func (fs *Decomposedfs) GetUpload(ctx context.Context, id string) (tusd.Upload, Id: &userpb.UserId{ Idp: info.Storage["Idp"], OpaqueId: info.Storage["UserId"], + Type: utils.UserTypeMap(info.Storage["UserType"]), }, Username: info.Storage["UserName"], } diff --git a/pkg/storage/utils/decomposedfs/upload_test.go b/pkg/storage/utils/decomposedfs/upload_test.go index 903f93bb2d..8a27f03f24 100644 --- a/pkg/storage/utils/decomposedfs/upload_test.go +++ b/pkg/storage/utils/decomposedfs/upload_test.go @@ -61,6 +61,7 @@ var _ = Describe("File uploads", func() { Id: &userpb.UserId{ Idp: "idp", OpaqueId: "userid", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "username", } diff --git a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go index 5f2c3f4678..c36b1eba45 100644 --- a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go +++ b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go @@ -27,10 +27,11 @@ package xattrs // collisions with other apps We are going to introduce a sub namespace // "user.ocis." const ( - OcisPrefix string = "user.ocis." - ParentidAttr string = OcisPrefix + "parentid" - OwnerIDAttr string = OcisPrefix + "owner.id" - OwnerIDPAttr string = OcisPrefix + "owner.idp" + OcisPrefix string = "user.ocis." + ParentidAttr string = OcisPrefix + "parentid" + OwnerIDAttr string = OcisPrefix + "owner.id" + OwnerIDPAttr string = OcisPrefix + "owner.idp" + OwnerTypeAttr string = OcisPrefix + "owner.type" // the base name of the node // updated when the file is renamed or moved NameAttr string = OcisPrefix + "name" diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 4b07a91f20..572590567c 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -43,6 +43,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/grants" "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) @@ -452,9 +453,9 @@ func (fs *localfs) AddGrant(ctx context.Context, ref *provider.Reference, g *pro } var grantee string if granteeType == acl.TypeUser { - grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, g.Grantee.GetUserId().Idp) + grantee = fmt.Sprintf("%s:%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, utils.UserTypeToString(g.Grantee.GetUserId().Type), g.Grantee.GetUserId().Idp) } else if granteeType == acl.TypeGroup { - grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp) + grantee = fmt.Sprintf("%s::%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp) } err = fs.addToACLDB(ctx, fn, grantee, role) @@ -486,9 +487,9 @@ func (fs *localfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]* } grantSplit := strings.Split(granteeID, ":") grantee := &provider.Grantee{Type: grants.GetGranteeType(grantSplit[0])} - parts := strings.Split(grantSplit[1], "@") + parts := strings.Split(grantSplit[2], "@") if grantSplit[0] == acl.TypeUser { - grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: parts[0], Idp: parts[1]}} + grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: parts[0], Idp: parts[1], Type: utils.UserTypeMap(grantSplit[1])}} } else if grantSplit[0] == acl.TypeGroup { grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: parts[0], Idp: parts[1]}} } diff --git a/pkg/storage/utils/localfs/upload.go b/pkg/storage/utils/localfs/upload.go index d19556fc43..187fdbea86 100644 --- a/pkg/storage/utils/localfs/upload.go +++ b/pkg/storage/utils/localfs/upload.go @@ -32,6 +32,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" tusd "github.com/tus/tusd/pkg/handler" @@ -181,6 +182,7 @@ func (fs *localfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tu "Idp": usr.Id.Idp, "UserId": usr.Id.OpaqueId, "UserName": usr.Username, + "UserType": utils.UserTypeToString(usr.Id.Type), "LogLevel": log.GetLevel().String(), } @@ -249,6 +251,7 @@ func (fs *localfs) GetUpload(ctx context.Context, id string) (tusd.Upload, error Id: &userpb.UserId{ Idp: info.Storage["Idp"], OpaqueId: info.Storage["UserId"], + Type: utils.UserTypeMap(info.Storage["UserType"]), }, Username: info.Storage["UserName"], } diff --git a/pkg/user/manager/demo/demo.go b/pkg/user/manager/demo/demo.go index 75e3deec3d..17f18db469 100644 --- a/pkg/user/manager/demo/demo.go +++ b/pkg/user/manager/demo/demo.go @@ -105,6 +105,7 @@ func getUsers() map[string]*userpb.User { Id: &userpb.UserId{ Idp: "http://localhost:9998", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "einstein", Groups: []string{"sailing-lovers", "violin-haters", "physics-lovers"}, @@ -117,6 +118,7 @@ func getUsers() map[string]*userpb.User { Id: &userpb.UserId{ Idp: "http://localhost:9998", OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "marie", Groups: []string{"radium-lovers", "polonium-lovers", "physics-lovers"}, @@ -129,6 +131,7 @@ func getUsers() map[string]*userpb.User { Id: &userpb.UserId{ Idp: "http://localhost:9998", OpaqueId: "932b4540-8d16-481e-8ef4-588e4b6b151c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "richard", Groups: []string{"quantum-lovers", "philosophy-haters", "physics-lovers"}, diff --git a/pkg/user/manager/demo/demo_test.go b/pkg/user/manager/demo/demo_test.go index 509c69b2ef..9eab45cd26 100644 --- a/pkg/user/manager/demo/demo_test.go +++ b/pkg/user/manager/demo/demo_test.go @@ -34,7 +34,7 @@ func TestUserManager(t *testing.T) { manager, _ := New(nil) // setup test data - uidEinstein := &userpb.UserId{Idp: "http://localhost:9998", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51"} + uidEinstein := &userpb.UserId{Idp: "http://localhost:9998", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", Type: userpb.UserType_USER_TYPE_PRIMARY} userEinstein := &userpb.User{ Id: uidEinstein, Username: "einstein", diff --git a/pkg/user/manager/json/json_test.go b/pkg/user/manager/json/json_test.go index 9007dda2ed..66838bd983 100644 --- a/pkg/user/manager/json/json_test.go +++ b/pkg/user/manager/json/json_test.go @@ -67,7 +67,7 @@ func TestUserManager(t *testing.T) { os.Remove(file.Name()) // json object with user meta data - userJSON = `[{"id":{"idp":"localhost","opaque_id":"einstein"},"username":"einstein","mail":"einstein@example.org","display_name":"Albert Einstein","groups":["sailing-lovers","violin-haters","physics-lovers"]}]` + userJSON = `[{"id":{"idp":"localhost","opaque_id":"einstein","type":1},"username":"einstein","mail":"einstein@example.org","display_name":"Albert Einstein","groups":["sailing-lovers","violin-haters","physics-lovers"]}]` // get file handler for temporary file file, err = ioutil.TempFile(tempdir, "json_test") @@ -89,7 +89,7 @@ func TestUserManager(t *testing.T) { manager, _ := New(input) // setup test data - uidEinstein := &userpb.UserId{Idp: "localhost", OpaqueId: "einstein"} + uidEinstein := &userpb.UserId{Idp: "localhost", OpaqueId: "einstein", Type: userpb.UserType_USER_TYPE_PRIMARY} userEinstein := &userpb.User{ Id: uidEinstein, Username: "einstein", @@ -97,7 +97,7 @@ func TestUserManager(t *testing.T) { Mail: "einstein@example.org", DisplayName: "Albert Einstein", } - userFake := &userpb.UserId{Idp: "localhost", OpaqueId: "fakeUser"} + userFake := &userpb.UserId{Idp: "localhost", OpaqueId: "fakeUser", Type: userpb.UserType_USER_TYPE_PRIMARY} groupsEinstein := []string{"sailing-lovers", "violin-haters", "physics-lovers"} // positive test GetUserGroups diff --git a/pkg/user/manager/ldap/ldap.go b/pkg/user/manager/ldap/ldap.go index f830b44179..133a4f57bc 100644 --- a/pkg/user/manager/ldap/ldap.go +++ b/pkg/user/manager/ldap/ldap.go @@ -176,6 +176,7 @@ func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User id := &userpb.UserId{ Idp: m.c.Idp, OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, } groups, err := m.GetUserGroups(ctx, id) if err != nil { @@ -263,6 +264,7 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use id := &userpb.UserId{ Idp: m.c.Idp, OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, } groups, err := m.GetUserGroups(ctx, id) if err != nil { @@ -331,6 +333,7 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, id := &userpb.UserId{ Idp: m.c.Idp, OpaqueId: entry.GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, } groups, err := m.GetUserGroups(ctx, id) if err != nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index ae20e56886..13c28007dc 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -227,3 +227,47 @@ func MakeRelativePath(p string) string { } return "." + p } + +// UserTypeMap translates account type string to CS3 UserType +func UserTypeMap(accountType string) userpb.UserType { + var t userpb.UserType + switch accountType { + case "primary": + t = userpb.UserType_USER_TYPE_PRIMARY + case "secondary": + t = userpb.UserType_USER_TYPE_SECONDARY + case "service": + t = userpb.UserType_USER_TYPE_SERVICE + case "application": + t = userpb.UserType_USER_TYPE_APPLICATION + case "guest": + t = userpb.UserType_USER_TYPE_GUEST + case "federated": + t = userpb.UserType_USER_TYPE_FEDERATED + case "lightweight": + t = userpb.UserType_USER_TYPE_LIGHTWEIGHT + } + return t +} + +// UserTypeToString translates CS3 UserType to user-readable string +func UserTypeToString(accountType userpb.UserType) string { + var t string + switch accountType { + case userpb.UserType_USER_TYPE_PRIMARY: + t = "primary" + case userpb.UserType_USER_TYPE_SECONDARY: + t = "secondary" + case userpb.UserType_USER_TYPE_SERVICE: + t = "service" + case userpb.UserType_USER_TYPE_APPLICATION: + t = "application" + case userpb.UserType_USER_TYPE_GUEST: + t = "guest" + case userpb.UserType_USER_TYPE_FEDERATED: + t = "federated" + case userpb.UserType_USER_TYPE_LIGHTWEIGHT: + t = "lightweight" + } + return t +} diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 642283d980..0bacd54eaa 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -60,6 +60,7 @@ var _ = Describe("storage providers", func() { Id: &userpb.UserId{ Idp: "0.0.0.0:19000", OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, } diff --git a/tests/integration/grpc/userprovider_test.go b/tests/integration/grpc/userprovider_test.go index 0a19454e81..c345950437 100644 --- a/tests/integration/grpc/userprovider_test.go +++ b/tests/integration/grpc/userprovider_test.go @@ -54,6 +54,7 @@ var _ = Describe("user providers", func() { Id: &userpb.UserId{ Idp: existingIdp, OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, } tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) @@ -107,6 +108,7 @@ var _ = Describe("user providers", func() { userID: &userpb.UserId{ Idp: existingIdp, OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, want: &userpb.GetUserResponse{ Status: &rpc.Status{ @@ -129,6 +131,7 @@ var _ = Describe("user providers", func() { userID: &userpb.UserId{ Idp: existingIdp, OpaqueId: "doesnote-xist-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, want: &userpb.GetUserResponse{ Status: &rpc.Status{ @@ -141,6 +144,7 @@ var _ = Describe("user providers", func() { userID: &userpb.UserId{ Idp: existingIdp, OpaqueId: "", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, want: &userpb.GetUserResponse{ Status: &rpc.Status{ @@ -153,6 +157,7 @@ var _ = Describe("user providers", func() { userID: &userpb.UserId{ Idp: "http://does-not-exist:12345", OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, }, want: &userpb.GetUserResponse{ Status: &rpc.Status{ From 634b2982ac49340773fbda2345ad14b523ad3cc9 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 12:04:58 +0200 Subject: [PATCH 02/23] Add user type resolution to GRAPPA --- examples/meshdirectory/users.demo.json | 9 ++- examples/oc-phoenix/users.demo.json | 9 ++- examples/ocm-partners/users-ailleron.json | 12 ++- examples/ocm-partners/users-cern.json | 12 ++- examples/ocm-partners/users-cesnet.json | 9 ++- examples/ocm-partners/users-cubbit.json | 9 ++- examples/ocm-partners/users-dtu.json | 73 +++++++++---------- examples/ocm-partners/users-surfsara.json | 9 ++- examples/ocm-partners/users-switch.json | 12 ++- examples/ocm-partners/users-wwu.json | 9 ++- examples/ocmd/users.demo.json | 12 ++- examples/standalone/users.demo.json | 9 ++- examples/storage-references/users.demo.json | 9 ++- pkg/auth/manager/demo/demo.go | 3 + pkg/auth/manager/impersonator/impersonator.go | 2 +- pkg/auth/manager/ldap/ldap.go | 1 + pkg/auth/manager/oidc/oidc.go | 1 + pkg/cbox/user/rest/rest.go | 41 ++++++++++- 18 files changed, 158 insertions(+), 83 deletions(-) diff --git a/examples/meshdirectory/users.demo.json b/examples/meshdirectory/users.demo.json index d13a252b9b..12c784f7eb 100644 --- a/examples/meshdirectory/users.demo.json +++ b/examples/meshdirectory/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", diff --git a/examples/oc-phoenix/users.demo.json b/examples/oc-phoenix/users.demo.json index d13a252b9b..12c784f7eb 100644 --- a/examples/oc-phoenix/users.demo.json +++ b/examples/oc-phoenix/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", diff --git a/examples/ocm-partners/users-ailleron.json b/examples/ocm-partners/users-ailleron.json index 13e47a36ca..274463f27c 100644 --- a/examples/ocm-partners/users-ailleron.json +++ b/examples/ocm-partners/users-ailleron.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "jarek1234", - "idp": "softwaremind.com" + "idp": "softwaremind.com", + "type": 1 }, "username": "jarek", "secret": "jarekpass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "mateusz5678", - "idp": "softwaremind.com" + "idp": "softwaremind.com", + "type": 1 }, "username": "mateusz", "secret": "mateuszpass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "dawid9876", - "idp": "softwaremind.com" + "idp": "softwaremind.com", + "type": 1 }, "username": "dawid", "secret": "dawidpass", @@ -35,7 +38,8 @@ { "id": { "opaque_id": "test4242", - "idp": "softwaremind.com" + "idp": "softwaremind.com", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-cern.json b/examples/ocm-partners/users-cern.json index 3b6d3abd51..381db07928 100644 --- a/examples/ocm-partners/users-cern.json +++ b/examples/ocm-partners/users-cern.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "ishank1234", - "idp": "cern.ch" + "idp": "cern.ch", + "type": 1 }, "username": "ishank", "secret": "ishankpass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "hugo5678", - "idp": "cern.ch" + "idp": "cern.ch", + "type": 1 }, "username": "hugo", "secret": "hugopass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "samuel9876", - "idp": "cern.ch" + "idp": "cern.ch", + "type": 1 }, "username": "samuel", "secret": "samuelpass", @@ -35,7 +38,8 @@ { "id": { "opaque_id": "test4242", - "idp": "cern.ch" + "idp": "cern.ch", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-cesnet.json b/examples/ocm-partners/users-cesnet.json index 308bccdcb2..ecf238a0ed 100644 --- a/examples/ocm-partners/users-cesnet.json +++ b/examples/ocm-partners/users-cesnet.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "miroslav1234", - "idp": "cesnet.cz" + "idp": "cesnet.cz", + "type": 1 }, "username": "miroslav", "secret": "miroslavpass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "milan5678", - "idp": "cesnet.cz" + "idp": "cesnet.cz", + "type": 1 }, "username": "milan", "secret": "milanpass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "test4242", - "idp": "cesnet.cz" + "idp": "cesnet.cz", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-cubbit.json b/examples/ocm-partners/users-cubbit.json index 30004d1ae2..c2c4fca84a 100644 --- a/examples/ocm-partners/users-cubbit.json +++ b/examples/ocm-partners/users-cubbit.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "alessandro1234", - "idp": "cubbit.io" + "idp": "cubbit.io", + "type": 1 }, "username": "alessandro", "secret": "alessandropass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "lorenzo5678", - "idp": "cubbit.io" + "idp": "cubbit.io", + "type": 1 }, "username": "lorenzo", "secret": "lorenzopass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "test4242", - "idp": "cubbit.io" + "idp": "cubbit.io", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-dtu.json b/examples/ocm-partners/users-dtu.json index c70f9f69d9..543949edad 100644 --- a/examples/ocm-partners/users-dtu.json +++ b/examples/ocm-partners/users-dtu.json @@ -1,43 +1,38 @@ [ - - { - - - "id": { - "opaque_id": "marina1234", - "idp": "dtu.dk" - - }, - "username": "marina", - "secret": "marinapass", - "mail": "marpap@dtu.dk", - "display_name": "Marina Papathanasiou", - "groups": ["sailing-lovers", "violin-haters", "physics-lovers"] + { + "id": { + "opaque_id": "marina1234", + "idp": "dtu.dk", + "type": 1 }, - { - "id": { - "opaque_id": "frederik7899", - "idp": "dtu.dk" - - }, - "username": "frederik", - "secret": "frederikpass", - "mail": "frederik.orellana@deic.dk", - "display_name": "Frederik Orellana", - "groups": ["radium-lovers", "polonium-lovers", "physics-lovers"] - - + "username": "marina", + "secret": "marinapass", + "mail": "marpap@dtu.dk", + "display_name": "Marina Papathanasiou", + "groups": ["sailing-lovers", "violin-haters", "physics-lovers"] + }, + { + "id": { + "opaque_id": "frederik7899", + "idp": "dtu.dk", + "type": 1 }, - { - "id": { - "opaque_id": "test3456", - "idp": "dtu.dk" - }, - "username": "test", - "secret": "testpass", - "mail": "test7@dtu.dk", - "display_name": "User_test", - "groups": ["test-lovers", "bug-haters", "ai-lovers"] - } - + "username": "frederik", + "secret": "frederikpass", + "mail": "frederik.orellana@deic.dk", + "display_name": "Frederik Orellana", + "groups": ["radium-lovers", "polonium-lovers", "physics-lovers"] + }, + { + "id": { + "opaque_id": "test3456", + "idp": "dtu.dk", + "type": 1 + }, + "username": "test", + "secret": "testpass", + "mail": "test7@dtu.dk", + "display_name": "User_test", + "groups": ["test-lovers", "bug-haters", "ai-lovers"] + } ] diff --git a/examples/ocm-partners/users-surfsara.json b/examples/ocm-partners/users-surfsara.json index 0edb001fd6..be00e7f856 100644 --- a/examples/ocm-partners/users-surfsara.json +++ b/examples/ocm-partners/users-surfsara.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "ron1234", - "idp": "surfsara.nl" + "idp": "surfsara.nl", + "type": 1 }, "username": "ron", "secret": "ronpass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "antoon5678", - "idp": "surfsara.nl" + "idp": "surfsara.nl", + "type": 1 }, "username": "antoon", "secret": "antoonpass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "test4242", - "idp": "surfsara.nl" + "idp": "surfsara.nl", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-switch.json b/examples/ocm-partners/users-switch.json index b2c080b828..76c65aeee2 100644 --- a/examples/ocm-partners/users-switch.json +++ b/examples/ocm-partners/users-switch.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "kerins1234", - "idp": "switch.ch" + "idp": "switch.ch", + "type": 1 }, "username": "kerins", "secret": "kerinspass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "furter5678", - "idp": "switch.ch" + "idp": "switch.ch", + "type": 1 }, "username": "furter", "secret": "furterpass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "schmid9876", - "idp": "switch.ch" + "idp": "switch.ch", + "type": 1 }, "username": "schmid", "secret": "schmidpass", @@ -35,7 +38,8 @@ { "id": { "opaque_id": "test4242", - "idp": "switch.ch" + "idp": "switch.ch", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocm-partners/users-wwu.json b/examples/ocm-partners/users-wwu.json index 5b73f88ee0..9e9373ba12 100644 --- a/examples/ocm-partners/users-wwu.json +++ b/examples/ocm-partners/users-wwu.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "holger1234", - "idp": "uni-muenster.de" + "idp": "uni-muenster.de", + "type": 1 }, "username": "holger", "secret": "holgerpass", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "daniel5678", - "idp": "uni-muenster.de" + "idp": "uni-muenster.de", + "type": 1 }, "username": "daniel", "secret": "danielpass", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "test4242", - "idp": "uni-muenster.de" + "idp": "uni-muenster.de", + "type": 1 }, "username": "test", "secret": "testpass", diff --git a/examples/ocmd/users.demo.json b/examples/ocmd/users.demo.json index 21479c1ec7..e1b52cb806 100644 --- a/examples/ocmd/users.demo.json +++ b/examples/ocmd/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "cernbox.cern.ch" + "idp": "cernbox.cern.ch", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -27,7 +28,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "cesnet.cz" + "idp": "cesnet.cz", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -52,7 +54,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "example.org" + "idp": "example.org", + "type": 1 }, "username": "richard", "secret": "superfluidity", @@ -77,7 +80,8 @@ { "id": { "opaque_id": "932b4522-139b-4815-8ef4-42cdf82c3d51", - "idp": "example.com" + "idp": "example.com", + "type": 1 }, "username": "test", "secret": "test", diff --git a/examples/standalone/users.demo.json b/examples/standalone/users.demo.json index d13a252b9b..12c784f7eb 100644 --- a/examples/standalone/users.demo.json +++ b/examples/standalone/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", diff --git a/examples/storage-references/users.demo.json b/examples/storage-references/users.demo.json index d13a252b9b..12c784f7eb 100644 --- a/examples/storage-references/users.demo.json +++ b/examples/storage-references/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index d6807d0286..1ae50dbdc0 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -72,6 +72,7 @@ func getCredentials() map[string]Credentials { Id: &user.UserId{ Idp: "http://localhost:9998", OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + Type: user.UserType_USER_TYPE_PRIMARY, }, Username: "einstein", Groups: []string{"sailing-lovers", "violin-haters", "physics-lovers"}, @@ -85,6 +86,7 @@ func getCredentials() map[string]Credentials { Id: &user.UserId{ Idp: "http://localhost:9998", OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: user.UserType_USER_TYPE_PRIMARY, }, Username: "marie", Groups: []string{"radium-lovers", "polonium-lovers", "physics-lovers"}, @@ -98,6 +100,7 @@ func getCredentials() map[string]Credentials { Id: &user.UserId{ Idp: "http://localhost:9998", OpaqueId: "932b4540-8d16-481e-8ef4-588e4b6b151c", + Type: user.UserType_USER_TYPE_PRIMARY, }, Username: "richard", Groups: []string{"quantum-lovers", "philosophy-haters", "physics-lovers"}, diff --git a/pkg/auth/manager/impersonator/impersonator.go b/pkg/auth/manager/impersonator/impersonator.go index 68bab32836..26e7300930 100644 --- a/pkg/auth/manager/impersonator/impersonator.go +++ b/pkg/auth/manager/impersonator/impersonator.go @@ -43,7 +43,7 @@ func New(c map[string]interface{}) (auth.Manager, error) { func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { // allow passing in uid as @ at := strings.LastIndex(clientID, "@") - uid := &user.UserId{} + uid := &user.UserId{Type: user.UserType_USER_TYPE_PRIMARY} if at < 0 { uid.OpaqueId = clientID } else { diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index 32e31cf5d7..c062ecab13 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -174,6 +174,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) userID := &user.UserId{ Idp: am.c.Idp, OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(am.c.Schema.UID), + Type: user.UserType_USER_TYPE_PRIMARY, } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index 6958828ab3..b6b1f92201 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -141,6 +141,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) userID := &user.UserId{ OpaqueId: claims[am.c.IDClaim].(string), // a stable non reassignable id Idp: claims["issuer"].(string), // in the scope of this issuer + Type: user.UserType_USER_TYPE_PRIMARY, } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index e20927e5bd..43ac8ef79c 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -137,9 +137,12 @@ func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[st return nil, errors.New("rest: error in type assertion") } - if userData["type"].(string) == "Application" || strings.HasPrefix(userData["upn"].(string), "guest") { - return nil, errors.New("rest: guest and application accounts not supported") + t, _ := userData["type"].(string) + userType := getUserType(t, userData["upn"].(string)) + if userType == userpb.UserType_USER_TYPE_APPLICATION || userType == userpb.UserType_USER_TYPE_FEDERATED { + return nil, errors.New("rest: federated and application accounts not supported") } + return userData, nil } @@ -258,7 +261,7 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s for _, usr := range userData { usrInfo, ok := usr.(map[string]interface{}) - if !ok || usrInfo["type"].(string) == "Application" || strings.HasPrefix(usrInfo["upn"].(string), "guest") { + if !ok { continue } @@ -267,10 +270,17 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s name, _ := usrInfo["displayName"].(string) uidNumber, _ := usrInfo["uid"].(float64) gidNumber, _ := usrInfo["gid"].(float64) + t, _ := usrInfo["type"].(string) + + userType := getUserType(t, upn) + if userType == userpb.UserType_USER_TYPE_APPLICATION || userType == userpb.UserType_USER_TYPE_FEDERATED { + continue + } uid := &userpb.UserId{ OpaqueId: upn, Idp: m.conf.IDProvider, + Type: userType, } users[uid.OpaqueId] = &userpb.User{ Id: uid, @@ -374,3 +384,28 @@ func extractUID(u *userpb.User) (string, error) { } return strconv.FormatInt(u.UidNumber, 10), nil } + +func getUserType(userType, upn string) userpb.UserType { + var t userpb.UserType + switch userType { + case "Application": + t = userpb.UserType_USER_TYPE_APPLICATION + case "Service": + t = userpb.UserType_USER_TYPE_SERVICE + case "Secondary": + t = userpb.UserType_USER_TYPE_SECONDARY + case "Person": + switch { + case strings.HasPrefix(upn, "guest"): + t = userpb.UserType_USER_TYPE_LIGHTWEIGHT + case strings.Contains(upn, "@"): + t = userpb.UserType_USER_TYPE_FEDERATED + default: + t = userpb.UserType_USER_TYPE_PRIMARY + } + default: + t = userpb.UserType_USER_TYPE_INVALID + } + return t + +} From d165827ab070e9b81f9f18317e8f11e813cdd594 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 15:50:39 +0200 Subject: [PATCH 03/23] Add share scope --- internal/grpc/interceptors/auth/auth.go | 2 + pkg/auth/manager/demo/demo.go | 2 +- pkg/auth/manager/impersonator/impersonator.go | 2 +- pkg/auth/manager/json/json.go | 2 +- pkg/auth/manager/ldap/ldap.go | 2 +- pkg/auth/manager/oidc/oidc.go | 2 +- pkg/auth/manager/publicshares/publicshares.go | 2 +- pkg/auth/scope/lightweight.go | 117 ++++++++++++++++ pkg/auth/scope/publicshare.go | 26 ++-- pkg/auth/scope/resourceinfo.go | 28 ++-- pkg/auth/scope/scope.go | 7 +- pkg/auth/scope/share.go | 125 ++++++++++++++++++ pkg/auth/scope/user.go | 22 +-- 13 files changed, 293 insertions(+), 46 deletions(-) create mode 100644 pkg/auth/scope/lightweight.go create mode 100644 pkg/auth/scope/share.go diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index b7618b4686..31ed4e45d3 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -106,6 +106,8 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI return handler(ctx, req) } + log.Info().Msgf("GRPC unary interceptor %s, %+v", info.FullMethod, req) + span.AddAttributes(trace.BoolAttribute("auth_enabled", true)) tkn, ok := token.ContextGetToken(ctx) diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index 1ae50dbdc0..1e855f66c1 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -51,7 +51,7 @@ func New(m map[string]interface{}) (auth.Manager, error) { } func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/manager/impersonator/impersonator.go b/pkg/auth/manager/impersonator/impersonator.go index 26e7300930..0f9fd612ba 100644 --- a/pkg/auth/manager/impersonator/impersonator.go +++ b/pkg/auth/manager/impersonator/impersonator.go @@ -51,7 +51,7 @@ func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) ( uid.Idp = clientID[at+1:] } - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index 7c1e5f6ca6..dd876cd35d 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -106,7 +106,7 @@ func New(m map[string]interface{}) (auth.Manager, error) { } func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, map[string]*authpb.Scope, error) { - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index c062ecab13..ffeebbd58c 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -217,7 +217,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: gidNumber, } - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index b6b1f92201..7968affab1 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -172,7 +172,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: int64(gid), } - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/manager/publicshares/publicshares.go b/pkg/auth/manager/publicshares/publicshares.go index 0b2df90ee7..ceed142111 100644 --- a/pkg/auth/manager/publicshares/publicshares.go +++ b/pkg/auth/manager/publicshares/publicshares.go @@ -132,7 +132,7 @@ func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user if share.Permissions.Permissions.InitiateFileUpload { role = authpb.Role_ROLE_EDITOR } - scope, err := scope.GetPublicShareScope(share, role) + scope, err := scope.AddPublicShareScope(share, role, nil) if err != nil { return nil, nil, err } diff --git a/pkg/auth/scope/lightweight.go b/pkg/auth/scope/lightweight.go new file mode 100644 index 0000000000..aaf2942f3f --- /dev/null +++ b/pkg/auth/scope/lightweight.go @@ -0,0 +1,117 @@ +// 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 scope + +// import ( +// "fmt" +// "strings" +// +// authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" +// provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +// registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" +// types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" +// "github.com/cs3org/reva/pkg/errtypes" +// "github.com/cs3org/reva/pkg/utils" +// ) +// +// func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) { +// var r provider.ResourceInfo +// err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r) +// if err != nil { +// return false, err +// } +// +// switch v := resource.(type) { +// // Viewer role +// case *registry.GetStorageProvidersRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// case *provider.StatRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// case *provider.ListContainerRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// case *provider.InitiateFileDownloadRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// +// // Editor role +// // TODO(ishank011): Add role checks, +// // need to return appropriate status codes in the ocs/ocdav layers. +// case *provider.CreateContainerRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// case *provider.DeleteRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// case *provider.MoveRequest: +// return checkResourceInfo(&r, v.GetSource()) && checkResourceInfo(&r, v.GetDestination()), nil +// case *provider.InitiateFileUploadRequest: +// return checkResourceInfo(&r, v.GetRef()), nil +// +// case string: +// return checkPath(v), nil +// } +// +// return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) +// } +// +// func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool { +// // ref: > +// if ref.GetId() != nil { +// return inf.Id.StorageId == ref.GetId().StorageId && inf.Id.OpaqueId == ref.GetId().OpaqueId +// } +// // ref: +// if strings.HasPrefix(ref.GetPath(), inf.Path) { +// return true +// } +// return false +// } +// +// func checkPath(path string) bool { +// paths := []string{ +// "/dataprovider", +// "/data", +// } +// for _, p := range paths { +// if strings.HasPrefix(path, p) { +// return true +// } +// } +// return false +// } +// +// // AddLightweightAccountScope adds the scope to allow access to lightweight user. +// func AddLightweightAccountScope(scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { +// ref := &provider.Reference{ +// Spec: &provider.Reference_Path{ +// Path: "/", +// }, +// } +// val, err := utils.MarshalProtoV1ToJSON(ref) +// if err != nil { +// return nil, err +// } +// if scopes == nil { +// scopes = make(map[string]*authpb.Scope) +// } +// scopes["resourceinfo:"+r.Id.String()] = &authpb.Scope{ +// Resource: &types.OpaqueEntry{ +// Decoder: "json", +// Value: val, +// }, +// Role: role, +// } +// return scopes, nil +// } diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index 7a2e9ed8b7..31a0400df0 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -64,7 +64,7 @@ func publicshareScope(scope *authpb.Scope, resource interface{}) (bool, error) { case *link.GetPublicShareRequest: return checkPublicShareRef(&share, v.GetRef()), nil case string: - return checkPath(v), nil + return checkResourcePath(v), nil } return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) @@ -73,7 +73,7 @@ func publicshareScope(scope *authpb.Scope, resource interface{}) (bool, error) { func checkStorageRef(s *link.PublicShare, r *provider.Reference) bool { // r: > if r.ResourceId != nil && r.Path == "" { // path must be empty - return s.ResourceId.StorageId == r.ResourceId.StorageId && s.ResourceId.OpaqueId == r.ResourceId.OpaqueId + return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) } // r: @@ -88,20 +88,22 @@ func checkPublicShareRef(s *link.PublicShare, ref *link.PublicShareReference) bo return ref.GetToken() == s.Token } -// GetPublicShareScope returns the scope to allow access to a public share and +// AddPublicShareScope adds the scope to allow access to a public share and // the shared resource. -func GetPublicShareScope(share *link.PublicShare, role authpb.Role) (map[string]*authpb.Scope, error) { +func AddPublicShareScope(share *link.PublicShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { val, err := utils.MarshalProtoV1ToJSON(share) if err != nil { return nil, err } - return map[string]*authpb.Scope{ - "publicshare:" + share.Id.OpaqueId: &authpb.Scope{ - Resource: &types.OpaqueEntry{ - Decoder: "json", - Value: val, - }, - Role: role, + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["publicshare:"+share.Id.OpaqueId] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, }, - }, nil + Role: role, + } + return scopes, nil } diff --git a/pkg/auth/scope/resourceinfo.go b/pkg/auth/scope/resourceinfo.go index 3d90770df1..7ace8b3e12 100644 --- a/pkg/auth/scope/resourceinfo.go +++ b/pkg/auth/scope/resourceinfo.go @@ -61,7 +61,7 @@ func resourceinfoScope(scope *authpb.Scope, resource interface{}) (bool, error) return checkResourceInfo(&r, v.GetRef()), nil case string: - return checkPath(v), nil + return checkResourcePath(v), nil } return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) @@ -71,7 +71,7 @@ func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool // ref: > if ref.ResourceId != nil { // path can be empty or a relative path // TODO what about the path? - return inf.Id.StorageId == ref.ResourceId.StorageId && inf.Id.OpaqueId == ref.ResourceId.OpaqueId + return utils.ResourceIDEqual(inf.Id, ref.ResourceId) } // ref: if strings.HasPrefix(ref.GetPath(), inf.Path) { @@ -80,7 +80,7 @@ func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool return false } -func checkPath(path string) bool { +func checkResourcePath(path string) bool { paths := []string{ "/dataprovider", "/data", @@ -93,19 +93,21 @@ func checkPath(path string) bool { return false } -// GetResourceInfoScope returns the scope to allow access to a resource info object. -func GetResourceInfoScope(r *provider.ResourceInfo, role authpb.Role) (map[string]*authpb.Scope, error) { +// AddResourceInfoScope adds the scope to allow access to a resource info object. +func AddResourceInfoScope(r *provider.ResourceInfo, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { val, err := utils.MarshalProtoV1ToJSON(r) if err != nil { return nil, err } - return map[string]*authpb.Scope{ - "resourceinfo:" + r.Id.String(): &authpb.Scope{ - Resource: &types.OpaqueEntry{ - Decoder: "json", - Value: val, - }, - Role: role, + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["resourceinfo:"+r.Id.String()] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, }, - }, nil + Role: role, + } + return scopes, nil } diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 35bdb29cfc..2f4df8dba5 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -31,6 +31,7 @@ var supportedScopes = map[string]Verifier{ "user": userScope, "publicshare": publicshareScope, "resourceinfo": resourceinfoScope, + "share": shareScope, } // VerifyScope is the function to be called when dismantling tokens to check if @@ -39,11 +40,7 @@ func VerifyScope(scopeMap map[string]*authpb.Scope, resource interface{}) (bool, for k, scope := range scopeMap { for s, f := range supportedScopes { if strings.HasPrefix(k, s) { - valid, err := f(scope, resource) - if err != nil { - continue - } - if valid { + if valid, err := f(scope, resource); err == nil && valid { return true, nil } } diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go new file mode 100644 index 0000000000..741a02a050 --- /dev/null +++ b/pkg/auth/scope/share.go @@ -0,0 +1,125 @@ +// 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 scope + +import ( + "fmt" + "strings" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" +) + +func shareScope(scope *authpb.Scope, resource interface{}) (bool, error) { + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share) + if err != nil { + return false, err + } + + switch v := resource.(type) { + // Viewer role + case *registry.GetStorageProvidersRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.StatRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.ListContainerRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.InitiateFileDownloadRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + + // Editor role + // TODO(ishank011): Add role checks, + // need to return appropriate status codes in the ocs/ocdav layers. + case *provider.CreateContainerRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.DeleteRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.MoveRequest: + return checkShareStorageRef(&share, v.GetSource()) && checkShareStorageRef(&share, v.GetDestination()), nil + case *provider.InitiateFileUploadRequest: + return checkShareStorageRef(&share, v.GetRef()), nil + + case *collaboration.ListReceivedSharesRequest: + return true, nil + case *collaboration.GetReceivedShareRequest: + return checkShareRef(&share, v.GetRef()), nil + case string: + return checkSharePath(v) || checkResourcePath(v), nil + } + + return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) +} + +func checkShareStorageRef(s *collaboration.Share, r *provider.Reference) bool { + // ref: > + if r.GetResourceId() != nil && r.Path == "" { // path must be empty + return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) + } + return false +} + +func checkShareRef(s *collaboration.Share, ref *collaboration.ShareReference) bool { + if ref.GetId() != nil { + return ref.GetId().OpaqueId == s.Id.OpaqueId + } + if key := ref.GetKey(); key != nil { + return (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) && + utils.ResourceIDEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee) + } + return false +} + +func checkSharePath(path string) bool { + paths := []string{ + "/ocs/v2.php/apps/files_sharing/api/v1/shares", + "/ocs/v1.php/apps/files_sharing/api/v1/shares", + } + for _, p := range paths { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + +// AddPShareScope adds the scope to allow access to a user/group share and +// the shared resource. +func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + val, err := utils.MarshalProtoV1ToJSON(share) + if err != nil { + return nil, err + } + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["share:"+share.Id.OpaqueId] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: role, + } + return scopes, nil +} diff --git a/pkg/auth/scope/user.go b/pkg/auth/scope/user.go index 82a700dfce..819381e911 100644 --- a/pkg/auth/scope/user.go +++ b/pkg/auth/scope/user.go @@ -31,20 +31,22 @@ func userScope(scope *authpb.Scope, resource interface{}) (bool, error) { return true, nil } -// GetOwnerScope returns the default owner scope with access to all resources. -func GetOwnerScope() (map[string]*authpb.Scope, error) { +// AddOwnerScope adds the default owner scope with access to all resources. +func AddOwnerScope(scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { ref := &provider.Reference{Path: "/"} val, err := utils.MarshalProtoV1ToJSON(ref) if err != nil { return nil, err } - return map[string]*authpb.Scope{ - "user": &authpb.Scope{ - Resource: &types.OpaqueEntry{ - Decoder: "json", - Value: val, - }, - Role: authpb.Role_ROLE_OWNER, + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["user"] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, }, - }, nil + Role: authpb.Role_ROLE_OWNER, + } + return scopes, nil } From 46fa1c9b6e95c0ce40919fed43e1bef583923ff5 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 17:35:20 +0200 Subject: [PATCH 04/23] Expand scopes when minting tokens and add checks for shares and lw scopes --- internal/grpc/interceptors/auth/auth.go | 89 +++++++----- .../grpc/services/gateway/authprovider.go | 75 +++++++++- pkg/auth/scope/lightweight.go | 130 +++++------------- pkg/auth/scope/scope.go | 1 + pkg/auth/scope/share.go | 2 +- 5 files changed, 167 insertions(+), 130 deletions(-) diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 31ed4e45d3..973a9838f9 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -24,6 +24,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" @@ -243,46 +244,72 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. // Currently, we only check for public shares, but this will be extended // for OCM shares, guest accounts, etc. log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) - var share link.PublicShare - var publicShareScope []byte for k := range tokenScope { - if strings.HasPrefix(k, "publicshare") { - publicShareScope = tokenScope[k].Resource.Value - break + switch { + case strings.HasPrefix(k, "publicshare"): + var share link.PublicShare + err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + case strings.HasPrefix(k, "lightweight"): + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + continue + } + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + continue + } + for _, share := range shares.Shares { + if ok, err := checkResourcePath(ctx, ref, share.Share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + } } } - err = utils.UnmarshalJSONToProtoV1(publicShareScope, &share) - if err != nil { - return nil, err - } + } + } - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return nil, err - } + return nil, err +} - // Since the public share is obtained from the scope, the current token - // has access to it. - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: share.ResourceId, - }, - } +func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return false, err + } - statResponse, err := client.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { - return nil, err - } + // Since the public share is obtained from the scope, the current token + // has access to it. + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ResourceId: r}, + } - if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { - // The path corresponds to the resource to which the token has access. - // We allow access to it. - return u, nil - } - } + statResponse, err := client.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + return false, err } - return nil, errtypes.PermissionDenied("access token has invalid scope") + if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { + // The path corresponds to the resource to which the token has access. + // We allow access to it. + return true, nil + } + return false, nil } func extractRef(req interface{}) (*provider.Reference, bool) { diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 847d0761e1..4d7cdd4627 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -21,19 +21,24 @@ package gateway import ( "context" "fmt" + "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth/scope" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" tokenpkg "github.com/cs3org/reva/pkg/token" userpkg "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" "google.golang.org/grpc/metadata" ) @@ -93,7 +98,15 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } - token, err := s.tokenmgr.MintToken(ctx, res.User, res.TokenScope) + scope, err := s.expandScopes(ctx, res.TokenScope) + if err != nil { + err = errors.Wrap(err, "authsvc: error expanding token scope") + return &gateway.AuthenticateResponse{ + Status: status.NewUnauthenticated(ctx, err, "error expanding access token scope"), + }, nil + } + + token, err := s.tokenmgr.MintToken(ctx, res.User, scope) if err != nil { err = errors.Wrap(err, "authsvc: error in MintToken") res := &gateway.AuthenticateResponse{ @@ -191,3 +204,63 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (provider.P return nil, errtypes.InternalError("gateway: error finding an auth provider for type: " + authType) } + +func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + newMap := make(map[string]*authpb.Scope) + for k, v := range scopeMap { + newMap[k] = v + switch { + case strings.HasPrefix(k, "publicshare"): + var share link.PublicShare + err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + + case strings.HasPrefix(k, "lightweight"): + shares, err := s.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + return nil, err + } + for _, share := range shares.Shares { + newMap, err = scope.AddShareScope(share.Share, v.Role, newMap) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.Share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + } + } + } + return newMap, nil +} + +func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.ResourceId, role authpb.Role, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + statReq := &storageprovider.StatRequest{ + Ref: &storageprovider.Reference{ + Spec: &storageprovider.Reference_Id{Id: r}, + }, + } + statResponse, err := s.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + return nil, err + } + return scope.AddResourceInfoScope(statResponse.Info, role, scopeMap) +} diff --git a/pkg/auth/scope/lightweight.go b/pkg/auth/scope/lightweight.go index aaf2942f3f..977e02349a 100644 --- a/pkg/auth/scope/lightweight.go +++ b/pkg/auth/scope/lightweight.go @@ -18,100 +18,36 @@ package scope -// import ( -// "fmt" -// "strings" -// -// authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" -// provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" -// registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" -// types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" -// "github.com/cs3org/reva/pkg/errtypes" -// "github.com/cs3org/reva/pkg/utils" -// ) -// -// func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) { -// var r provider.ResourceInfo -// err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r) -// if err != nil { -// return false, err -// } -// -// switch v := resource.(type) { -// // Viewer role -// case *registry.GetStorageProvidersRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.StatRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.ListContainerRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.InitiateFileDownloadRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// -// // Editor role -// // TODO(ishank011): Add role checks, -// // need to return appropriate status codes in the ocs/ocdav layers. -// case *provider.CreateContainerRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.DeleteRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.MoveRequest: -// return checkResourceInfo(&r, v.GetSource()) && checkResourceInfo(&r, v.GetDestination()), nil -// case *provider.InitiateFileUploadRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// -// case string: -// return checkPath(v), nil -// } -// -// return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) -// } -// -// func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool { -// // ref: > -// if ref.GetId() != nil { -// return inf.Id.StorageId == ref.GetId().StorageId && inf.Id.OpaqueId == ref.GetId().OpaqueId -// } -// // ref: -// if strings.HasPrefix(ref.GetPath(), inf.Path) { -// return true -// } -// return false -// } -// -// func checkPath(path string) bool { -// paths := []string{ -// "/dataprovider", -// "/data", -// } -// for _, p := range paths { -// if strings.HasPrefix(path, p) { -// return true -// } -// } -// return false -// } -// -// // AddLightweightAccountScope adds the scope to allow access to lightweight user. -// func AddLightweightAccountScope(scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { -// ref := &provider.Reference{ -// Spec: &provider.Reference_Path{ -// Path: "/", -// }, -// } -// val, err := utils.MarshalProtoV1ToJSON(ref) -// if err != nil { -// return nil, err -// } -// if scopes == nil { -// scopes = make(map[string]*authpb.Scope) -// } -// scopes["resourceinfo:"+r.Id.String()] = &authpb.Scope{ -// Resource: &types.OpaqueEntry{ -// Decoder: "json", -// Value: val, -// }, -// Role: role, -// } -// return scopes, nil -// } +import ( + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/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/utils" +) + +func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) { + // Lightweight accounts have access to resources shared with them. + // These cannot be resolved from here, but need to be added to the scope from + // where the call to mint tokens is made. + return false, nil +} + +// AddLightweightAccountScope adds the scope to allow access to lightweight user. +func AddLightweightAccountScope(role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + ref := &provider.Reference{Path: "/"} + val, err := utils.MarshalProtoV1ToJSON(ref) + if err != nil { + return nil, err + } + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["lightweight"] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: role, + } + return scopes, nil +} diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 2f4df8dba5..4758e9468e 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -32,6 +32,7 @@ var supportedScopes = map[string]Verifier{ "publicshare": publicshareScope, "resourceinfo": resourceinfoScope, "share": shareScope, + "lightweight": lightweightAccountScope, } // VerifyScope is the function to be called when dismantling tokens to check if diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go index 741a02a050..eace67e437 100644 --- a/pkg/auth/scope/share.go +++ b/pkg/auth/scope/share.go @@ -104,7 +104,7 @@ func checkSharePath(path string) bool { return false } -// AddPShareScope adds the scope to allow access to a user/group share and +// AddShareScope adds the scope to allow access to a user/group share and // the shared resource. func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { val, err := utils.MarshalProtoV1ToJSON(share) From b8af5f53ef47ed6ac7e8b2f25801c7688888db54 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 17:37:26 +0200 Subject: [PATCH 05/23] Update integration tests --- tests/integration/grpc/storageprovider_test.go | 2 +- tests/integration/grpc/userprovider_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 0bacd54eaa..fe14e70103 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -82,7 +82,7 @@ var _ = Describe("storage providers", func() { // Add auth token tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) Expect(err).ToNot(HaveOccurred()) - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) Expect(err).ToNot(HaveOccurred()) t, err := tokenManager.MintToken(ctx, user, scope) Expect(err).ToNot(HaveOccurred()) diff --git a/tests/integration/grpc/userprovider_test.go b/tests/integration/grpc/userprovider_test.go index c345950437..61f1276cbb 100644 --- a/tests/integration/grpc/userprovider_test.go +++ b/tests/integration/grpc/userprovider_test.go @@ -59,7 +59,7 @@ var _ = Describe("user providers", func() { } tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) Expect(err).ToNot(HaveOccurred()) - scope, err := scope.GetOwnerScope() + scope, err := scope.AddOwnerScope(nil) Expect(err).ToNot(HaveOccurred()) t, err := tokenManager.MintToken(ctx, user, scope) Expect(err).ToNot(HaveOccurred()) From b23fe5bd5af7750ef0531d1ae35464712316c970 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 17:46:13 +0200 Subject: [PATCH 06/23] Update changelog --- changelog/unreleased/lw-user-types.md | 9 +++++++++ changelog/unreleased/user-type.md | 4 ---- internal/grpc/interceptors/auth/auth.go | 6 +----- 3 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 changelog/unreleased/lw-user-types.md delete mode 100644 changelog/unreleased/user-type.md diff --git a/changelog/unreleased/lw-user-types.md b/changelog/unreleased/lw-user-types.md new file mode 100644 index 0000000000..9921f95b78 --- /dev/null +++ b/changelog/unreleased/lw-user-types.md @@ -0,0 +1,9 @@ +Enhancement: Add support for lightweight user types + +This PR adds support for assigning and consuming user type when setting/reading +users. These changes are further required to enable setting varying access +scopes for different types of users, such as lightweight accounts which can only +access resources shared with them. + +https://github.com/cs3org/reva/pull/1744 +https://github.com/cs3org/cs3apis/pull/120 diff --git a/changelog/unreleased/user-type.md b/changelog/unreleased/user-type.md deleted file mode 100644 index 24edc9b75f..0000000000 --- a/changelog/unreleased/user-type.md +++ /dev/null @@ -1,4 +0,0 @@ -Enhancement: Assign and consume user type when setting/reading users - -https://github.com/cs3org/reva/pull/1744 -https://github.com/cs3org/cs3apis/pull/120 diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 973a9838f9..fe7a25a055 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -107,8 +107,6 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI return handler(ctx, req) } - log.Info().Msgf("GRPC unary interceptor %s, %+v", info.FullMethod, req) - span.AddAttributes(trace.BoolAttribute("auth_enabled", true)) tkn, ok := token.ContextGetToken(ctx) @@ -241,8 +239,6 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. if ref.GetPath() != "" { // Try to extract the resource ID from the scope resource. - // Currently, we only check for public shares, but this will be extended - // for OCM shares, guest accounts, etc. log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) for k := range tokenScope { switch { @@ -293,7 +289,7 @@ func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider return false, err } - // Since the public share is obtained from the scope, the current token + // Since the resource ID is obtained from the scope, the current token // has access to it. statReq := &provider.StatRequest{ Ref: &provider.Reference{ResourceId: r}, From c94bb653a250cb9f33d518d3f919415839d8a86c Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 18:44:01 +0200 Subject: [PATCH 07/23] Mint temporary token for expanding access --- internal/grpc/interceptors/auth/auth.go | 11 +++- .../grpc/services/gateway/authprovider.go | 55 +++++++++++++++---- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index fe7a25a055..83119bced2 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -31,6 +31,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/auth/scope" "github.com/cs3org/reva/pkg/errtypes" + statuspkg "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/token" @@ -267,7 +268,8 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. continue } shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil { + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") continue } for _, share := range shares.Shares { @@ -280,7 +282,7 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } } - return nil, err + return nil, errtypes.PermissionDenied("access to resource not allowed within the assigned scope") } func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { @@ -296,9 +298,12 @@ func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider } statResponse, err := client.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + if err != nil { return false, err } + if statResponse.Status.Code != rpc.Code_CODE_OK { + return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor") + } if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { // The path corresponds to the resource to which the token has access. diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 4d7cdd4627..434d3cf5e0 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -27,6 +27,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" @@ -98,6 +99,23 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } + // We need to expand the scopes of lightweight accounts, user shares and + // public shares, for which we need to retrieve the receieved shares and stat + // the resources referenced by these. Since the current scope can do that, + // mint a temporary token based on that and expand the scope. Then set the + // token obtained from the updated scope in the context. + token, err := s.tokenmgr.MintToken(ctx, res.User, res.TokenScope) + if err != nil { + err = errors.Wrap(err, "authsvc: error in MintToken") + res := &gateway.AuthenticateResponse{ + Status: status.NewUnauthenticated(ctx, err, "error creating access token"), + } + return res, nil + } + + ctx = tokenpkg.ContextSetToken(ctx, token) + ctx = userpkg.ContextSetUser(ctx, res.User) + ctx = metadata.AppendToOutgoingContext(ctx, tokenpkg.TokenHeader, token) scope, err := s.expandScopes(ctx, res.TokenScope) if err != nil { err = errors.Wrap(err, "authsvc: error expanding token scope") @@ -106,7 +124,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } - token, err := s.tokenmgr.MintToken(ctx, res.User, scope) + token, err = s.tokenmgr.MintToken(ctx, res.User, scope) if err != nil { err = errors.Wrap(err, "authsvc: error in MintToken") res := &gateway.AuthenticateResponse{ @@ -115,7 +133,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest return res, nil } - if scope, ok := res.TokenScope["user"]; s.c.DisableHomeCreationOnLogin || !ok || scope.Role != authpb.Role_ROLE_OWNER { + if scope, ok := res.TokenScope["user"]; s.c.DisableHomeCreationOnLogin || !ok || scope.Role != authpb.Role_ROLE_OWNER || res.User.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { gwRes := &gateway.AuthenticateResponse{ Status: status.NewOK(ctx), User: res.User, @@ -206,7 +224,9 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (provider.P } func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + log := appctx.GetLogger(ctx) newMap := make(map[string]*authpb.Scope) + for k, v := range scopeMap { newMap[k] = v switch { @@ -214,37 +234,44 @@ func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scop var share link.PublicShare err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error unmarshalling public share %+v", v.Resource.Value) + continue } newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error expanding publicshare scope %+v", share.ResourceId) + continue } case strings.HasPrefix(k, "share"): var share collaboration.Share err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error unmarshalling share %+v", v.Resource.Value) + continue } newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error expanding share scope %+v", share.ResourceId) + continue } case strings.HasPrefix(k, "lightweight"): shares, err := s.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil { - return nil, err + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue } for _, share := range shares.Shares { newMap, err = scope.AddShareScope(share.Share, v.Role, newMap) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error expanding received share scope %+v", share.Share.ResourceId) + continue } newMap, err = s.statAndAddResource(ctx, share.Share.ResourceId, v.Role, newMap) if err != nil { - return nil, err + log.Warn().Err(err).Msgf("error expanding received share scope %+v", share.Share.ResourceId) + continue } } } @@ -259,8 +286,12 @@ func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.Resourc }, } statResponse, err := s.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { - return nil, err + if err != nil { + return scopeMap, err } + if statResponse.Status.Code != rpc.Code_CODE_OK { + return scopeMap, status.NewErrorFromCode(statResponse.Status.Code, "authprovider") + } + return scope.AddResourceInfoScope(statResponse.Info, role, scopeMap) } From 336b815ca0d13dae8b1d8d07eeeee477221b5fe2 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 4 Jun 2021 14:53:43 +0200 Subject: [PATCH 08/23] Check share ID in auth interceptor for lw accounts --- internal/grpc/interceptors/auth/auth.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 83119bced2..08e8902933 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -279,6 +279,25 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } } } + } else { // ref has ID present + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return nil, err + } + for k := range tokenScope { + if strings.HasPrefix(k, "lightweight") { + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue + } + for _, share := range shares.Shares { + if utils.ResourceEqual(share.Share.ResourceId, ref.GetId()) { + return u, nil + } + } + } + } } } From f04755fca1e2b5f372d2b1c1bca10b4e9a88b125 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Mon, 7 Jun 2021 15:02:06 +0200 Subject: [PATCH 09/23] Initial changes to eosfs and eosbinary for using EOS tokens --- pkg/eosclient/eosbinary/eosbinary.go | 221 +++++++++++++++------------ pkg/eosclient/eosclient.go | 68 +++++---- pkg/storage/utils/eosfs/eosfs.go | 189 ++++++++++++----------- pkg/storage/utils/eosfs/upload.go | 4 +- 4 files changed, 257 insertions(+), 225 deletions(-) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index 8d5207f896..eab800b42d 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -153,12 +153,14 @@ func New(opt *Options) *Client { return c } -// exec executes the command and returns the stdout, stderr and return code -func (c *Client) execute(ctx context.Context, cmd *exec.Cmd) (string, string, error) { +// executeXRDCopy executes xrdcpy commands and returns the stdout, stderr and return code +func (c *Client) executeXRDCopy(ctx context.Context, cmdArgs []string) (string, string, error) { log := appctx.GetLogger(ctx) outBuf := &bytes.Buffer{} errBuf := &bytes.Buffer{} + + cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, cmdArgs...) cmd.Stdout = outBuf cmd.Stderr = errBuf cmd.Env = []string{ @@ -203,22 +205,32 @@ func (c *Client) execute(ctx context.Context, cmd *exec.Cmd) (string, string, er } // exec executes only EOS commands the command and returns the stdout, stderr and return code. -// execute() executes arbitrary commands. -func (c *Client) executeEOS(ctx context.Context, cmd *exec.Cmd) (string, string, error) { +func (c *Client) executeEOS(ctx context.Context, cmdArgs []string, auth eosclient.Authorization) (string, string, error) { log := appctx.GetLogger(ctx) outBuf := &bytes.Buffer{} errBuf := &bytes.Buffer{} + + cmd := exec.CommandContext(ctx, c.opt.EosBinary) cmd.Stdout = outBuf cmd.Stderr = errBuf cmd.Env = []string{ "EOS_MGM_URL=" + c.opt.URL, } + + if auth.Token != "" { + cmd.Env = append(cmd.Env, "EOSAUTHZ="+auth.Token) + } else if auth.Role.UID != "" && auth.Role.GID != "" { + cmd.Args = append(cmd.Args, []string{"-r", auth.Role.UID, auth.Role.GID}...) + } + if c.opt.UseKeytab { cmd.Env = append(cmd.Env, "XrdSecPROTOCOL="+c.opt.SecProtocol) cmd.Env = append(cmd.Env, "XrdSecSSSKT="+c.opt.Keytab) } + cmd.Args = append(cmd.Args, cmdArgs...) + trace := trace.FromContext(ctx).SpanContext().TraceID.String() cmd.Args = append(cmd.Args, "--comment", trace) @@ -259,13 +271,13 @@ func (c *Client) executeEOS(ctx context.Context, cmd *exec.Cmd) (string, string, } // AddACL adds an new acl to EOS with the given aclType. -func (c *Client) AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, uid, gid, path) +func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { + finfo, err := c.GetFileInfoByPath(ctx, auth, path) if err != nil { return err } sysACL := a.CitrineSerialize() - args := []string{"-r", rootUID, rootGID, "acl"} + args := []string{"acl"} if finfo.IsDir { args = append(args, "--sys", "--recursive") @@ -276,27 +288,26 @@ func (c *Client) AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path st Key: "eval.useracl", Val: "1", } - if err = c.SetAttr(ctx, uid, gid, userACLAttr, false, path); err != nil { + if err = c.SetAttr(ctx, auth, userACLAttr, false, path); err != nil { return err } } args = append(args, sysACL, path) - cmd := exec.CommandContext(ctx, c.opt.EosBinary, args...) - _, _, err = c.executeEOS(ctx, cmd) + _, _, err = c.executeEOS(ctx, args, rootAuth) return err } // RemoveACL removes the acl from EOS. -func (c *Client) RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, uid, gid, path) +func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { + finfo, err := c.GetFileInfoByPath(ctx, auth, path) if err != nil { return err } sysACL := a.CitrineSerialize() - args := []string{"-r", rootUID, rootGID, "acl"} + args := []string{"acl"} if finfo.IsDir { args = append(args, "--sys", "--recursive") } else { @@ -305,25 +316,24 @@ func (c *Client) RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path Type: SystemAttr, Key: "eval.useracl", } - if err = c.UnsetAttr(ctx, uid, gid, userACLAttr, path); err != nil { + if err = c.UnsetAttr(ctx, auth, userACLAttr, path); err != nil { return err } } args = append(args, sysACL, path) - cmd := exec.CommandContext(ctx, c.opt.EosBinary, args...) - _, _, err = c.executeEOS(ctx, cmd) + _, _, err = c.executeEOS(ctx, args, rootAuth) return err } // UpdateACL updates the EOS acl. -func (c *Client) UpdateACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { - return c.AddACL(ctx, uid, gid, rootUID, rootGID, path, a) +func (c *Client) UpdateACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { + return c.AddACL(ctx, auth, rootAuth, path, a) } // GetACL for a file -func (c *Client) GetACL(ctx context.Context, uid, gid, path, aclType, target string) (*acl.Entry, error) { - acls, err := c.ListACLs(ctx, uid, gid, path) +func (c *Client) GetACL(ctx context.Context, auth eosclient.Authorization, path, aclType, target string) (*acl.Entry, error) { + acls, err := c.ListACLs(ctx, auth, path) if err != nil { return nil, err } @@ -339,9 +349,9 @@ func (c *Client) GetACL(ctx context.Context, uid, gid, path, aclType, target str // ListACLs returns the list of ACLs present under the given path. // EOS returns uids/gid for Citrine version and usernames for older versions. // For Citire we need to convert back the uid back to username. -func (c *Client) ListACLs(ctx context.Context, uid, gid, path string) ([]*acl.Entry, error) { +func (c *Client) ListACLs(ctx context.Context, auth eosclient.Authorization, path string) ([]*acl.Entry, error) { - parsedACLs, err := c.getACLForPath(ctx, uid, gid, path) + parsedACLs, err := c.getACLForPath(ctx, auth, path) if err != nil { return nil, err } @@ -351,8 +361,8 @@ func (c *Client) ListACLs(ctx context.Context, uid, gid, path string) ([]*acl.En return parsedACLs.Entries, nil } -func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl.ACLs, error) { - finfo, err := c.GetFileInfoByPath(ctx, uid, gid, path) +func (c *Client) getACLForPath(ctx context.Context, auth eosclient.Authorization, path string) (*acl.ACLs, error) { + finfo, err := c.GetFileInfoByPath(ctx, auth, path) if err != nil { return nil, err } @@ -361,9 +371,9 @@ func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl } // GetFileInfoByInode returns the FileInfo by the given inode -func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode uint64) (*eosclient.FileInfo, error) { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "info", fmt.Sprintf("inode:%d", inode), "-m") - stdout, _, err := c.executeEOS(ctx, cmd) +func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authorization, inode uint64) (*eosclient.FileInfo, error) { + args := []string{"file", "info", fmt.Sprintf("inode:%d", inode), "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) if err != nil { return nil, err } @@ -373,7 +383,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode } if c.opt.VersionInvariant && isVersionFolder(info.File) { - info, err = c.getFileInfoFromVersion(ctx, uid, gid, info.File) + info, err = c.getFileInfoFromVersion(ctx, auth, info.File) if err != nil { return nil, err } @@ -384,9 +394,9 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode } // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal -func (c *Client) GetFileInfoByFXID(ctx context.Context, uid, gid string, fxid string) (*eosclient.FileInfo, error) { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "info", fmt.Sprintf("fxid:%s", fxid), "-m") - stdout, _, err := c.executeEOS(ctx, cmd) +func (c *Client) GetFileInfoByFXID(ctx context.Context, auth eosclient.Authorization, fxid string) (*eosclient.FileInfo, error) { + args := []string{"file", "info", fmt.Sprintf("fxid:%s", fxid), "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) if err != nil { return nil, err } @@ -394,9 +404,9 @@ func (c *Client) GetFileInfoByFXID(ctx context.Context, uid, gid string, fxid st } // GetFileInfoByPath returns the FilInfo at the given path -func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) (*eosclient.FileInfo, error) { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "info", path, "-m") - stdout, _, err := c.executeEOS(ctx, cmd) +func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { + args := []string{"file", "info", path, "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) if err != nil { return nil, err } @@ -406,7 +416,7 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) ( } if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir { - if inode, err := c.getVersionFolderInode(ctx, uid, gid, path); err == nil { + if inode, err := c.getVersionFolderInode(ctx, auth, path); err == nil { info.Inode = inode } } @@ -415,18 +425,18 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) ( } // SetAttr sets an extended attributes on a path. -func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.Attribute, recursive bool, path string) error { +func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } - var cmd *exec.Cmd + var args []string if recursive { - cmd = exec.CommandContext(ctx, "/usr/bin/eos", "-r", uid, gid, "attr", "-r", "set", serializeAttribute(attr), path) + args = []string{"attr", "-r", "set", serializeAttribute(attr), path} } else { - cmd = exec.CommandContext(ctx, "/usr/bin/eos", "-r", uid, gid, "attr", "set", serializeAttribute(attr), path) + args = []string{"attr", "set", serializeAttribute(attr), path} } - _, _, err := c.executeEOS(ctx, cmd) + _, _, err := c.executeEOS(ctx, args, auth) if err != nil { return err } @@ -434,12 +444,12 @@ func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.A } // UnsetAttr unsets an extended attribute on a path. -func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient.Attribute, path string) error { +func (c *Client) UnsetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, path string) error { if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } - cmd := exec.CommandContext(ctx, "/usr/bin/eos", "-r", uid, gid, "attr", "-r", "rm", fmt.Sprintf("%d.%s", attr.Type, attr.Key), path) - _, _, err := c.executeEOS(ctx, cmd) + args := []string{"attr", "-r", "rm", fmt.Sprintf("%d.%s", attr.Type, attr.Key), path} + _, _, err := c.executeEOS(ctx, args, auth) if err != nil { return err } @@ -447,9 +457,9 @@ func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient } // GetQuota gets the quota of a user on the quota node defined by path -func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path string) (*eosclient.QuotaInfo, error) { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", rootUID, rootGID, "quota", "ls", "-u", username, "-m") - stdout, _, err := c.executeEOS(ctx, cmd) +func (c *Client) GetQuota(ctx context.Context, username string, rootAuth eosclient.Authorization, path string) (*eosclient.QuotaInfo, error) { + args := []string{"quota", "ls", "-u", username, "-m"} + stdout, _, err := c.executeEOS(ctx, args, rootAuth) if err != nil { return nil, err } @@ -457,11 +467,11 @@ func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path } // SetQuota sets the quota of a user on the quota node defined by path -func (c *Client) SetQuota(ctx context.Context, rootUID, rootGID string, info *eosclient.SetQuotaInfo) error { +func (c *Client) SetQuota(ctx context.Context, rootAuth eosclient.Authorization, info *eosclient.SetQuotaInfo) error { maxBytes := fmt.Sprintf("%d", info.MaxBytes) maxFiles := fmt.Sprintf("%d", info.MaxFiles) - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", rootUID, rootGID, "quota", "set", "-u", info.Username, "-p", info.QuotaNode, "-v", maxBytes, "-i", maxFiles) - _, _, err := c.executeEOS(ctx, cmd) + args := []string{"quota", "set", "-u", info.Username, "-p", info.QuotaNode, "-v", maxBytes, "-i", maxFiles} + _, _, err := c.executeEOS(ctx, args, rootAuth) if err != nil { return err } @@ -469,51 +479,51 @@ func (c *Client) SetQuota(ctx context.Context, rootUID, rootGID string, info *eo } // Touch creates a 0-size,0-replica file in the EOS namespace. -func (c *Client) Touch(ctx context.Context, uid, gid, path string) error { - cmd := exec.CommandContext(ctx, "/usr/bin/eos", "-r", uid, gid, "file", "touch", path) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) Touch(ctx context.Context, auth eosclient.Authorization, path string) error { + args := []string{"file", "touch", path} + _, _, err := c.executeEOS(ctx, args, auth) return err } // Chown given path -func (c *Client) Chown(ctx context.Context, uid, gid, chownUID, chownGID, path string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "chown", chownUID+":"+chownGID, path) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) Chown(ctx context.Context, auth, chownauth eosclient.Authorization, path string) error { + args := []string{"chown", chownauth.Role.UID + ":" + chownauth.Role.GID, path} + _, _, err := c.executeEOS(ctx, args, auth) return err } // Chmod given path -func (c *Client) Chmod(ctx context.Context, uid, gid, mode, path string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "chmod", mode, path) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) Chmod(ctx context.Context, auth eosclient.Authorization, mode, path string) error { + args := []string{"chmod", mode, path} + _, _, err := c.executeEOS(ctx, args, auth) return err } // CreateDir creates a directory at the given path -func (c *Client) CreateDir(ctx context.Context, uid, gid, path string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "mkdir", "-p", path) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) CreateDir(ctx context.Context, auth eosclient.Authorization, path string) error { + args := []string{"mkdir", "-p", path} + _, _, err := c.executeEOS(ctx, args, auth) return err } // Remove removes the resource at the given path -func (c *Client) Remove(ctx context.Context, uid, gid, path string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "rm", "-r", path) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) Remove(ctx context.Context, auth eosclient.Authorization, path string) error { + args := []string{"rm", "-r", path} + _, _, err := c.executeEOS(ctx, args, auth) return err } // Rename renames the resource referenced by oldPath to newPath -func (c *Client) Rename(ctx context.Context, uid, gid, oldPath, newPath string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "rename", oldPath, newPath) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) Rename(ctx context.Context, auth eosclient.Authorization, oldPath, newPath string) error { + args := []string{"file", "rename", oldPath, newPath} + _, _, err := c.executeEOS(ctx, args, auth) return err } // List the contents of the directory given by path -func (c *Client) List(ctx context.Context, uid, gid, path string) ([]*eosclient.FileInfo, error) { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "find", "--fileinfo", "--maxdepth", "1", path) - stdout, _, err := c.executeEOS(ctx, cmd) +func (c *Client) List(ctx context.Context, auth eosclient.Authorization, path string) ([]*eosclient.FileInfo, error) { + args := []string{"find", "--fileinfo", "--maxdepth", "1", path} + stdout, _, err := c.executeEOS(ctx, args, auth) if err != nil { return nil, errors.Wrapf(err, "eosclient: error listing fn=%s", path) } @@ -521,14 +531,20 @@ func (c *Client) List(ctx context.Context, uid, gid, path string) ([]*eosclient. } // Read reads a file from the mgm -func (c *Client) Read(ctx context.Context, uid, gid, path string) (io.ReadCloser, error) { +func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path string) (io.ReadCloser, error) { rand := "eosread-" + uuid.New().String() localTarget := fmt.Sprintf("%s/%s", c.opt.CacheDirectory, rand) defer os.RemoveAll(localTarget) xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path) - cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, "--nopbar", "--silent", "-f", xrdPath, localTarget, fmt.Sprintf("-OSeos.ruid=%s&eos.rgid=%s", uid, gid)) - _, _, err := c.execute(ctx, cmd) + args := []string{"--nopbar", "--silent", "-f", xrdPath, localTarget} + if auth.Token != "" { + args[3] += "authz=" + auth.Token + } else if auth.Role.UID != "" && auth.Role.GID != "" { + args = append(args, fmt.Sprintf("-OSeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) + } + + _, _, err := c.executeXRDCopy(ctx, args) if err != nil { return nil, err } @@ -536,7 +552,7 @@ func (c *Client) Read(ctx context.Context, uid, gid, path string) (io.ReadCloser } // Write writes a stream to the mgm -func (c *Client) Write(ctx context.Context, uid, gid, path string, stream io.ReadCloser) error { +func (c *Client) Write(ctx context.Context, auth eosclient.Authorization, path string, stream io.ReadCloser) error { fd, err := ioutil.TempFile(c.opt.CacheDirectory, "eoswrite-") if err != nil { return err @@ -550,23 +566,28 @@ func (c *Client) Write(ctx context.Context, uid, gid, path string, stream io.Rea return err } - return c.WriteFile(ctx, uid, gid, path, fd.Name()) + return c.WriteFile(ctx, auth, path, fd.Name()) } // WriteFile writes an existing file to the mgm -func (c *Client) WriteFile(ctx context.Context, uid, gid, path, source string) error { +func (c *Client) WriteFile(ctx context.Context, auth eosclient.Authorization, path, source string) error { xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path) - cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, "--nopbar", "--silent", "-f", source, xrdPath, fmt.Sprintf("-ODeos.ruid=%s&eos.rgid=%s", uid, gid)) - _, _, err := c.execute(ctx, cmd) + args := []string{"--nopbar", "--silent", "-f", source, xrdPath} + if auth.Token != "" { + args[4] += "authz=" + auth.Token + } else if auth.Role.UID != "" && auth.Role.GID != "" { + args = append(args, fmt.Sprintf("-OSeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) + } + _, _, err := c.executeXRDCopy(ctx, args) return err } // ListDeletedEntries returns a list of the deleted entries. -func (c *Client) ListDeletedEntries(ctx context.Context, uid, gid string) ([]*eosclient.DeletedEntry, error) { +func (c *Client) ListDeletedEntries(ctx context.Context, auth eosclient.Authorization) ([]*eosclient.DeletedEntry, error) { // TODO(labkode): add protection if slave is configured and alive to count how many files are in the trashbin before // triggering the recycle ls call that could break the instance because of unavailable memory. - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "recycle", "ls", "-m") - stdout, _, err := c.executeEOS(ctx, cmd) + args := []string{"recycle", "ls", "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) if err != nil { return nil, err } @@ -574,23 +595,23 @@ func (c *Client) ListDeletedEntries(ctx context.Context, uid, gid string) ([]*eo } // RestoreDeletedEntry restores a deleted entry. -func (c *Client) RestoreDeletedEntry(ctx context.Context, uid, gid, key string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "recycle", "restore", key) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) RestoreDeletedEntry(ctx context.Context, auth eosclient.Authorization, key string) error { + args := []string{"recycle", "restore", key} + _, _, err := c.executeEOS(ctx, args, auth) return err } // PurgeDeletedEntries purges all entries from the recycle bin. -func (c *Client) PurgeDeletedEntries(ctx context.Context, uid, gid string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "recycle", "purge") - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) PurgeDeletedEntries(ctx context.Context, auth eosclient.Authorization) error { + args := []string{"recycle", "purge"} + _, _, err := c.executeEOS(ctx, args, auth) return err } // ListVersions list all the versions for a given file. -func (c *Client) ListVersions(ctx context.Context, uid, gid, p string) ([]*eosclient.FileInfo, error) { +func (c *Client) ListVersions(ctx context.Context, auth eosclient.Authorization, p string) ([]*eosclient.FileInfo, error) { versionFolder := getVersionFolder(p) - finfos, err := c.List(ctx, uid, gid, versionFolder) + finfos, err := c.List(ctx, auth, versionFolder) if err != nil { // we send back an empty list return []*eosclient.FileInfo{}, nil @@ -599,26 +620,26 @@ func (c *Client) ListVersions(ctx context.Context, uid, gid, p string) ([]*eoscl } // RollbackToVersion rollbacks a file to a previous version. -func (c *Client) RollbackToVersion(ctx context.Context, uid, gid, path, version string) error { - cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "versions", path, version) - _, _, err := c.executeEOS(ctx, cmd) +func (c *Client) RollbackToVersion(ctx context.Context, auth eosclient.Authorization, path, version string) error { + args := []string{"file", "versions", path, version} + _, _, err := c.executeEOS(ctx, args, auth) return err } // ReadVersion reads the version for the given file. -func (c *Client) ReadVersion(ctx context.Context, uid, gid, p, version string) (io.ReadCloser, error) { +func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, p, version string) (io.ReadCloser, error) { versionFile := path.Join(getVersionFolder(p), version) - return c.Read(ctx, uid, gid, versionFile) + return c.Read(ctx, auth, versionFile) } -func (c *Client) getVersionFolderInode(ctx context.Context, uid, gid, p string) (uint64, error) { +func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { versionFolder := getVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, uid, gid, versionFolder) + md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) if err != nil { - if err = c.CreateDir(ctx, uid, gid, versionFolder); err != nil { + if err = c.CreateDir(ctx, auth, versionFolder); err != nil { return 0, err } - md, err = c.GetFileInfoByPath(ctx, uid, gid, versionFolder) + md, err = c.GetFileInfoByPath(ctx, auth, versionFolder) if err != nil { return 0, err } @@ -626,9 +647,9 @@ func (c *Client) getVersionFolderInode(ctx context.Context, uid, gid, p string) return md.Inode, nil } -func (c *Client) getFileInfoFromVersion(ctx context.Context, uid, gid, p string) (*eosclient.FileInfo, error) { +func (c *Client) getFileInfoFromVersion(ctx context.Context, auth eosclient.Authorization, p string) (*eosclient.FileInfo, error) { file := getFileFromVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, uid, gid, file) + md, err := c.GetFileInfoByPath(ctx, auth, file) if err != nil { return nil, err } diff --git a/pkg/eosclient/eosclient.go b/pkg/eosclient/eosclient.go index 1f4b03313c..cffe7d4911 100644 --- a/pkg/eosclient/eosclient.go +++ b/pkg/eosclient/eosclient.go @@ -27,34 +27,34 @@ import ( // EOSClient is the interface which enables access to EOS instances through various interfaces. type EOSClient interface { - AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error - RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error - UpdateACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error - GetACL(ctx context.Context, uid, gid, path, aclType, target string) (*acl.Entry, error) - ListACLs(ctx context.Context, uid, gid, path string) ([]*acl.Entry, error) - GetFileInfoByInode(ctx context.Context, uid, gid string, inode uint64) (*FileInfo, error) - GetFileInfoByFXID(ctx context.Context, uid, gid string, fxid string) (*FileInfo, error) - GetFileInfoByPath(ctx context.Context, uid, gid, path string) (*FileInfo, error) - SetAttr(ctx context.Context, uid, gid string, attr *Attribute, recursive bool, path string) error - UnsetAttr(ctx context.Context, uid, gid string, attr *Attribute, path string) error - GetQuota(ctx context.Context, username, rootUID, rootGID, path string) (*QuotaInfo, error) - SetQuota(ctx context.Context, rootUID, rootGID string, info *SetQuotaInfo) error - Touch(ctx context.Context, uid, gid, path string) error - Chown(ctx context.Context, uid, gid, chownUID, chownGID, path string) error - Chmod(ctx context.Context, uid, gid, mode, path string) error - CreateDir(ctx context.Context, uid, gid, path string) error - Remove(ctx context.Context, uid, gid, path string) error - Rename(ctx context.Context, uid, gid, oldPath, newPath string) error - List(ctx context.Context, uid, gid, path string) ([]*FileInfo, error) - Read(ctx context.Context, uid, gid, path string) (io.ReadCloser, error) - Write(ctx context.Context, uid, gid, path string, stream io.ReadCloser) error - WriteFile(ctx context.Context, uid, gid, path, source string) error - ListDeletedEntries(ctx context.Context, uid, gid string) ([]*DeletedEntry, error) - RestoreDeletedEntry(ctx context.Context, uid, gid, key string) error - PurgeDeletedEntries(ctx context.Context, uid, gid string) error - ListVersions(ctx context.Context, uid, gid, p string) ([]*FileInfo, error) - RollbackToVersion(ctx context.Context, uid, gid, path, version string) error - ReadVersion(ctx context.Context, uid, gid, p, version string) (io.ReadCloser, error) + AddACL(ctx context.Context, auth, rootAuth Authorization, path string, a *acl.Entry) error + RemoveACL(ctx context.Context, auth, rootAuth Authorization, path string, a *acl.Entry) error + UpdateACL(ctx context.Context, auth, rootAuth Authorization, path string, a *acl.Entry) error + GetACL(ctx context.Context, auth Authorization, path, aclType, target string) (*acl.Entry, error) + ListACLs(ctx context.Context, auth Authorization, path string) ([]*acl.Entry, error) + GetFileInfoByInode(ctx context.Context, auth Authorization, inode uint64) (*FileInfo, error) + GetFileInfoByFXID(ctx context.Context, auth Authorization, fxid string) (*FileInfo, error) + GetFileInfoByPath(ctx context.Context, auth Authorization, path string) (*FileInfo, error) + SetAttr(ctx context.Context, auth Authorization, attr *Attribute, recursive bool, path string) error + UnsetAttr(ctx context.Context, auth Authorization, attr *Attribute, path string) error + GetQuota(ctx context.Context, username string, rootAuth Authorization, path string) (*QuotaInfo, error) + SetQuota(ctx context.Context, rooAuth Authorization, info *SetQuotaInfo) error + Touch(ctx context.Context, auth Authorization, path string) error + Chown(ctx context.Context, auth, chownauth Authorization, path string) error + Chmod(ctx context.Context, auth Authorization, mode, path string) error + CreateDir(ctx context.Context, auth Authorization, path string) error + Remove(ctx context.Context, auth Authorization, path string) error + Rename(ctx context.Context, auth Authorization, oldPath, newPath string) error + List(ctx context.Context, auth Authorization, path string) ([]*FileInfo, error) + Read(ctx context.Context, auth Authorization, path string) (io.ReadCloser, error) + Write(ctx context.Context, auth Authorization, path string, stream io.ReadCloser) error + WriteFile(ctx context.Context, auth Authorization, path, source string) error + ListDeletedEntries(ctx context.Context, auth Authorization) ([]*DeletedEntry, error) + RestoreDeletedEntry(ctx context.Context, auth Authorization, key string) error + PurgeDeletedEntries(ctx context.Context, auth Authorization) error + ListVersions(ctx context.Context, auth Authorization, p string) ([]*FileInfo, error) + RollbackToVersion(ctx context.Context, auth Authorization, path, version string) error + ReadVersion(ctx context.Context, auth Authorization, p, version string) (io.ReadCloser, error) } // AttrType is the type of extended attribute, @@ -119,3 +119,15 @@ type SetQuotaInfo struct { MaxBytes uint64 MaxFiles uint64 } + +// Role holds the attributes required to authenticate to EOS via role-based access. +type Role struct { + UID, GID string +} + +// Authorization specifies the mechanisms through which EOS can be accessed. +// One of the data members must be set. +type Authorization struct { + Role Role + Token string +} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 81b927b8c6..6195571111 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -29,6 +29,7 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/bluele/gcache" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" @@ -130,12 +131,11 @@ func (c *Config) init() { } type eosfs struct { - c eosclient.EOSClient - conf *Config - chunkHandler *chunking.ChunkHandler - singleUserUID string - singleUserGID string - userIDCache gcache.Cache + c eosclient.EOSClient + conf *Config + chunkHandler *chunking.ChunkHandler + singleUserAuth eosclient.Authorization + userIDCache sync.Map } // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance @@ -352,12 +352,12 @@ func (fs *eosfs) getPath(ctx context.Context, u *userpb.User, id *provider.Resou return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId) } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return "", err } - eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, uid, gid, fid) + eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) if err != nil { return "", errors.Wrap(err, "eosfs: error getting file info by inode") } @@ -393,12 +393,12 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri return "", errors.Wrap(err, "eosfs: error parsing fileid string") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return "", err } - eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, uid, gid, fileID) + eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fileID) if err != nil { return "", errors.Wrap(err, "eosfs: error getting file info by inode") } @@ -505,17 +505,17 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi return err } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return err } - err = fs.c.AddACL(ctx, uid, gid, rootUID, rootGID, fn, eosACL) + err = fs.c.AddACL(ctx, auth, rootAuth, fn, eosACL) if err != nil { return errors.Wrap(err, "eosfs: error adding acl") } @@ -537,10 +537,11 @@ func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, if t == acl.TypeUser { // since EOS Citrine ACLs are stored with uid, we need to convert username to // uid only for users. - qualifier, _, err = fs.getUIDGateway(ctx, g.Grantee.GetUserId()) + auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) if err != nil { return nil, err } + qualifier = auth.Role.UID } else { qualifier = g.Grantee.GetGroupId().OpaqueId } @@ -567,10 +568,11 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr var recipient string if eosACLType == acl.TypeUser { // since EOS Citrine ACLs are stored with uid, we need to convert username to uid - recipient, _, err = fs.getUIDGateway(ctx, g.Grantee.GetUserId()) + auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) if err != nil { return err } + recipient = auth.Role.UID } else { recipient = g.Grantee.GetGroupId().OpaqueId } @@ -587,17 +589,17 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr fn := fs.wrap(ctx, p) - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return err } - err = fs.c.RemoveACL(ctx, uid, gid, rootUID, rootGID, fn, eosACL) + err = fs.c.RemoveACL(ctx, auth, rootAuth, fn, eosACL) if err != nil { return errors.Wrap(err, "eosfs: error removing acl") } @@ -620,12 +622,12 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr } fn := fs.wrap(ctx, p) - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } - acls, err := fs.c.ListACLs(ctx, uid, gid, fn) + acls, err := fs.c.ListACLs(ctx, auth, fn) if err != nil { return nil, err } @@ -682,12 +684,12 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st fn := fs.wrap(ctx, p) - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } - eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, uid, gid, fn) + eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err } @@ -703,12 +705,12 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string fn := fs.wrapShadow(ctx, p) - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } - eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, uid, gid, fn) + eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err } @@ -751,14 +753,14 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } fn := fs.wrap(ctx, p) - eosFileInfos, err := fs.c.List(ctx, uid, gid, fn) + eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") } @@ -805,7 +807,7 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } @@ -814,7 +816,7 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc finfos := []*provider.ResourceInfo{} for _, fn := range fns { - eosFileInfos, err := fs.c.List(ctx, uid, gid, fn) + eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") } @@ -842,14 +844,14 @@ func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*p if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } fn := fs.wrapShadow(ctx, p) - eosFileInfos, err := fs.c.List(ctx, uid, gid, fn) + eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") } @@ -882,12 +884,12 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { return 0, 0, errors.Wrap(err, "eosfs: no uid in ctx") } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return 0, 0, err } - qi, err := fs.c.GetQuota(ctx, uid, rootUID, rootGID, fs.conf.QuotaNode) + qi, err := fs.c.GetQuota(ctx, uid, rootAuth, fs.conf.QuotaNode) if err != nil { err := errors.Wrap(err, "eosfs: error getting quota") return 0, 0, err @@ -925,7 +927,7 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil } @@ -934,7 +936,7 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error { for _, sf := range shadowFolders { fn := path.Join(home, sf) - _, err = fs.c.GetFileInfoByPath(ctx, uid, gid, fn) + _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, fn) if err != nil { if _, ok := err.(errtypes.IsNotFound); !ok { return errors.Wrap(err, "eosfs: error verifying if shadow directory exists") @@ -956,17 +958,16 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { } home := fs.wrap(ctx, "/") - rootuid, rootgid, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - _, err = fs.c.GetFileInfoByPath(ctx, rootuid, rootgid, home) + _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home) if err == nil { // home already exists return nil } @@ -991,7 +992,7 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { QuotaNode: fs.conf.QuotaNode, } - err = fs.c.SetQuota(ctx, rootuid, rootgid, quotaInfo) + err = fs.c.SetQuota(ctx, rootAuth, quotaInfo) if err != nil { err := errors.Wrap(err, "eosfs: error setting quota") return err @@ -1017,28 +1018,28 @@ func (fs *eosfs) CreateHome(ctx context.Context) error { } func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error { - uid, gid, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil } - chownUID, chownGID, err := fs.getUserUIDAndGID(ctx, u) + chownAuth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - err = fs.c.CreateDir(ctx, uid, gid, path) + err = fs.c.CreateDir(ctx, rootAuth, path) if err != nil { // EOS will return success on mkdir over an existing directory. return errors.Wrap(err, "eosfs: error creating dir") } - err = fs.c.Chown(ctx, uid, gid, chownUID, chownGID, path) + err = fs.c.Chown(ctx, rootAuth, chownAuth, path) if err != nil { return errors.Wrap(err, "eosfs: error chowning directory") } - err = fs.c.Chmod(ctx, uid, gid, "2770", path) + err = fs.c.Chmod(ctx, rootAuth, "2770", path) if err != nil { return errors.Wrap(err, "eosfs: error chmoding directory") } @@ -1067,7 +1068,7 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, } for _, attr := range attrs { - err = fs.c.SetAttr(ctx, uid, gid, attr, recursiveAttr, path) + err = fs.c.SetAttr(ctx, rootAuth, attr, recursiveAttr, path) if err != nil { return errors.Wrap(err, "eosfs: error setting attribute") } @@ -1083,7 +1084,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error { return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -1095,7 +1096,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error { } fn := fs.wrap(ctx, p) - return fs.c.CreateDir(ctx, uid, gid, fn) + return fs.c.CreateDir(ctx, auth, fn) } func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { @@ -1115,7 +1116,7 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U // Current mechanism is: touch to hidden dir, set xattr, rename. dir, base := path.Split(fn) tmp := path.Join(dir, fmt.Sprintf(".sys.reva#.%s", base)) - uid, gid, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil } @@ -1132,13 +1133,13 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U Val: targetURI.String(), } - if err := fs.c.SetAttr(ctx, uid, gid, attr, false, tmp); err != nil { + if err := fs.c.SetAttr(ctx, rootAuth, attr, false, tmp); err != nil { err = errors.Wrapf(err, "eosfs: error setting reva.ref attr on file: %q", tmp) return err } // rename to have the file visible in user space. - if err := fs.c.Rename(ctx, uid, gid, tmp, fn); err != nil { + if err := fs.c.Rename(ctx, rootAuth, tmp, fn); err != nil { err = errors.Wrapf(err, "eosfs: error renaming from: %q to %q", tmp, fn) return err } @@ -1152,7 +1153,7 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -1168,7 +1169,7 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { fn := fs.wrap(ctx, p) - return fs.c.Remove(ctx, uid, gid, fn) + return fs.c.Remove(ctx, auth, fn) } func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { @@ -1182,13 +1183,13 @@ func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } fn := fs.wrapShadow(ctx, p) - return fs.c.Remove(ctx, uid, gid, fn) + return fs.c.Remove(ctx, auth, fn) } return errors.New("eosfs: shadow delete of share folder that is neither root nor child. path=" + p) @@ -1200,7 +1201,7 @@ func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) e return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -1221,7 +1222,7 @@ func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) e oldFn := fs.wrap(ctx, oldPath) newFn := fs.wrap(ctx, newPath) - return fs.c.Rename(ctx, uid, gid, oldFn, newFn) + return fs.c.Rename(ctx, auth, oldFn, newFn) } func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error { @@ -1230,7 +1231,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -1249,7 +1250,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error oldfn := fs.wrapShadow(ctx, oldPath) newfn := fs.wrapShadow(ctx, newPath) - return fs.c.Rename(ctx, uid, gid, oldfn, newfn) + return fs.c.Rename(ctx, auth, oldfn, newfn) } func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { @@ -1258,7 +1259,7 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } @@ -1273,7 +1274,7 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read } fn := fs.wrap(ctx, p) - return fs.c.Read(ctx, uid, gid, fn) + return fs.c.Read(ctx, auth, fn) } func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { @@ -1282,7 +1283,7 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } @@ -1298,7 +1299,7 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] fn := fs.wrap(ctx, p) - eosRevisions, err := fs.c.ListVersions(ctx, uid, gid, fn) + eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing versions") } @@ -1317,7 +1318,7 @@ func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } @@ -1334,7 +1335,7 @@ func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, fn := fs.wrap(ctx, p) fn = fs.wrap(ctx, fn) - return fs.c.ReadVersion(ctx, uid, gid, fn, revisionKey) + return fs.c.ReadVersion(ctx, auth, fn, revisionKey) } func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { @@ -1343,7 +1344,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -1359,7 +1360,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r fn := fs.wrap(ctx, p) - return fs.c.RollbackToVersion(ctx, uid, gid, fn, revisionKey) + return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey) } func (fs *eosfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { @@ -1372,12 +1373,12 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - return fs.c.PurgeDeletedEntries(ctx, uid, gid) + return fs.c.PurgeDeletedEntries(ctx, auth) } func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*provider.RecycleItem, error) { @@ -1386,12 +1387,12 @@ func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*prov return nil, errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return nil, err } - eosDeletedEntries, err := fs.c.ListDeletedEntries(ctx, uid, gid) + eosDeletedEntries, err := fs.c.ListDeletedEntries(ctx, auth) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing deleted entries") } @@ -1417,12 +1418,12 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, r return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } - return fs.c.RestoreDeletedEntry(ctx, uid, gid, key) + return fs.c.RestoreDeletedEntry(ctx, auth, key) } func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { @@ -1514,7 +1515,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - uid, _, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return &provider.ResourcePermissions{ // no permissions @@ -1533,7 +1534,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - if (e.Type == acl.TypeUser && e.Qualifier == uid) || userInGroup { + if (e.Type == acl.TypeUser && e.Qualifier == auth.Role.UID) || userInGroup { mergePermissions(&perm, grants.GetGrantPermissionSet(e.Permissions, eosFileInfo.IsDir)) } } @@ -1631,29 +1632,29 @@ func getResourceType(isDir bool) provider.ResourceType { return provider.ResourceType_RESOURCE_TYPE_FILE } -func (fs *eosfs) extractUIDAndGID(u *userpb.User) (string, string, error) { +func (fs *eosfs) extractUIDAndGID(u *userpb.User) (eosclient.Authorization, error) { if u.UidNumber == 0 { return "", "", errors.New("eosfs: uid missing for user") } if u.GidNumber == 0 { return "", "", errors.New("eosfs: gid missing for user") } - return strconv.FormatInt(u.UidNumber, 10), strconv.FormatInt(u.GidNumber, 10), nil + return eosclient.Authorization{Role: eosclient.Role{strconv.FormatInt(u.UidNumber, 10), strconv.FormatInt(u.GidNumber, 10)}}, nil } -func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (string, string, error) { +func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (eosclient.Authorization, error) { client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) if err != nil { - return "", "", errors.Wrap(err, "eosfs: error getting gateway grpc client") + return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting gateway grpc client") } getUserResp, err := client.GetUser(ctx, &userpb.GetUserRequest{ UserId: u, }) if err != nil { - return "", "", errors.Wrap(err, "eosfs: error getting user") + return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting user") } if getUserResp.Status.Code != rpc.Code_CODE_OK { - return "", "", errors.Wrap(err, "eosfs: grpc get user failed") + return eosclient.Authorization{}, errors.Wrap(err, "eosfs: grpc get user failed") } return fs.extractUIDAndGID(getUserResp.User) } @@ -1690,30 +1691,28 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User return getUserResp.User.Id, nil } -func (fs *eosfs) getUserUIDAndGID(ctx context.Context, u *userpb.User) (string, string, error) { +func (fs *eosfs) getUserUIDAndGID(ctx context.Context, u *userpb.User) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { - if fs.singleUserUID != "" && fs.singleUserGID != "" { - return fs.singleUserUID, fs.singleUserGID, nil + if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { + return fs.singleUserAuth, nil } - uid, gid, err := fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) - fs.singleUserUID = uid - fs.singleUserGID = gid - return fs.singleUserUID, fs.singleUserGID, err + var err error + fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) + return fs.singleUserAuth, err } return fs.extractUIDAndGID(u) } -func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (string, string, error) { +func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { - if fs.singleUserUID != "" && fs.singleUserGID != "" { - return fs.singleUserUID, fs.singleUserGID, nil + if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { + return fs.singleUserAuth, nil } - uid, gid, err := fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) - fs.singleUserUID = uid - fs.singleUserGID = gid - return fs.singleUserUID, fs.singleUserGID, err + var err error + fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) + return fs.singleUserAuth, err } - return "0", "0", nil + return eosclient.Authorization{Role: eosclient.Role{"0", "0"}}, nil } type eosSysMetadata struct { diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 74b3f923ec..15847a8273 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -34,7 +34,7 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC if err != nil { return errors.Wrap(err, "eos: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return err } @@ -71,7 +71,7 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC } fn := fs.wrap(ctx, p) - return fs.c.Write(ctx, uid, gid, fn, r) + return fs.c.Write(ctx, auth, fn, r) } func (fs *eosfs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { From 2d9a344dc60596bd008bc0b82a85ee902a18ebbc Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Mon, 7 Jun 2021 17:13:40 +0200 Subject: [PATCH 10/23] Comment out grpc client option --- pkg/storage/utils/eosfs/eosfs.go | 117 +++++++++++++++++-------------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 6195571111..d67d98178b 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -29,7 +29,6 @@ import ( "regexp" "strconv" "strings" - "sync" "github.com/bluele/gcache" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" @@ -40,7 +39,6 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/eosclient" "github.com/cs3org/reva/pkg/eosclient/eosbinary" - "github.com/cs3org/reva/pkg/eosclient/eosgrpc" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -135,7 +133,7 @@ type eosfs struct { conf *Config chunkHandler *chunking.ChunkHandler singleUserAuth eosclient.Authorization - userIDCache sync.Map + userIDCache gcache.Cache } // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance @@ -151,40 +149,54 @@ func NewEOSFS(c *Config) (storage.FS, error) { } var eosClient eosclient.EOSClient - if c.UseGRPC { - eosClientOpts := &eosgrpc.Options{ - XrdcopyBinary: c.XrdcopyBinary, - URL: c.MasterURL, - GrpcURI: c.GrpcURI, - CacheDirectory: c.CacheDirectory, - UseKeytab: c.UseKeytab, - Keytab: c.Keytab, - Authkey: c.GRPCAuthkey, - SecProtocol: c.SecProtocol, - VersionInvariant: c.VersionInvariant, - ReadUsesLocalTemp: c.ReadUsesLocalTemp, - WriteUsesLocalTemp: c.WriteUsesLocalTemp, - MaxIdleConns: c.MaxIdleConns, - MaxConnsPerHost: c.MaxConnsPerHost, - MaxIdleConnsPerHost: c.MaxIdleConnsPerHost, - IdleConnTimeout: c.IdleConnTimeout, - } - eosClient = eosgrpc.New(eosClientOpts) - } else { - eosClientOpts := &eosbinary.Options{ - XrdcopyBinary: c.XrdcopyBinary, - URL: c.MasterURL, - EosBinary: c.EosBinary, - CacheDirectory: c.CacheDirectory, - ForceSingleUserMode: c.ForceSingleUserMode, - SingleUsername: c.SingleUsername, - UseKeytab: c.UseKeytab, - Keytab: c.Keytab, - SecProtocol: c.SecProtocol, - VersionInvariant: c.VersionInvariant, - } - eosClient = eosbinary.New(eosClientOpts) - } + // if c.UseGRPC { + // eosClientOpts := &eosgrpc.Options{ + // XrdcopyBinary: c.XrdcopyBinary, + // URL: c.MasterURL, + // GrpcURI: c.GrpcURI, + // CacheDirectory: c.CacheDirectory, + // UseKeytab: c.UseKeytab, + // Keytab: c.Keytab, + // Authkey: c.GRPCAuthkey, + // SecProtocol: c.SecProtocol, + // VersionInvariant: c.VersionInvariant, + // ReadUsesLocalTemp: c.ReadUsesLocalTemp, + // WriteUsesLocalTemp: c.WriteUsesLocalTemp, + // MaxIdleConns: c.MaxIdleConns, + // MaxConnsPerHost: c.MaxConnsPerHost, + // MaxIdleConnsPerHost: c.MaxIdleConnsPerHost, + // IdleConnTimeout: c.IdleConnTimeout, + // } + // eosClient = eosgrpc.New(eosClientOpts) + // } else { + // eosClientOpts := &eosbinary.Options{ + // XrdcopyBinary: c.XrdcopyBinary, + // URL: c.MasterURL, + // EosBinary: c.EosBinary, + // CacheDirectory: c.CacheDirectory, + // ForceSingleUserMode: c.ForceSingleUserMode, + // SingleUsername: c.SingleUsername, + // UseKeytab: c.UseKeytab, + // Keytab: c.Keytab, + // SecProtocol: c.SecProtocol, + // VersionInvariant: c.VersionInvariant, + // } + // eosClient = eosbinary.New(eosClientOpts) + // } + + eosClientOpts := &eosbinary.Options{ + XrdcopyBinary: c.XrdcopyBinary, + URL: c.MasterURL, + EosBinary: c.EosBinary, + CacheDirectory: c.CacheDirectory, + ForceSingleUserMode: c.ForceSingleUserMode, + SingleUsername: c.SingleUsername, + UseKeytab: c.UseKeytab, + Keytab: c.Keytab, + SecProtocol: c.SecProtocol, + VersionInvariant: c.VersionInvariant, + } + eosClient = eosbinary.New(eosClientOpts) eosfs := &eosfs{ c: eosClient, @@ -202,12 +214,12 @@ func (fs *eosfs) userIDcacheWarmup() { if !fs.conf.EnableHome { ctx := context.Background() paths := []string{fs.wrap(ctx, "/")} - uid, gid, _ := fs.getRootUIDAndGID(ctx) + auth, _ := fs.getRootUIDAndGID(ctx) for i := 0; i < fs.conf.UserIDCacheWarmupDepth; i++ { var newPaths []string for _, fn := range paths { - if eosFileInfos, err := fs.c.List(ctx, uid, gid, fn); err == nil { + if eosFileInfos, err := fs.c.List(ctx, auth, fn); err == nil { for _, f := range eosFileInfos { _, _ = fs.getUserIDGateway(ctx, strconv.FormatUint(f.UID, 10)) newPaths = append(newPaths, f.File) @@ -412,7 +424,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return errors.Wrap(err, "eosfs: error getting uid and gid for user") } @@ -439,7 +451,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen // TODO(labkode): SetArbitraryMetadata does not has semantic for recursivity. // We set it to false - err := fs.c.SetAttr(ctx, uid, gid, attr, false, p) + err := fs.c.SetAttr(ctx, auth, attr, false, p) if err != nil { return errors.Wrap(err, "eosfs: error setting xattr in eos driver") } @@ -454,7 +466,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer return errors.Wrap(err, "eosfs: no user in ctx") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { return errors.Wrap(err, "eosfs: error getting uid and gid for user") } @@ -478,7 +490,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer Key: k, } - err := fs.c.UnsetAttr(ctx, uid, gid, attr, p) + err := fs.c.UnsetAttr(ctx, auth, attr, p) if err != nil { return errors.Wrap(err, "eosfs: error unsetting xattr in eos driver") } @@ -879,9 +891,9 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } - uid, _, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserUIDAndGID(ctx, u) if err != nil { - return 0, 0, errors.Wrap(err, "eosfs: no uid in ctx") + return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") } rootAuth, err := fs.getRootUIDAndGID(ctx) @@ -889,7 +901,7 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { return 0, 0, err } - qi, err := fs.c.GetQuota(ctx, uid, rootAuth, fs.conf.QuotaNode) + qi, err := fs.c.GetQuota(ctx, auth.Role.UID, rootAuth, fs.conf.QuotaNode) if err != nil { err := errors.Wrap(err, "eosfs: error getting quota") return 0, 0, err @@ -985,8 +997,8 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { // set quota for user quotaInfo := &eosclient.SetQuotaInfo{ Username: u.Username, - UID: uid, - GID: gid, + UID: auth.Role.UID, + GID: auth.Role.GID, MaxBytes: fs.conf.DefaultQuotaBytes, MaxFiles: fs.conf.DefaultQuotaFiles, QuotaNode: fs.conf.QuotaNode, @@ -1634,10 +1646,10 @@ func getResourceType(isDir bool) provider.ResourceType { func (fs *eosfs) extractUIDAndGID(u *userpb.User) (eosclient.Authorization, error) { if u.UidNumber == 0 { - return "", "", errors.New("eosfs: uid missing for user") + return eosclient.Authorization{}, errors.New("eosfs: uid missing for user") } if u.GidNumber == 0 { - return "", "", errors.New("eosfs: gid missing for user") + return eosclient.Authorization{}, errors.New("eosfs: gid missing for user") } return eosclient.Authorization{Role: eosclient.Role{strconv.FormatInt(u.UidNumber, 10), strconv.FormatInt(u.GidNumber, 10)}}, nil } @@ -1700,6 +1712,9 @@ func (fs *eosfs) getUserUIDAndGID(ctx context.Context, u *userpb.User) (eosclien fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) return fs.singleUserAuth, err } + // if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + // + // } return fs.extractUIDAndGID(u) } @@ -1712,7 +1727,7 @@ func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (eosclient.Authorization, fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) return fs.singleUserAuth, err } - return eosclient.Authorization{Role: eosclient.Role{"0", "0"}}, nil + return eosclient.Authorization{Role: eosclient.Role{UID: "0", GID: "0"}}, nil } type eosSysMetadata struct { From aff3e3096f5b80d85fd914feeb169b2c72f5c7de Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 7 Jul 2021 15:14:13 +0200 Subject: [PATCH 11/23] Fix cli commands --- cmd/reva/app-tokens-create.go | 44 +++++++------------ internal/grpc/interceptors/auth/auth.go | 2 +- .../grpc/services/gateway/authprovider.go | 4 +- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index 9b5a0d46f7..64da5ef530 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -136,52 +136,42 @@ func appTokensCreateCommand() *command { } func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *appTokenCreateOpts) (map[string]*authpb.Scope, error) { - var scopeList []map[string]*authpb.Scope - switch { - case opts.Unlimited: - return scope.GetOwnerScope() - case len(opts.Share) != 0: + if opts.Unlimited { + return scope.AddOwnerScope(nil) + } + + var scopes map[string]*authpb.Scope + var err error + if len(opts.Share) != 0 { // TODO(gmgigi96): verify format for _, entry := range opts.Share { // share = xxxx:[r|w] shareIDPerm := strings.Split(entry, ":") shareID, perm := shareIDPerm[0], shareIDPerm[1] - scope, err := getPublicShareScope(ctx, client, shareID, perm) + scopes, err = getPublicShareScope(ctx, client, shareID, perm, scopes) if err != nil { return nil, err } - scopeList = append(scopeList, scope) } - fallthrough - case len(opts.Path) != 0: + } + + if len(opts.Path) != 0 { // TODO(gmgigi96): verify format for _, entry := range opts.Path { // path = /home/a/b:[r|w] pathPerm := strings.Split(entry, ":") path, perm := pathPerm[0], pathPerm[1] - scope, err := getPathScope(ctx, client, path, perm) + scopes, err = getPathScope(ctx, client, path, perm, scopes) if err != nil { return nil, err } - scopeList = append(scopeList, scope) } - fallthrough - default: - return mergeListScopeIntoMap(scopeList), nil } -} -func mergeListScopeIntoMap(scopeList []map[string]*authpb.Scope) map[string]*authpb.Scope { - merged := make(map[string]*authpb.Scope) - for _, scope := range scopeList { - for k, v := range scope { - merged[k] = v - } - } - return merged + return scopes, nil } -func getPublicShareScope(ctx context.Context, client gateway.GatewayAPIClient, shareID, perm string) (map[string]*authpb.Scope, error) { +func getPublicShareScope(ctx context.Context, client gateway.GatewayAPIClient, shareID, perm string, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { role, err := parsePermission(perm) if err != nil { return nil, err @@ -204,10 +194,10 @@ func getPublicShareScope(ctx context.Context, client gateway.GatewayAPIClient, s return nil, formatError(publicShareResponse.Status) } - return scope.GetPublicShareScope(publicShareResponse.GetShare(), role) + return scope.AddPublicShareScope(publicShareResponse.GetShare(), role, scopes) } -func getPathScope(ctx context.Context, client gateway.GatewayAPIClient, path, perm string) (map[string]*authpb.Scope, error) { +func getPathScope(ctx context.Context, client gateway.GatewayAPIClient, path, perm string, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { role, err := parsePermission(perm) if err != nil { return nil, err @@ -222,7 +212,7 @@ func getPathScope(ctx context.Context, client gateway.GatewayAPIClient, path, pe return nil, formatError(statResponse.Status) } - return scope.GetResourceInfoScope(statResponse.GetInfo(), role) + return scope.AddResourceInfoScope(statResponse.GetInfo(), role, scopes) } // parse permission string in the form of "rw" to create a role diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 08e8902933..935b60ae8b 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -292,7 +292,7 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. continue } for _, share := range shares.Shares { - if utils.ResourceEqual(share.Share.ResourceId, ref.GetId()) { + if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { return u, nil } } diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 434d3cf5e0..0dea3a4828 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -281,9 +281,7 @@ func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scop func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.ResourceId, role authpb.Role, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { statReq := &storageprovider.StatRequest{ - Ref: &storageprovider.Reference{ - Spec: &storageprovider.Reference_Id{Id: r}, - }, + Ref: &storageprovider.Reference{ResourceId: r}, } statResponse, err := s.Stat(ctx, statReq) if err != nil { From 9cd67888ebe089c5eb2841fd02b5c61ec0ac3b49 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 7 Jul 2021 19:30:05 +0200 Subject: [PATCH 12/23] Add received share scope --- examples/storage-references/users.demo.json | 12 ++++ internal/grpc/interceptors/auth/auth.go | 37 +++++++++- .../grpc/services/gateway/authprovider.go | 8 +-- .../http/services/owncloud/ocdav/error.go | 3 + pkg/auth/manager/demo/demo.go | 20 ++++-- pkg/auth/manager/json/json.go | 20 ++++-- pkg/auth/manager/ldap/ldap.go | 18 +++-- pkg/auth/manager/oidc/oidc.go | 20 ++++-- pkg/auth/scope/lightweight.go | 5 ++ pkg/auth/scope/receivedshare.go | 67 +++++++++++++++++++ pkg/auth/scope/scope.go | 11 +-- pkg/auth/scope/share.go | 2 + 12 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 pkg/auth/scope/receivedshare.go diff --git a/examples/storage-references/users.demo.json b/examples/storage-references/users.demo.json index 12c784f7eb..426ba07b75 100644 --- a/examples/storage-references/users.demo.json +++ b/examples/storage-references/users.demo.json @@ -34,5 +34,17 @@ "mail": "richard@example.org", "display_name": "Richard Feynman", "groups": ["quantum-lovers", "philosophy-haters", "physics-lovers"] + }, + { + "id": { + "opaque_id": "0e4d9dc1-8349-49fe-8afc-6b844aec1cf6", + "idp": "localhost:20080", + "type": 7 + }, + "username": "lwaccount", + "secret": "lightweight", + "mail": "lwaccount@example.org", + "display_name": "Lightweight Test Account", + "groups": ["guest-users", "weight-loss-club", "physics-lovers"] } ] diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 935b60ae8b..7fc315c851 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -121,7 +121,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr) if err != nil { log.Warn().Err(err).Msg("access token is invalid") - return nil, status.Errorf(codes.Unauthenticated, "auth: core access token is invalid") + return nil, status.Errorf(codes.PermissionDenied, "auth: core access token is invalid") } // store user and core access token in context. @@ -192,7 +192,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr) if err != nil { log.Warn().Err(err).Msg("access token is invalid") - return status.Errorf(codes.Unauthenticated, "auth: core access token is invalid") + return status.Errorf(codes.PermissionDenied, "auth: core access token is invalid") } // store user and core access token in context. @@ -299,6 +299,29 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } } } + } else if ref, ok := extractShareRef(req); ok { + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return nil, err + } + for k := range tokenScope { + if strings.HasPrefix(k, "lightweight") { + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue + } + for _, s := range shares.Shares { + if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId { + return u, nil + } + if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) && + utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) { + return u, nil + } + } + } + } } return nil, errtypes.PermissionDenied("access to resource not allowed within the assigned scope") @@ -353,3 +376,13 @@ func extractRef(req interface{}) (*provider.Reference, bool) { } return nil, false } + +func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) { + switch v := req.(type) { + case *collaboration.GetReceivedShareRequest: + return v.GetRef(), true + case *collaboration.UpdateReceivedShareRequest: + return v.GetRef(), true + } + return nil, false +} diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 0dea3a4828..11928bd720 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -239,7 +239,7 @@ func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scop } newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) if err != nil { - log.Warn().Err(err).Msgf("error expanding publicshare scope %+v", share.ResourceId) + log.Warn().Err(err).Msgf("error expanding publicshare resource scope %+v", share.ResourceId) continue } @@ -252,7 +252,7 @@ func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scop } newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) if err != nil { - log.Warn().Err(err).Msgf("error expanding share scope %+v", share.ResourceId) + log.Warn().Err(err).Msgf("error expanding share resource scope %+v", share.ResourceId) continue } @@ -263,14 +263,14 @@ func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scop continue } for _, share := range shares.Shares { - newMap, err = scope.AddShareScope(share.Share, v.Role, newMap) + newMap, err = scope.AddReceivedShareScope(share, v.Role, newMap) if err != nil { log.Warn().Err(err).Msgf("error expanding received share scope %+v", share.Share.ResourceId) continue } newMap, err = s.statAndAddResource(ctx, share.Share.ResourceId, v.Role, newMap) if err != nil { - log.Warn().Err(err).Msgf("error expanding received share scope %+v", share.Share.ResourceId) + log.Warn().Err(err).Msgf("error expanding received share resource scope %+v", share.Share.ResourceId) continue } } diff --git a/internal/http/services/owncloud/ocdav/error.go b/internal/http/services/owncloud/ocdav/error.go index 114b80fdff..73c644726d 100644 --- a/internal/http/services/owncloud/ocdav/error.go +++ b/internal/http/services/owncloud/ocdav/error.go @@ -107,6 +107,9 @@ func HandleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status case rpc.Code_CODE_PERMISSION_DENIED: log.Debug().Interface("status", s).Msg("permission denied") w.WriteHeader(http.StatusForbidden) + case rpc.Code_CODE_UNAUTHENTICATED: + log.Debug().Interface("status", s).Msg("unauthenticated") + w.WriteHeader(http.StatusUnauthorized) case rpc.Code_CODE_INVALID_ARGUMENT: log.Debug().Interface("status", s).Msg("bad request") w.WriteHeader(http.StatusBadRequest) diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index 1e855f66c1..b7c09ced22 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -51,14 +51,22 @@ func New(m map[string]interface{}) (auth.Manager, error) { } func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { - scope, err := scope.AddOwnerScope(nil) - if err != nil { - return nil, nil, err - } - if c, ok := m.credentials[clientID]; ok { if c.Secret == clientSecret { - return c.User, scope, nil + var scopes map[string]*authpb.Scope + var err error + if c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) + if err != nil { + return nil, nil, err + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + } + return c.User, scopes, nil } } return nil, nil, errtypes.InvalidCredentials(clientID) diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index dd876cd35d..7d5b0021a8 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -106,13 +106,21 @@ func New(m map[string]interface{}) (auth.Manager, error) { } func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, map[string]*authpb.Scope, error) { - scope, err := scope.AddOwnerScope(nil) - if err != nil { - return nil, nil, err - } - if c, ok := m.credentials[username]; ok { if c.Secret == secret { + var scopes map[string]*authpb.Scope + var err error + if c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) + if err != nil { + return nil, nil, err + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + } return &user.User{ Id: c.ID, Username: c.Username, @@ -124,7 +132,7 @@ func (m *manager) Authenticate(ctx context.Context, username string, secret stri GidNumber: c.GIDNumber, Opaque: c.Opaque, // TODO add arbitrary keys as opaque data - }, scope, nil + }, scopes, nil } } return nil, nil, errtypes.InvalidCredentials(username) diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index ffeebbd58c..4a8ea78238 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -174,7 +174,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) userID := &user.UserId{ Idp: am.c.Idp, OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(am.c.Schema.UID), - Type: user.UserType_USER_TYPE_PRIMARY, + Type: user.UserType_USER_TYPE_PRIMARY, // TODO: assign the appropriate user type } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { @@ -217,14 +217,22 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: gidNumber, } - scope, err := scope.AddOwnerScope(nil) - if err != nil { - return nil, nil, err + var scopes map[string]*authpb.Scope + if userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) + if err != nil { + return nil, nil, err + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } } log.Debug().Interface("entry", sr.Entries[0]).Interface("user", u).Msg("authenticated user") - return u, scope, nil + return u, scopes, nil } diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index 7968affab1..f3bff7c1a5 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -172,12 +172,20 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: int64(gid), } - scope, err := scope.AddOwnerScope(nil) - if err != nil { - return nil, nil, err - } - - return u, scope, nil + var scopes map[string]*authpb.Scope + if userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) + if err != nil { + return nil, nil, err + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + } + + return u, scopes, nil } func (am *mgr) getOAuthCtx(ctx context.Context) context.Context { diff --git a/pkg/auth/scope/lightweight.go b/pkg/auth/scope/lightweight.go index 977e02349a..9390743b24 100644 --- a/pkg/auth/scope/lightweight.go +++ b/pkg/auth/scope/lightweight.go @@ -20,6 +20,7 @@ package scope import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/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/utils" @@ -29,6 +30,10 @@ func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, e // Lightweight accounts have access to resources shared with them. // These cannot be resolved from here, but need to be added to the scope from // where the call to mint tokens is made. + // From here, we only allow ListReceivedShares calls + if _, ok := resource.(*collaboration.ListReceivedSharesRequest); ok { + return true, nil + } return false, nil } diff --git a/pkg/auth/scope/receivedshare.go b/pkg/auth/scope/receivedshare.go new file mode 100644 index 0000000000..cc9b90353f --- /dev/null +++ b/pkg/auth/scope/receivedshare.go @@ -0,0 +1,67 @@ +// 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 scope + +import ( + "fmt" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" +) + +func receivedShareScope(scope *authpb.Scope, resource interface{}) (bool, error) { + var share collaboration.ReceivedShare + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share) + if err != nil { + return false, err + } + + switch v := resource.(type) { + case *collaboration.GetReceivedShareRequest: + return checkShareRef(share.Share, v.GetRef()), nil + case *collaboration.UpdateReceivedShareRequest: + return checkShareRef(share.Share, v.GetRef()), nil + case string: + return checkSharePath(v) || checkResourcePath(v), nil + } + return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) +} + +// AddReceivedShareScope adds the scope to allow access to a received user/group share and +// the shared resource. +func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + val, err := utils.MarshalProtoV1ToJSON(share) + if err != nil { + return nil, err + } + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["receivedshare:"+share.Share.Id.OpaqueId] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: role, + } + return scopes, nil +} diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 4758e9468e..e88d0acc43 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -28,11 +28,12 @@ import ( type Verifier func(*authpb.Scope, interface{}) (bool, error) var supportedScopes = map[string]Verifier{ - "user": userScope, - "publicshare": publicshareScope, - "resourceinfo": resourceinfoScope, - "share": shareScope, - "lightweight": lightweightAccountScope, + "user": userScope, + "publicshare": publicshareScope, + "resourceinfo": resourceinfoScope, + "share": shareScope, + "receivedshare": receivedShareScope, + "lightweight": lightweightAccountScope, } // VerifyScope is the function to be called when dismantling tokens to check if diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go index eace67e437..de26f62a02 100644 --- a/pkg/auth/scope/share.go +++ b/pkg/auth/scope/share.go @@ -95,6 +95,8 @@ func checkSharePath(path string) bool { paths := []string{ "/ocs/v2.php/apps/files_sharing/api/v1/shares", "/ocs/v1.php/apps/files_sharing/api/v1/shares", + "/remote.php/webdav", + "/remote.php/dav/files", } for _, p := range paths { if strings.HasPrefix(path, p) { From 112f2551e9d0b3d9c5262bf7f40bb0bb18b3edde Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 7 Jul 2021 19:31:35 +0200 Subject: [PATCH 13/23] Lint fix --- pkg/auth/manager/demo/demo.go | 2 +- pkg/auth/manager/json/json.go | 2 +- pkg/auth/manager/ldap/ldap.go | 2 +- pkg/auth/manager/oidc/oidc.go | 2 +- pkg/storage/utils/eosfs/eosfs.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index b7c09ced22..a4d18f6964 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -55,7 +55,7 @@ func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret strin if c.Secret == clientSecret { var scopes map[string]*authpb.Scope var err error - if c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if c.User.Id != nil && c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index 7d5b0021a8..e12c907126 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -110,7 +110,7 @@ func (m *manager) Authenticate(ctx context.Context, username string, secret stri if c.Secret == secret { var scopes map[string]*authpb.Scope var err error - if c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if c.ID != nil && c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index 4a8ea78238..1727076d0f 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -218,7 +218,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } var scopes map[string]*authpb.Scope - if userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index f3bff7c1a5..dd717bec85 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -173,7 +173,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } var scopes map[string]*authpb.Scope - if userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index d67d98178b..3a4f306da5 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1651,7 +1651,7 @@ func (fs *eosfs) extractUIDAndGID(u *userpb.User) (eosclient.Authorization, erro if u.GidNumber == 0 { return eosclient.Authorization{}, errors.New("eosfs: gid missing for user") } - return eosclient.Authorization{Role: eosclient.Role{strconv.FormatInt(u.UidNumber, 10), strconv.FormatInt(u.GidNumber, 10)}}, nil + return eosclient.Authorization{Role: eosclient.Role{UID: strconv.FormatInt(u.UidNumber, 10), GID: strconv.FormatInt(u.GidNumber, 10)}}, nil } func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (eosclient.Authorization, error) { From af7324bd2d1879d92ef3603d06b12dc7eae0f3a9 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 7 Jul 2021 20:52:16 +0200 Subject: [PATCH 14/23] Refactor eosgrpc and introduce authorization --- pkg/eosclient/eosbinary/eosbinary.go | 4 +- pkg/eosclient/eosgrpc/eosgrpc.go | 343 ++++++++---------- .../eosgrpc/{eos_http => }/eoshttp.go | 118 +++--- pkg/storage/utils/eosfs/eosfs.go | 163 ++++----- pkg/storage/utils/eosfs/upload.go | 2 +- 5 files changed, 293 insertions(+), 337 deletions(-) rename pkg/eosclient/eosgrpc/{eos_http => }/eoshttp.go (86%) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index eab800b42d..beccff6431 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -146,11 +146,11 @@ type Client struct { } // New creates a new client with the given options. -func New(opt *Options) *Client { +func New(opt *Options) (*Client, error) { opt.init() c := new(Client) c.opt = opt - return c + return c, nil } // executeXRDCopy executes xrdcpy commands and returns the stdout, stderr and return code diff --git a/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index ea4db75db3..0c0ae43d20 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -25,7 +25,6 @@ import ( "fmt" "io" "io/ioutil" - "net/http" "os" "os/exec" "path" @@ -38,7 +37,6 @@ import ( "github.com/cs3org/reva/pkg/eosclient" erpc "github.com/cs3org/reva/pkg/eosclient/eosgrpc/eos_grpc" - ehttp "github.com/cs3org/reva/pkg/eosclient/eosgrpc/eos_http" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" "github.com/cs3org/reva/pkg/storage/utils/acl" @@ -104,18 +102,6 @@ type Options struct { // SecProtocol is the comma separated list of security protocols used by xrootd. // For example: "sss, unix" SecProtocol string - - // HTTP connections to EOS: max number of idle conns - MaxIdleConns int - - // HTTP connections to EOS: max number of conns per host - MaxConnsPerHost int - - // HTTP connections to EOS: max number of idle conns per host - MaxIdleConnsPerHost int - - // HTTP connections to EOS: idle conections TTL - IdleConnTimeout int } func (opt *Options) init() { @@ -137,16 +123,9 @@ func (opt *Options) init() { // Client performs actions against a EOS management node (MGM) // using the EOS GRPC interface. type Client struct { - opt *Options - htopts ehttp.Options - httptransport *http.Transport - cl erpc.EosClient -} - -// GetHTTPCl creates an http client for immediate usage, using the already instantiated resources -func (c *Client) GetHTTPCl() *ehttp.Client { - - return ehttp.New(&c.htopts, c.httptransport) + opt *Options + httpcl *EOSHTTPClient + cl erpc.EosClient } // Create and connect a grpc eos Client @@ -179,29 +158,27 @@ func newgrpc(ctx context.Context, opt *Options) (erpc.EosClient, error) { } // New creates a new client with the given options. -func New(opt *Options) *Client { +func New(opt *Options, httpOpts *HTTPOptions) (*Client, error) { tlog := logger.New().With().Int("pid", os.Getpid()).Logger() tlog.Debug().Str("Creating new eosgrpc client. opt: ", "'"+fmt.Sprintf("%#v", opt)+"' ").Msg("") opt.init() - c := new(Client) - c.opt = opt - - t, err := c.htopts.Init() + httpcl, err := NewEOSHTTPClient(httpOpts) if err != nil { - panic("Cant't init the EOS http client options") + return nil, err } - c.httptransport = t - c.htopts.BaseURL = c.opt.URL tctx := appctx.WithLogger(context.Background(), &tlog) - ccl, err := newgrpc(tctx, opt) + cl, err := newgrpc(tctx, opt) if err != nil { - return nil + return nil, err } - c.cl = ccl - return c + return &Client{ + opt: opt, + httpcl: httpcl, + cl: cl, + }, nil } // If the error is not nil, take that @@ -220,20 +197,20 @@ func (c *Client) getRespError(rsp *erpc.NSResponse, err error) error { } // Common code to create and initialize a NSRequest -func (c *Client) initNSRequest(ctx context.Context, uid, gid string) (*erpc.NSRequest, error) { +func (c *Client) initNSRequest(ctx context.Context, auth eosclient.Authorization) (*erpc.NSRequest, error) { // Stuff filename, uid, gid into the MDRequest type log := appctx.GetLogger(ctx) - log.Debug().Str("(uid,gid)", "("+uid+","+gid+")").Msg("New grpcNS req") + log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcNS req") rq := new(erpc.NSRequest) rq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(uid, 10, 64) + uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) if err != nil { return nil, err } - gidInt, err := strconv.ParseUint(gid, 10, 64) + gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) if err != nil { return nil, err } @@ -245,20 +222,20 @@ func (c *Client) initNSRequest(ctx context.Context, uid, gid string) (*erpc.NSRe } // Common code to create and initialize a NSRequest -func (c *Client) initMDRequest(ctx context.Context, uid, gid string) (*erpc.MDRequest, error) { +func (c *Client) initMDRequest(ctx context.Context, auth eosclient.Authorization) (*erpc.MDRequest, error) { // Stuff filename, uid, gid into the MDRequest type log := appctx.GetLogger(ctx) - log.Debug().Str("(uid,gid)", "("+uid+","+gid+")").Msg("New grpcMD req") + log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcMD req") mdrq := new(erpc.MDRequest) mdrq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(uid, 10, 64) + uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) if err != nil { return nil, err } - gidInt, err := strconv.ParseUint(gid, 10, 64) + gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) if err != nil { return nil, err } @@ -271,12 +248,12 @@ func (c *Client) initMDRequest(ctx context.Context, uid, gid string) (*erpc.MDRe } // AddACL adds an new acl to EOS with the given aclType. -func (c *Client) AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { +func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "AddACL").Str("uid,gid", uid+","+gid).Str("rootuid,rootgid", rootUID+","+rootGID).Str("path", path).Msg("") + log.Info().Str("func", "AddACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") - acls, err := c.getACLForPath(ctx, uid, gid, path) + acls, err := c.getACLForPath(ctx, auth, path) if err != nil { return err } @@ -288,7 +265,7 @@ func (c *Client) AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path st sysACL := acls.Serialize() // Init a new NSRequest - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -323,12 +300,12 @@ func (c *Client) AddACL(ctx context.Context, uid, gid, rootUID, rootGID, path st } // RemoveACL removes the acl from EOS. -func (c *Client) RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { +func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "RemoveACL").Str("uid,gid", uid+","+gid).Str("rootuid,rootgid", rootUID+","+rootGID).Str("path", path).Msg("") + log.Info().Str("func", "RemoveACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") - acls, err := c.getACLForPath(ctx, uid, gid, path) + acls, err := c.getACLForPath(ctx, auth, path) if err != nil { return err } @@ -337,7 +314,7 @@ func (c *Client) RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path sysACL := acls.Serialize() // Init a new NSRequest - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -372,17 +349,17 @@ func (c *Client) RemoveACL(ctx context.Context, uid, gid, rootUID, rootGID, path } // UpdateACL updates the EOS acl. -func (c *Client) UpdateACL(ctx context.Context, uid, gid, rootUID, rootGID, path string, a *acl.Entry) error { - return c.AddACL(ctx, uid, gid, path, rootUID, rootGID, a) +func (c *Client) UpdateACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { + return c.AddACL(ctx, auth, rootAuth, path, a) } // GetACL for a file -func (c *Client) GetACL(ctx context.Context, uid, gid, path, aclType, target string) (*acl.Entry, error) { +func (c *Client) GetACL(ctx context.Context, auth eosclient.Authorization, path, aclType, target string) (*acl.Entry, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GetACL").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "GetACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") - acls, err := c.ListACLs(ctx, uid, gid, path) + acls, err := c.ListACLs(ctx, auth, path) if err != nil { return nil, err } @@ -398,11 +375,11 @@ func (c *Client) GetACL(ctx context.Context, uid, gid, path, aclType, target str // ListACLs returns the list of ACLs present under the given path. // EOS returns uids/gid for Citrine version and usernames for older versions. // For Citire we need to convert back the uid back to username. -func (c *Client) ListACLs(ctx context.Context, uid, gid, path string) ([]*acl.Entry, error) { +func (c *Client) ListACLs(ctx context.Context, auth eosclient.Authorization, path string) ([]*acl.Entry, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "ListACLs").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "ListACLs").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") - parsedACLs, err := c.getACLForPath(ctx, uid, gid, path) + parsedACLs, err := c.getACLForPath(ctx, auth, path) if err != nil { return nil, err } @@ -412,12 +389,12 @@ func (c *Client) ListACLs(ctx context.Context, uid, gid, path string) ([]*acl.En return parsedACLs.Entries, nil } -func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl.ACLs, error) { +func (c *Client) getACLForPath(ctx context.Context, auth eosclient.Authorization, path string) (*acl.ACLs, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GetACLForPath").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "GetACLForPath").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return nil, err } @@ -441,17 +418,17 @@ func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl } if resp == nil { - return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", uid, path)) + return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path)) } log.Debug().Str("func", "GetACLForPath").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") if resp.Acl == nil { - return nil, errtypes.InternalError(fmt.Sprintf("nil acl for uid: '%s' path: '%s'", uid, path)) + return nil, errtypes.InternalError(fmt.Sprintf("nil acl for uid: '%s' path: '%s'", auth.Role.UID, path)) } if resp.GetError() != nil { - log.Error().Str("func", "GetACLForPath").Str("uid", uid).Str("path", path).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") + log.Error().Str("func", "GetACLForPath").Str("uid", auth.Role.UID).Str("path", path).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") } aclret, err := acl.Parse(resp.Acl.Rule, acl.ShortTextForm) @@ -462,12 +439,12 @@ func (c *Client) getACLForPath(ctx context.Context, uid, gid, path string) (*acl } // GetFileInfoByInode returns the FileInfo by the given inode -func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode uint64) (*eosclient.FileInfo, error) { +func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authorization, inode uint64) (*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GetFileInfoByInode").Str("uid,gid", uid+","+gid).Uint64("inode", inode).Msg("") + log.Info().Str("func", "GetFileInfoByInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Uint64("inode", inode).Msg("") // Initialize the common fields of the MDReq - mdrq, err := c.initMDRequest(ctx, uid, gid) + mdrq, err := c.initMDRequest(ctx, auth) if err != nil { return nil, err } @@ -502,7 +479,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode } if c.opt.VersionInvariant && isVersionFolder(info.File) { - info, err = c.getFileInfoFromVersion(ctx, uid, gid, info.File) + info, err = c.getFileInfoFromVersion(ctx, auth, info.File) if err != nil { return nil, err } @@ -514,12 +491,12 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, uid, gid string, inode } // SetAttr sets an extended attributes on a path. -func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.Attribute, recursive bool, path string) error { +func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "SetAttr").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "SetAttr").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -544,7 +521,7 @@ func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.A } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", uid, gid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", auth.Role.UID, auth.Role.GID, path)) } if resp.GetError() != nil { @@ -556,12 +533,12 @@ func (c *Client) SetAttr(ctx context.Context, uid, gid string, attr *eosclient.A } // UnsetAttr unsets an extended attribute on a path. -func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient.Attribute, path string) error { +func (c *Client) UnsetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "UnsetAttr").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "UnsetAttr").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -585,7 +562,7 @@ func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", uid, gid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", auth.Role.UID, auth.Role.GID, path)) } if resp.GetError() != nil { @@ -596,12 +573,12 @@ func (c *Client) UnsetAttr(ctx context.Context, uid, gid string, attr *eosclient } // GetFileInfoByPath returns the FilInfo at the given path -func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) (*eosclient.FileInfo, error) { +func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GetFileInfoByPath").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "GetFileInfoByPath").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the MDReq - mdrq, err := c.initMDRequest(ctx, uid, gid) + mdrq, err := c.initMDRequest(ctx, auth) if err != nil { return nil, err } @@ -641,7 +618,7 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) ( } if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir { - inode, err := c.getVersionFolderInode(ctx, uid, gid, path) + inode, err := c.getVersionFolderInode(ctx, auth, path) if err != nil { return nil, err } @@ -651,17 +628,17 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, uid, gid, path string) ( } // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal -func (c *Client) GetFileInfoByFXID(ctx context.Context, uid, gid string, fxid string) (*eosclient.FileInfo, error) { +func (c *Client) GetFileInfoByFXID(ctx context.Context, auth eosclient.Authorization, fxid string) (*eosclient.FileInfo, error) { return nil, errtypes.NotSupported("eosgrpc: GetFileInfoByFXID not implemented") } // GetQuota gets the quota of a user on the quota node defined by path -func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path string) (*eosclient.QuotaInfo, error) { +func (c *Client) GetQuota(ctx context.Context, username string, rootAuth eosclient.Authorization, path string) (*eosclient.QuotaInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("username", username).Str("path", path).Msg("") + log.Info().Str("func", "GetQuota").Str("rootuid,rootgid", rootAuth.Role.UID+","+rootAuth.Role.GID).Str("username", username).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, rootUID, rootGID) + rq, err := c.initNSRequest(ctx, rootAuth) if err != nil { return nil, err } @@ -683,21 +660,21 @@ func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path } if resp == nil { - return nil, errtypes.InternalError(fmt.Sprintf("nil response for rootuid: '%s' rootgid: '%s' username: '%s' path: '%s'", rootUID, rootGID, username, path)) + return nil, errtypes.InternalError(fmt.Sprintf("nil response for username: '%s' path: '%s'", username, path)) } if resp.GetError() != nil { - log.Error().Str("func", "GetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Int64("eoserrcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") + log.Error().Str("func", "GetQuota").Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Int64("eoserrcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") } else { - log.Debug().Str("func", "GetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response") + log.Debug().Str("func", "GetQuota").Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response") } if resp.Quota == nil { - return nil, errtypes.InternalError(fmt.Sprintf("nil quota response? rootuid: '%s' rootgid: '%s' path: '%s'", rootUID, rootGID, path)) + return nil, errtypes.InternalError(fmt.Sprintf("nil quota response? path: '%s'", path)) } if resp.Quota.Code != 0 { - return nil, errtypes.InternalError(fmt.Sprintf("Quota error from eos. rootuid: '%s' rootgid: '%s' info: '%#v'", rootUID, rootGID, resp.Quota)) + return nil, errtypes.InternalError(fmt.Sprintf("Quota error from eos. info: '%#v'", resp.Quota)) } qi := new(eosclient.QuotaInfo) @@ -708,7 +685,7 @@ func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path // Let's loop on all the quotas that match this uid (apparently there can be many) // If there are many for this node, we sum them up for i := 0; i < len(resp.Quota.Quotanode); i++ { - log.Debug().Str("func", "GetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("quotanode:", fmt.Sprintf("%d: %#v", i, resp.Quota.Quotanode[i])).Msg("") + log.Debug().Str("func", "GetQuota").Str("quotanode:", fmt.Sprintf("%d: %#v", i, resp.Quota.Quotanode[i])).Msg("") mx := int64(resp.Quota.Quotanode[i].Maxlogicalbytes) - int64(resp.Quota.Quotanode[i].Usedbytes) if mx < 0 { @@ -730,16 +707,16 @@ func (c *Client) GetQuota(ctx context.Context, username, rootUID, rootGID, path } // SetQuota sets the quota of a user on the quota node defined by path -func (c *Client) SetQuota(ctx context.Context, rootUID, rootGID string, info *eosclient.SetQuotaInfo) error { +func (c *Client) SetQuota(ctx context.Context, rootAuth eosclient.Authorization, info *eosclient.SetQuotaInfo) error { { log := appctx.GetLogger(ctx) - log.Info().Str("func", "SetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("info:", fmt.Sprintf("%#v", info)).Msg("") + log.Info().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", info)).Msg("") // EOS does not have yet this command... work in progress, this is a draft piece of code // return errtypes.NotSupported("eosgrpc: SetQuota not implemented") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, rootUID, rootGID) + rq, err := c.initNSRequest(ctx, rootAuth) if err != nil { return err } @@ -769,24 +746,24 @@ func (c *Client) SetQuota(ctx context.Context, rootUID, rootGID string, info *eo } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for rootuid: '%s' rootgid: '%s' info: '%#v'", rootUID, rootGID, info)) + return errtypes.InternalError(fmt.Sprintf("nil response for info: '%#v'", info)) } if resp.GetError() != nil { - log.Error().Str("func", "SetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("info:", fmt.Sprintf("%#v", resp)).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") + log.Error().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", resp)).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp") } else { - log.Debug().Str("func", "SetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response") + log.Debug().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response") } if resp.Quota == nil { - return errtypes.InternalError(fmt.Sprintf("nil quota response? rootuid: '%s' rootgid: '%s' info: '%#v'", rootUID, rootGID, info)) + return errtypes.InternalError(fmt.Sprintf("nil quota response? info: '%#v'", info)) } if resp.Quota.Code != 0 { - return errtypes.InternalError(fmt.Sprintf("Quota error from eos. rootuid: '%s' rootgid: '%s' quota: '%#v'", rootUID, rootGID, resp.Quota)) + return errtypes.InternalError(fmt.Sprintf("Quota error from eos. quota: '%#v'", resp.Quota)) } - log.Debug().Str("func", "GetQuota").Str("rootuid,rootgid", rootUID+","+rootGID).Str("quotanodes", fmt.Sprintf("%d", len(resp.Quota.Quotanode))).Msg("grpc response") + log.Debug().Str("func", "GetQuota").Str("quotanodes", fmt.Sprintf("%d", len(resp.Quota.Quotanode))).Msg("grpc response") return err @@ -794,12 +771,12 @@ func (c *Client) SetQuota(ctx context.Context, rootUID, rootGID string, info *eo } // Touch creates a 0-size,0-replica file in the EOS namespace. -func (c *Client) Touch(ctx context.Context, uid, gid, path string) error { +func (c *Client) Touch(ctx context.Context, auth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Touch").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "Touch").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -820,7 +797,7 @@ func (c *Client) Touch(ctx context.Context, uid, gid, path string) error { } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", uid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path)) } log.Debug().Str("func", "Touch").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -830,23 +807,23 @@ func (c *Client) Touch(ctx context.Context, uid, gid, path string) error { } // Chown given path -func (c *Client) Chown(ctx context.Context, uid, gid, chownUID, chownGID, path string) error { +func (c *Client) Chown(ctx context.Context, auth, chownAuth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Chown").Str("uid,gid", uid+","+gid).Str("chownuid,chowngid", chownUID+","+chownGID).Str("path", path).Msg("") + log.Info().Str("func", "Chown").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("chownuid,chowngid", chownAuth.Role.UID+","+chownAuth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } msg := new(erpc.NSRequest_ChownRequest) msg.Owner = new(erpc.RoleId) - msg.Owner.Uid, err = strconv.ParseUint(chownUID, 10, 64) + msg.Owner.Uid, err = strconv.ParseUint(chownAuth.Role.UID, 10, 64) if err != nil { return err } - msg.Owner.Gid, err = strconv.ParseUint(chownGID, 10, 64) + msg.Owner.Gid, err = strconv.ParseUint(chownAuth.Role.GID, 10, 64) if err != nil { return err } @@ -865,22 +842,22 @@ func (c *Client) Chown(ctx context.Context, uid, gid, chownUID, chownGID, path s } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' chownuid: '%s' path: '%s'", uid, chownUID, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' chownuid: '%s' path: '%s'", auth.Role.UID, chownAuth.Role.UID, path)) } - log.Debug().Str("func", "Chown").Str("path", path).Str("uid,gid", uid+","+gid).Str("chownuid,chowngid", chownUID+","+chownGID).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") + log.Debug().Str("func", "Chown").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("chownuid,chowngid", chownAuth.Role.UID+","+chownAuth.Role.GID).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") return err } // Chmod given path -func (c *Client) Chmod(ctx context.Context, uid, gid, mode, path string) error { +func (c *Client) Chmod(ctx context.Context, auth eosclient.Authorization, mode, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Chmod").Str("uid,gid", uid+","+gid).Str("mode", mode).Str("path", path).Msg("") + log.Info().Str("func", "Chmod").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("mode", mode).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -907,7 +884,7 @@ func (c *Client) Chmod(ctx context.Context, uid, gid, mode, path string) error { } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' mode: '%s' path: '%s'", uid, mode, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' mode: '%s' path: '%s'", auth.Role.UID, mode, path)) } log.Debug().Str("func", "Chmod").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -917,12 +894,12 @@ func (c *Client) Chmod(ctx context.Context, uid, gid, mode, path string) error { } // CreateDir creates a directory at the given path -func (c *Client) CreateDir(ctx context.Context, uid, gid, path string) error { +func (c *Client) CreateDir(ctx context.Context, auth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Createdir").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "Createdir").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -950,7 +927,7 @@ func (c *Client) CreateDir(ctx context.Context, uid, gid, path string) error { } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", uid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path)) } log.Debug().Str("func", "Createdir").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -959,12 +936,12 @@ func (c *Client) CreateDir(ctx context.Context, uid, gid, path string) error { } -func (c *Client) rm(ctx context.Context, uid, gid, path string) error { +func (c *Client) rm(ctx context.Context, auth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "rm").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "rm").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -985,7 +962,7 @@ func (c *Client) rm(ctx context.Context, uid, gid, path string) error { } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", uid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path)) } log.Debug().Str("func", "rm").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -994,12 +971,12 @@ func (c *Client) rm(ctx context.Context, uid, gid, path string) error { } -func (c *Client) rmdir(ctx context.Context, uid, gid, path string) error { +func (c *Client) rmdir(ctx context.Context, auth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "rmdir").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "rmdir").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -1022,7 +999,7 @@ func (c *Client) rmdir(ctx context.Context, uid, gid, path string) error { } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", uid, path)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path)) } log.Debug().Str("func", "rmdir").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -1031,30 +1008,30 @@ func (c *Client) rmdir(ctx context.Context, uid, gid, path string) error { } // Remove removes the resource at the given path -func (c *Client) Remove(ctx context.Context, uid, gid, path string) error { +func (c *Client) Remove(ctx context.Context, auth eosclient.Authorization, path string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Remove").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "Remove").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") - nfo, err := c.GetFileInfoByPath(ctx, uid, gid, path) + nfo, err := c.GetFileInfoByPath(ctx, auth, path) if err != nil { log.Warn().Err(err).Str("func", "Remove").Str("path", path).Str("err", err.Error()) return err } if nfo.IsDir { - return c.rmdir(ctx, uid, gid, path) + return c.rmdir(ctx, auth, path) } - return c.rm(ctx, uid, gid, path) + return c.rm(ctx, auth, path) } // Rename renames the resource referenced by oldPath to newPath -func (c *Client) Rename(ctx context.Context, uid, gid, oldPath, newPath string) error { +func (c *Client) Rename(ctx context.Context, auth eosclient.Authorization, oldPath, newPath string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Rename").Str("uid,gid", uid+","+gid).Str("oldPath", oldPath).Str("newPath", newPath).Msg("") + log.Info().Str("func", "Rename").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("oldPath", oldPath).Str("newPath", newPath).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -1075,7 +1052,7 @@ func (c *Client) Rename(ctx context.Context, uid, gid, oldPath, newPath string) } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' oldpath: '%s' newpath: '%s'", uid, oldPath, newPath)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' oldpath: '%s' newpath: '%s'", auth.Role.UID, oldPath, newPath)) } log.Debug().Str("func", "Rename").Str("oldPath", oldPath).Str("newPath", newPath).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response") @@ -1085,9 +1062,9 @@ func (c *Client) Rename(ctx context.Context, uid, gid, oldPath, newPath string) } // List the contents of the directory given by path -func (c *Client) List(ctx context.Context, uid, gid, dpath string) ([]*eosclient.FileInfo, error) { +func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath string) ([]*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "List").Str("uid,gid", uid+","+gid).Str("dpath", dpath).Msg("") + log.Info().Str("func", "List").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("dpath", dpath).Msg("") // Stuff filename, uid, gid into the FindRequest type fdrq := new(erpc.FindRequest) @@ -1098,11 +1075,11 @@ func (c *Client) List(ctx context.Context, uid, gid, dpath string) ([]*eosclient fdrq.Role = new(erpc.RoleId) - uidInt, err := strconv.ParseUint(uid, 10, 64) + uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64) if err != nil { return nil, err } - gidInt, err := strconv.ParseUint(gid, 10, 64) + gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64) if err != nil { return nil, err } @@ -1171,9 +1148,9 @@ func (c *Client) List(ctx context.Context, uid, gid, dpath string) ([]*eosclient // itself, e.g. strange timeouts or TCP issues may be more difficult to trace // Let's consider this experimental for the moment, maybe I'll like to add a config // parameter to choose between the two behaviours -func (c *Client) Read(ctx context.Context, uid, gid, path string) (io.ReadCloser, error) { +func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path string) (io.ReadCloser, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Read").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "Read").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") var localTarget string var err error @@ -1185,17 +1162,17 @@ func (c *Client) Read(ctx context.Context, uid, gid, path string) (io.ReadCloser localTarget := fmt.Sprintf("%s/%s", c.opt.CacheDirectory, rand) defer os.RemoveAll(localTarget) - log.Info().Str("func", "Read").Str("uid,gid", uid+","+gid).Str("path", path).Str("tempfile", localTarget).Msg("") + log.Info().Str("func", "Read").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("tempfile", localTarget).Msg("") localfile, err = os.Create(localTarget) if err != nil { - log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", uid+","+gid).Str("err", err.Error()).Msg("") + log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("err", err.Error()).Msg("") return nil, errtypes.InternalError(fmt.Sprintf("can't open local temp file '%s'", localTarget)) } } - bodystream, err := c.GetHTTPCl().GETFile(ctx, c.httptransport, "", uid, gid, path, localfile) + bodystream, err := c.httpcl.GETFile(ctx, "", auth, path, localfile) if err != nil { - log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", uid+","+gid).Str("err", err.Error()).Msg("") + log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("err", err.Error()).Msg("") return nil, errtypes.InternalError(fmt.Sprintf("can't GET local cache file '%s'", localTarget)) } @@ -1205,9 +1182,9 @@ func (c *Client) Read(ctx context.Context, uid, gid, path string) (io.ReadCloser // Write writes a file to the mgm // Somehow the same considerations as Read apply -func (c *Client) Write(ctx context.Context, uid, gid, path string, stream io.ReadCloser) error { +func (c *Client) Write(ctx context.Context, auth eosclient.Authorization, path string, stream io.ReadCloser) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Write").Str("uid,gid", uid+","+gid).Str("path", path).Msg("") + log.Info().Str("func", "Write").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("") var length int64 length = -1 @@ -1219,7 +1196,7 @@ func (c *Client) Write(ctx context.Context, uid, gid, path string, stream io.Rea defer fd.Close() defer os.RemoveAll(fd.Name()) - log.Info().Str("func", "Write").Str("uid,gid", uid+","+gid).Str("path", path).Str("tempfile", fd.Name()).Msg("") + log.Info().Str("func", "Write").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("tempfile", fd.Name()).Msg("") // copy stream to local temp file length, err = io.Copy(fd, stream) if err != nil { @@ -1233,35 +1210,35 @@ func (c *Client) Write(ctx context.Context, uid, gid, path string, stream io.Rea defer wfd.Close() defer os.RemoveAll(fd.Name()) - return c.GetHTTPCl().PUTFile(ctx, c.httptransport, "", uid, gid, path, wfd, length) + return c.httpcl.PUTFile(ctx, "", auth, path, wfd, length) } - return c.GetHTTPCl().PUTFile(ctx, c.httptransport, "", uid, gid, path, stream, length) + return c.httpcl.PUTFile(ctx, "", auth, path, stream, length) - // return c.GetHttpCl().PUTFile(ctx, remoteuser, uid, gid, urlpathng, stream) + // return c.httpcl.PUTFile(ctx, remoteuser, auth, urlpathng, stream) // return c.WriteFile(ctx, uid, gid, path, fd.Name()) } // WriteFile writes an existing file to the mgm. Old xrdcp utility -func (c *Client) WriteFile(ctx context.Context, uid, gid, path, source string) error { +func (c *Client) WriteFile(ctx context.Context, auth eosclient.Authorization, path, source string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "WriteFile").Str("uid,gid", uid+","+gid).Str("path", path).Str("source", source).Msg("") + log.Info().Str("func", "WriteFile").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("source", source).Msg("") xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path) - cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, "--nopbar", "--silent", "-f", source, xrdPath, fmt.Sprintf("-ODeos.ruid=%s&eos.rgid=%s", uid, gid)) + cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, "--nopbar", "--silent", "-f", source, xrdPath, fmt.Sprintf("-ODeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) _, _, err := c.execute(ctx, cmd) return err } // ListDeletedEntries returns a list of the deleted entries. -func (c *Client) ListDeletedEntries(ctx context.Context, uid, gid string) ([]*eosclient.DeletedEntry, error) { +func (c *Client) ListDeletedEntries(ctx context.Context, auth eosclient.Authorization) ([]*eosclient.DeletedEntry, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "ListDeletedEntries").Str("uid,gid", uid+","+gid).Msg("") + log.Info().Str("func", "ListDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return nil, err } @@ -1280,7 +1257,7 @@ func (c *Client) ListDeletedEntries(ctx context.Context, uid, gid string) ([]*eo } if resp == nil { - return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s'", uid)) + return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s'", auth.Role.UID)) } if resp.GetError() != nil { @@ -1315,12 +1292,12 @@ func (c *Client) ListDeletedEntries(ctx context.Context, uid, gid string) ([]*eo } // RestoreDeletedEntry restores a deleted entry. -func (c *Client) RestoreDeletedEntry(ctx context.Context, uid, gid, key string) error { +func (c *Client) RestoreDeletedEntry(ctx context.Context, auth eosclient.Authorization, key string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "RestoreDeletedEntries").Str("uid,gid", uid+","+gid).Str("key", key).Msg("") + log.Info().Str("func", "RestoreDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("key", key).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -1341,7 +1318,7 @@ func (c *Client) RestoreDeletedEntry(ctx context.Context, uid, gid, key string) } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' key: '%s'", uid, key)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' key: '%s'", auth.Role.UID, key)) } if resp.GetError() != nil { @@ -1353,12 +1330,12 @@ func (c *Client) RestoreDeletedEntry(ctx context.Context, uid, gid, key string) } // PurgeDeletedEntries purges all entries from the recycle bin. -func (c *Client) PurgeDeletedEntries(ctx context.Context, uid, gid string) error { +func (c *Client) PurgeDeletedEntries(ctx context.Context, auth eosclient.Authorization) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "PurgeDeletedEntries").Str("uid,gid", uid+","+gid).Msg("") + log.Info().Str("func", "PurgeDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Msg("") // Initialize the common fields of the NSReq - rq, err := c.initNSRequest(ctx, uid, gid) + rq, err := c.initNSRequest(ctx, auth) if err != nil { return err } @@ -1377,7 +1354,7 @@ func (c *Client) PurgeDeletedEntries(ctx context.Context, uid, gid string) error } if resp == nil { - return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' ", uid)) + return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' ", auth.Role.UID)) } log.Info().Str("func", "PurgeDeletedEntries").Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("grpc response") @@ -1386,12 +1363,12 @@ func (c *Client) PurgeDeletedEntries(ctx context.Context, uid, gid string) error } // ListVersions list all the versions for a given file. -func (c *Client) ListVersions(ctx context.Context, uid, gid, p string) ([]*eosclient.FileInfo, error) { +func (c *Client) ListVersions(ctx context.Context, auth eosclient.Authorization, p string) ([]*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "ListVersions").Str("uid,gid", uid+","+gid).Str("p", p).Msg("") + log.Info().Str("func", "ListVersions").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("") versionFolder := getVersionFolder(p) - finfos, err := c.List(ctx, uid, gid, versionFolder) + finfos, err := c.List(ctx, auth, versionFolder) if err != nil { // we send back an empty list return []*eosclient.FileInfo{}, nil @@ -1400,7 +1377,7 @@ func (c *Client) ListVersions(ctx context.Context, uid, gid, p string) ([]*eoscl } // RollbackToVersion rollbacks a file to a previous version. -func (c *Client) RollbackToVersion(ctx context.Context, uid, gid, path, version string) error { +func (c *Client) RollbackToVersion(ctx context.Context, auth eosclient.Authorization, path, version string) error { // TODO(ffurano): /* cmd := exec.CommandContext(ctx, c.opt.EosBinary, "-r", uid, gid, "file", "versions", path, version) @@ -1411,25 +1388,25 @@ func (c *Client) RollbackToVersion(ctx context.Context, uid, gid, path, version } // ReadVersion reads the version for the given file. -func (c *Client) ReadVersion(ctx context.Context, uid, gid, p, version string) (io.ReadCloser, error) { +func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, p, version string) (io.ReadCloser, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "ReadVersion").Str("uid,gid", uid+","+gid).Str("p", p).Str("version", version).Msg("") + log.Info().Str("func", "ReadVersion").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Str("version", version).Msg("") versionFile := path.Join(getVersionFolder(p), version) - return c.Read(ctx, uid, gid, versionFile) + return c.Read(ctx, auth, versionFile) } -func (c *Client) getVersionFolderInode(ctx context.Context, uid, gid, p string) (uint64, error) { +func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "getVersionFolderInode").Str("uid,gid", uid+","+gid).Str("p", p).Msg("") + log.Info().Str("func", "getVersionFolderInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("") versionFolder := getVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, uid, gid, versionFolder) + md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) if err != nil { - if err = c.CreateDir(ctx, uid, gid, versionFolder); err != nil { + if err = c.CreateDir(ctx, auth, versionFolder); err != nil { return 0, err } - md, err = c.GetFileInfoByPath(ctx, uid, gid, versionFolder) + md, err = c.GetFileInfoByPath(ctx, auth, versionFolder) if err != nil { return 0, err } @@ -1437,12 +1414,12 @@ func (c *Client) getVersionFolderInode(ctx context.Context, uid, gid, p string) return md.Inode, nil } -func (c *Client) getFileInfoFromVersion(ctx context.Context, uid, gid, p string) (*eosclient.FileInfo, error) { +func (c *Client) getFileInfoFromVersion(ctx context.Context, auth eosclient.Authorization, p string) (*eosclient.FileInfo, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "getFileInfoFromVersion").Str("uid,gid", uid+","+gid).Str("p", p).Msg("") + log.Info().Str("func", "getFileInfoFromVersion").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("") file := getFileFromVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, uid, gid, file) + md, err := c.GetFileInfoByPath(ctx, auth, file) if err != nil { return nil, err } diff --git a/pkg/eosclient/eosgrpc/eos_http/eoshttp.go b/pkg/eosclient/eosgrpc/eoshttp.go similarity index 86% rename from pkg/eosclient/eosgrpc/eos_http/eoshttp.go rename to pkg/eosclient/eosgrpc/eoshttp.go index 6b1340e572..9720201467 100644 --- a/pkg/eosclient/eosgrpc/eos_http/eoshttp.go +++ b/pkg/eosclient/eosgrpc/eoshttp.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package eoshttp +package eosgrpc import ( "bytes" @@ -32,12 +32,13 @@ import ( "time" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/eosclient" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" ) -// Options to configure the Client. -type Options struct { +// HTTPOptions to configure the Client. +type HTTPOptions struct { // HTTP URL of the EOS MGM. // Default is https://eos-example.org @@ -79,7 +80,7 @@ type Options struct { } // Init fills the basic fields -func (opt *Options) Init() (*http.Transport, error) { +func (opt *HTTPOptions) init() { if opt.BaseURL == "" { opt.BaseURL = "https://eos-example.org" @@ -122,7 +123,28 @@ func (opt *Options) Init() (*http.Transport, error) { } else { os.Setenv("SSL_CERT_DIR", "/etc/grid-security/certificates") } +} + +// EOSHTTPClient performs HTTP-based tasks (e.g. upload, download) +// against a EOS management node (MGM) +// using the EOS XrdHTTP interface. +// In this module we wrap eos-related behaviour, e.g. headers or r/w retries +type EOSHTTPClient struct { + opt *HTTPOptions + cl *http.Client +} +// NewEOSHTTPClient creates a new client with the given options. +func NewEOSHTTPClient(opt *HTTPOptions) (*EOSHTTPClient, error) { + log := logger.New().With().Int("pid", os.Getpid()).Logger() + log.Debug().Str("func", "New").Str("Creating new eoshttp client. opt: ", "'"+fmt.Sprintf("%#v", opt)+"' ").Msg("") + + if opt == nil { + log.Debug().Str("opt is nil, Error creating http client ", "").Msg("") + return nil, errtypes.InternalError("HTTPOptions are nil") + } + + opt.init() cert, err := tls.LoadX509KeyPair(opt.ClientCertFile, opt.ClientKeyFile) if err != nil { return nil, err @@ -143,49 +165,17 @@ func (opt *Options) Init() (*http.Transport, error) { DisableCompression: true, } - return t, nil -} - -// Client performs HTTP-based tasks (e.g. upload, download) -// against a EOS management node (MGM) -// using the EOS XrdHTTP interface. -// In this module we wrap eos-related behaviour, e.g. headers or r/w retries -type Client struct { - opt Options - - cl *http.Client -} - -// New creates a new client with the given options. -func New(opt *Options, t *http.Transport) *Client { - log := logger.New().With().Int("pid", os.Getpid()).Logger() - log.Debug().Str("func", "New").Str("Creating new eoshttp client. opt: ", "'"+fmt.Sprintf("%#v", opt)+"' ").Msg("") - - if opt == nil { - log.Debug().Str("opt is nil, Error creating http client ", "").Msg("") - return nil - } - - c := new(Client) - c.opt = *opt - - // Let's be successful if the ping was ok. This is an initialization phase - // and we enforce the server to be up - log.Debug().Str("func", "newhttp").Str("Connecting to ", "'"+opt.BaseURL+"'").Msg("") - - c.cl = &http.Client{ - Transport: t} - - c.cl.CheckRedirect = func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - } - - if c.cl == nil { - log.Debug().Str("Error creating http client ", "").Msg("") - return nil + cl := &http.Client{ + Transport: t, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, } - return c + return &EOSHTTPClient{ + opt: opt, + cl: cl, + }, nil } // Format a human readable line that describes a response @@ -209,7 +199,7 @@ func rspdesc(rsp *http.Response) string { // If the error is not nil, take that // If there is an error coming from EOS, erturn a descriptive error -func (c *Client) getRespError(rsp *http.Response, err error) error { +func (c *EOSHTTPClient) getRespError(rsp *http.Response, err error) error { if err != nil { return err } @@ -232,7 +222,7 @@ func (c *Client) getRespError(rsp *http.Response, err error) error { } // From the basepath and the file path... build an url -func (c *Client) buildFullURL(urlpath, uid, gid string) (string, error) { +func (c *EOSHTTPClient) buildFullURL(urlpath string, auth eosclient.Authorization) (string, error) { u, err := url.Parse(c.opt.BaseURL) if err != nil { @@ -258,11 +248,11 @@ func (c *Client) buildFullURL(urlpath, uid, gid string) (string, error) { v := u.Query() - if len(uid) > 0 { - v.Set("eos.ruid", uid) + if len(auth.Role.UID) > 0 { + v.Set("eos.ruid", auth.Role.UID) } - if len(gid) > 0 { - v.Set("eos.rgid", gid) + if len(auth.Role.GID) > 0 { + v.Set("eos.rgid", auth.Role.GID) } u.RawQuery = v.Encode() @@ -270,13 +260,13 @@ func (c *Client) buildFullURL(urlpath, uid, gid string) (string, error) { } // GETFile does an entire GET to download a full file. Returns a stream to read the content from -func (c *Client) GETFile(ctx context.Context, httptransport *http.Transport, remoteuser, uid, gid, urlpath string, stream io.WriteCloser) (io.ReadCloser, error) { +func (c *EOSHTTPClient) GETFile(ctx context.Context, remoteuser string, auth eosclient.Authorization, urlpath string, stream io.WriteCloser) (io.ReadCloser, error) { log := appctx.GetLogger(ctx) - log.Info().Str("func", "GETFile").Str("remoteuser", remoteuser).Str("uid,gid", uid+","+gid).Str("path", urlpath).Msg("") + log.Info().Str("func", "GETFile").Str("remoteuser", remoteuser).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", urlpath).Msg("") // Now send the req and see what happens - finalurl, err := c.buildFullURL(urlpath, uid, gid) + finalurl, err := c.buildFullURL(urlpath, auth) if err != nil { log.Error().Str("func", "GETFile").Str("url", finalurl).Str("err", err.Error()).Msg("can't create request") return nil, err @@ -317,9 +307,6 @@ func (c *Client) GETFile(ctx context.Context, httptransport *http.Transport, rem return nil, err } - c.cl = &http.Client{ - Transport: httptransport} - req, err = http.NewRequestWithContext(ctx, "GET", loc.String(), nil) if err != nil { log.Error().Str("func", "GETFile").Str("url", loc.String()).Str("err", err.Error()).Msg("can't create redirected request") @@ -365,13 +352,13 @@ func (c *Client) GETFile(ctx context.Context, httptransport *http.Transport, rem } // PUTFile does an entire PUT to upload a full file, taking the data from a stream -func (c *Client) PUTFile(ctx context.Context, httptransport *http.Transport, remoteuser, uid, gid, urlpath string, stream io.ReadCloser, length int64) error { +func (c *EOSHTTPClient) PUTFile(ctx context.Context, remoteuser string, auth eosclient.Authorization, urlpath string, stream io.ReadCloser, length int64) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "PUTFile").Str("remoteuser", remoteuser).Str("uid,gid", uid+","+gid).Str("path", urlpath).Int64("length", length).Msg("") + log.Info().Str("func", "PUTFile").Str("remoteuser", remoteuser).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", urlpath).Int64("length", length).Msg("") // Now send the req and see what happens - finalurl, err := c.buildFullURL(urlpath, uid, gid) + finalurl, err := c.buildFullURL(urlpath, auth) if err != nil { log.Error().Str("func", "PUTFile").Str("url", finalurl).Str("err", err.Error()).Msg("can't create request") return err @@ -414,9 +401,6 @@ func (c *Client) PUTFile(ctx context.Context, httptransport *http.Transport, rem return err } - c.cl = &http.Client{ - Transport: httptransport} - req, err = http.NewRequestWithContext(ctx, "PUT", loc.String(), stream) if err != nil { log.Error().Str("func", "PUTFile").Str("url", loc.String()).Str("err", err.Error()).Msg("can't create redirected request") @@ -467,13 +451,13 @@ func (c *Client) PUTFile(ctx context.Context, httptransport *http.Transport, rem } // Head performs a HEAD req. Useful to check the server -func (c *Client) Head(ctx context.Context, remoteuser, uid, gid, urlpath string) error { +func (c *EOSHTTPClient) Head(ctx context.Context, remoteuser string, auth eosclient.Authorization, urlpath string) error { log := appctx.GetLogger(ctx) - log.Info().Str("func", "Head").Str("remoteuser", remoteuser).Str("uid,gid", uid+","+gid).Str("path", urlpath).Msg("") + log.Info().Str("func", "Head").Str("remoteuser", remoteuser).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", urlpath).Msg("") // Now send the req and see what happens - finalurl, err := c.buildFullURL(urlpath, uid, gid) + finalurl, err := c.buildFullURL(urlpath, auth) if err != nil { log.Error().Str("func", "Head").Str("url", finalurl).Str("err", err.Error()).Msg("can't create request") return err @@ -481,7 +465,7 @@ func (c *Client) Head(ctx context.Context, remoteuser, uid, gid, urlpath string) req, err := http.NewRequestWithContext(ctx, "HEAD", finalurl, nil) if err != nil { - log.Error().Str("func", "Head").Str("remoteuser", remoteuser).Str("uid,gid", uid+","+gid).Str("url", finalurl).Str("err", err.Error()).Msg("can't create request") + log.Error().Str("func", "Head").Str("remoteuser", remoteuser).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("url", finalurl).Str("err", err.Error()).Msg("can't create request") return err } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 3a4f306da5..dfc5a72606 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -39,6 +39,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/eosclient" "github.com/cs3org/reva/pkg/eosclient/eosbinary" + "github.com/cs3org/reva/pkg/eosclient/eosgrpc" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -149,54 +150,48 @@ func NewEOSFS(c *Config) (storage.FS, error) { } var eosClient eosclient.EOSClient - // if c.UseGRPC { - // eosClientOpts := &eosgrpc.Options{ - // XrdcopyBinary: c.XrdcopyBinary, - // URL: c.MasterURL, - // GrpcURI: c.GrpcURI, - // CacheDirectory: c.CacheDirectory, - // UseKeytab: c.UseKeytab, - // Keytab: c.Keytab, - // Authkey: c.GRPCAuthkey, - // SecProtocol: c.SecProtocol, - // VersionInvariant: c.VersionInvariant, - // ReadUsesLocalTemp: c.ReadUsesLocalTemp, - // WriteUsesLocalTemp: c.WriteUsesLocalTemp, - // MaxIdleConns: c.MaxIdleConns, - // MaxConnsPerHost: c.MaxConnsPerHost, - // MaxIdleConnsPerHost: c.MaxIdleConnsPerHost, - // IdleConnTimeout: c.IdleConnTimeout, - // } - // eosClient = eosgrpc.New(eosClientOpts) - // } else { - // eosClientOpts := &eosbinary.Options{ - // XrdcopyBinary: c.XrdcopyBinary, - // URL: c.MasterURL, - // EosBinary: c.EosBinary, - // CacheDirectory: c.CacheDirectory, - // ForceSingleUserMode: c.ForceSingleUserMode, - // SingleUsername: c.SingleUsername, - // UseKeytab: c.UseKeytab, - // Keytab: c.Keytab, - // SecProtocol: c.SecProtocol, - // VersionInvariant: c.VersionInvariant, - // } - // eosClient = eosbinary.New(eosClientOpts) - // } + var err error + if c.UseGRPC { + eosClientOpts := &eosgrpc.Options{ + XrdcopyBinary: c.XrdcopyBinary, + URL: c.MasterURL, + GrpcURI: c.GrpcURI, + CacheDirectory: c.CacheDirectory, + UseKeytab: c.UseKeytab, + Keytab: c.Keytab, + Authkey: c.GRPCAuthkey, + SecProtocol: c.SecProtocol, + VersionInvariant: c.VersionInvariant, + ReadUsesLocalTemp: c.ReadUsesLocalTemp, + WriteUsesLocalTemp: c.WriteUsesLocalTemp, + } + eosHTTPOpts := &eosgrpc.HTTPOptions{ + BaseURL: c.MasterURL, + MaxIdleConns: c.MaxIdleConns, + MaxConnsPerHost: c.MaxConnsPerHost, + MaxIdleConnsPerHost: c.MaxIdleConnsPerHost, + IdleConnTimeout: c.IdleConnTimeout, + } + eosClient, err = eosgrpc.New(eosClientOpts, eosHTTPOpts) + } else { + eosClientOpts := &eosbinary.Options{ + XrdcopyBinary: c.XrdcopyBinary, + URL: c.MasterURL, + EosBinary: c.EosBinary, + CacheDirectory: c.CacheDirectory, + ForceSingleUserMode: c.ForceSingleUserMode, + SingleUsername: c.SingleUsername, + UseKeytab: c.UseKeytab, + Keytab: c.Keytab, + SecProtocol: c.SecProtocol, + VersionInvariant: c.VersionInvariant, + } + eosClient, err = eosbinary.New(eosClientOpts) + } - eosClientOpts := &eosbinary.Options{ - XrdcopyBinary: c.XrdcopyBinary, - URL: c.MasterURL, - EosBinary: c.EosBinary, - CacheDirectory: c.CacheDirectory, - ForceSingleUserMode: c.ForceSingleUserMode, - SingleUsername: c.SingleUsername, - UseKeytab: c.UseKeytab, - Keytab: c.Keytab, - SecProtocol: c.SecProtocol, - VersionInvariant: c.VersionInvariant, + if err != nil { + return nil, errors.Wrap(err, "error initializing eosclient") } - eosClient = eosbinary.New(eosClientOpts) eosfs := &eosfs{ c: eosClient, @@ -214,7 +209,7 @@ func (fs *eosfs) userIDcacheWarmup() { if !fs.conf.EnableHome { ctx := context.Background() paths := []string{fs.wrap(ctx, "/")} - auth, _ := fs.getRootUIDAndGID(ctx) + auth, _ := fs.getRootAuth(ctx) for i := 0; i < fs.conf.UserIDCacheWarmupDepth; i++ { var newPaths []string @@ -364,7 +359,7 @@ func (fs *eosfs) getPath(ctx context.Context, u *userpb.User, id *provider.Resou return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId) } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return "", err } @@ -405,7 +400,7 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri return "", errors.Wrap(err, "eosfs: error parsing fileid string") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return "", err } @@ -424,7 +419,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return errors.Wrap(err, "eosfs: error getting uid and gid for user") } @@ -466,7 +461,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return errors.Wrap(err, "eosfs: error getting uid and gid for user") } @@ -517,12 +512,12 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi return err } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return err } @@ -601,12 +596,12 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr fn := fs.wrap(ctx, p) - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return err } @@ -634,7 +629,7 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr } fn := fs.wrap(ctx, p) - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -696,7 +691,7 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st fn := fs.wrap(ctx, p) - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -717,7 +712,7 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string fn := fs.wrapShadow(ctx, p) - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -765,7 +760,7 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -819,7 +814,7 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -856,7 +851,7 @@ func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*p if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -891,12 +886,12 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") } - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return 0, 0, err } @@ -939,7 +934,7 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil } @@ -970,11 +965,11 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { } home := fs.wrap(ctx, "/") - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1030,12 +1025,12 @@ func (fs *eosfs) CreateHome(ctx context.Context) error { } func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error { - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil } - chownAuth, err := fs.getUserUIDAndGID(ctx, u) + chownAuth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1096,7 +1091,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1128,7 +1123,7 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U // Current mechanism is: touch to hidden dir, set xattr, rename. dir, base := path.Split(fn) tmp := path.Join(dir, fmt.Sprintf(".sys.reva#.%s", base)) - rootAuth, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil } @@ -1165,7 +1160,7 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1195,7 +1190,7 @@ func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1213,7 +1208,7 @@ func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) e return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1243,7 +1238,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1271,7 +1266,7 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -1295,7 +1290,7 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -1330,7 +1325,7 @@ func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -1356,7 +1351,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1385,7 +1380,7 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1399,7 +1394,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*prov return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return nil, err } @@ -1430,7 +1425,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, r return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } @@ -1527,7 +1522,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return &provider.ResourcePermissions{ // no permissions @@ -1703,7 +1698,7 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User return getUserResp.User.Id, nil } -func (fs *eosfs) getUserUIDAndGID(ctx context.Context, u *userpb.User) (eosclient.Authorization, error) { +func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { return fs.singleUserAuth, nil @@ -1718,7 +1713,7 @@ func (fs *eosfs) getUserUIDAndGID(ctx context.Context, u *userpb.User) (eosclien return fs.extractUIDAndGID(u) } -func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (eosclient.Authorization, error) { +func (fs *eosfs) getRootAuth(ctx context.Context) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { return fs.singleUserAuth, nil diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 15847a8273..491483ee69 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -34,7 +34,7 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC if err != nil { return errors.Wrap(err, "eos: no user in ctx") } - auth, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u) if err != nil { return err } From da6abbbe6278e01fdf30f5b46f7e924951fe2532 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 8 Jul 2021 09:05:46 +0200 Subject: [PATCH 15/23] Optimize URL parsing and filter out app and federated accounts in rest driver --- internal/grpc/interceptors/auth/auth.go | 54 ++++++++++++++++--------- pkg/cbox/user/rest/rest.go | 26 +++++++----- pkg/eosclient/eosgrpc/eoshttp.go | 19 +++------ pkg/storage/utils/eosfs/eosfs.go | 9 +++-- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 7fc315c851..87b285e823 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -22,6 +22,7 @@ import ( "context" "strings" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -217,7 +218,6 @@ func (ss *wrappedServerStream) Context() context.Context { } func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string) (*userpb.User, error) { - log := appctx.GetLogger(ctx) u, tokenScope, err := mgr.DismantleToken(ctx, tkn) if err != nil { return nil, err @@ -232,35 +232,43 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. return u, nil } - // Check if req is of type *provider.Reference_Path - // If yes, the request might be coming from a share where the accessor is - // trying to impersonate the owner, since the share manager doesn't know the - // share path. + if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr); err != nil { + return nil, err + } + + return u, nil +} + +func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string) error { + log := appctx.GetLogger(ctx) + if ref, ok := extractRef(req); ok { + // Check if req is of type *provider.Reference_Path + // If yes, the request might be coming from a share where the accessor is + // trying to impersonate the owner, since the share manager doesn't know the + // share path. if ref.GetPath() != "" { - - // Try to extract the resource ID from the scope resource. log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) for k := range tokenScope { switch { case strings.HasPrefix(k, "publicshare"): var share link.PublicShare - err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) if err != nil { continue } if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { - return u, nil + return nil } case strings.HasPrefix(k, "share"): var share collaboration.Share - err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) if err != nil { continue } if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { - return u, nil + return nil } case strings.HasPrefix(k, "lightweight"): client, err := pool.GetGatewayServiceClient(gatewayAddr) @@ -274,15 +282,19 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } for _, share := range shares.Shares { if ok, err := checkResourcePath(ctx, ref, share.Share.ResourceId, gatewayAddr); err == nil && ok { - return u, nil + return nil } } } } - } else { // ref has ID present + } else { + // ref has ID present + // The request might be coming from a share created for a lightweight account + // after the token was minted. + log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId()) client, err := pool.GetGatewayServiceClient(gatewayAddr) if err != nil { - return nil, err + return err } for k := range tokenScope { if strings.HasPrefix(k, "lightweight") { @@ -293,16 +305,20 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } for _, share := range shares.Shares { if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { - return u, nil + return nil } } } } } } else if ref, ok := extractShareRef(req); ok { + // It's a share ref + // The request might be coming from a share created for a lightweight account + // after the token was minted. + log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref) client, err := pool.GetGatewayServiceClient(gatewayAddr) if err != nil { - return nil, err + return err } for k := range tokenScope { if strings.HasPrefix(k, "lightweight") { @@ -313,18 +329,18 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. } for _, s := range shares.Shares { if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId { - return u, nil + return nil } if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) && utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) { - return u, nil + return nil } } } } } - return nil, errtypes.PermissionDenied("access to resource not allowed within the assigned scope") + return errtypes.PermissionDenied("access to resource not allowed within the assigned scope") } func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 43ac8ef79c..0272e40c02 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -128,22 +128,26 @@ func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[st if err != nil { return nil, err } - if len(responseData) != 1 { - return nil, errors.New("rest: user not found: " + param + ":" + val) - } - userData, ok := responseData[0].(map[string]interface{}) - if !ok { - return nil, errors.New("rest: error in type assertion") + var users []map[string]interface{} + for _, usr := range responseData { + userData, ok := usr.(map[string]interface{}) + if !ok { + continue + } + + t, _ := userData["type"].(string) + userType := getUserType(t, userData["upn"].(string)) + if userType != userpb.UserType_USER_TYPE_APPLICATION && userType != userpb.UserType_USER_TYPE_FEDERATED { + users = append(users, userData) + } } - t, _ := userData["type"].(string) - userType := getUserType(t, userData["upn"].(string)) - if userType == userpb.UserType_USER_TYPE_APPLICATION || userType == userpb.UserType_USER_TYPE_FEDERATED { - return nil, errors.New("rest: federated and application accounts not supported") + if len(users) != 1 { + return nil, errors.New("rest: user not found: " + param + ":" + val) } - return userData, nil + return users[0], nil } func (m *manager) getInternalUserID(ctx context.Context, uid *userpb.UserId) (string, error) { diff --git a/pkg/eosclient/eosgrpc/eoshttp.go b/pkg/eosclient/eosgrpc/eoshttp.go index 9720201467..e7631b8951 100644 --- a/pkg/eosclient/eosgrpc/eoshttp.go +++ b/pkg/eosclient/eosgrpc/eoshttp.go @@ -28,7 +28,6 @@ import ( "net/url" "os" "strconv" - "strings" "time" "github.com/cs3org/reva/pkg/appctx" @@ -140,8 +139,8 @@ func NewEOSHTTPClient(opt *HTTPOptions) (*EOSHTTPClient, error) { log.Debug().Str("func", "New").Str("Creating new eoshttp client. opt: ", "'"+fmt.Sprintf("%#v", opt)+"' ").Msg("") if opt == nil { - log.Debug().Str("opt is nil, Error creating http client ", "").Msg("") - return nil, errtypes.InternalError("HTTPOptions are nil") + log.Debug().Str("opt is nil, error creating http client ", "").Msg("") + return nil, errtypes.InternalError("HTTPOptions is nil") } opt.init() @@ -234,20 +233,12 @@ func (c *EOSHTTPClient) buildFullURL(urlpath string, auth eosclient.Authorizatio return "", err } - // I feel safer putting here a check, to prohibit malicious users to - // inject a false uid/gid into the url - // Who knows, maybe it's redundant? Better more than nothing. - p1 := strings.Index(urlpath, "eos.ruid") - if p1 > 0 && (urlpath[p1-1] == '&' || urlpath[p1-1] == '?') { - return "", errtypes.PermissionDenied("Illegal malicious url " + urlpath) - } - p1 = strings.Index(urlpath, "eos.guid") - if p1 > 0 && (urlpath[p1-1] == '&' || urlpath[p1-1] == '?') { + // Prohibit malicious users from injecting a false uid/gid into the url + v := u.Query() + if v.Get("eos.ruid") != "" || v.Get("eos.rgid") != "" { return "", errtypes.PermissionDenied("Illegal malicious url " + urlpath) } - v := u.Query() - if len(auth.Role.UID) > 0 { v.Set("eos.ruid", auth.Role.UID) } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index dfc5a72606..e4dbf93089 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -964,15 +964,16 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { return errors.Wrap(err, "eosfs: no user in ctx") } + auth, err := fs.getUserAuth(ctx, u) + if err != nil { + return err + } + home := fs.wrap(ctx, "/") rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil } - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return err - } _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home) if err == nil { // home already exists From 726987226da30c68ab872ef7d4a528dab4a59861 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 8 Jul 2021 12:49:38 +0200 Subject: [PATCH 16/23] Initialize token generation --- pkg/eosclient/eosbinary/eosbinary.go | 15 +- pkg/eosclient/eosclient.go | 1 + pkg/eosclient/eosgrpc/eosgrpc.go | 8 +- pkg/storage/utils/eosfs/eosfs.go | 341 +++++++++++++-------------- pkg/storage/utils/eosfs/upload.go | 20 +- 5 files changed, 194 insertions(+), 191 deletions(-) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index beccff6431..bf5a487ea9 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -538,8 +538,9 @@ func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path st xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path) args := []string{"--nopbar", "--silent", "-f", xrdPath, localTarget} + if auth.Token != "" { - args[3] += "authz=" + auth.Token + args[3] += "?authz=" + auth.Token } else if auth.Role.UID != "" && auth.Role.GID != "" { args = append(args, fmt.Sprintf("-OSeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) } @@ -573,11 +574,13 @@ func (c *Client) Write(ctx context.Context, auth eosclient.Authorization, path s func (c *Client) WriteFile(ctx context.Context, auth eosclient.Authorization, path, source string) error { xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path) args := []string{"--nopbar", "--silent", "-f", source, xrdPath} + if auth.Token != "" { - args[4] += "authz=" + auth.Token + args[4] += "?authz=" + auth.Token } else if auth.Role.UID != "" && auth.Role.GID != "" { - args = append(args, fmt.Sprintf("-OSeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) + args = append(args, fmt.Sprintf("-ODeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID)) } + _, _, err := c.executeXRDCopy(ctx, args) return err } @@ -632,6 +635,12 @@ func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, return c.Read(ctx, auth, versionFile) } +func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization, p string, a *acl.Entry) (string, error) { + args := []string{"token", "--permission", a.Permissions, "--tree", "--path", path.Clean(p) + "/"} + stdout, _, err := c.executeEOS(ctx, args, auth) + return stdout, err +} + func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { versionFolder := getVersionFolder(p) md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) diff --git a/pkg/eosclient/eosclient.go b/pkg/eosclient/eosclient.go index cffe7d4911..8bd2038bc5 100644 --- a/pkg/eosclient/eosclient.go +++ b/pkg/eosclient/eosclient.go @@ -55,6 +55,7 @@ type EOSClient interface { ListVersions(ctx context.Context, auth Authorization, p string) ([]*FileInfo, error) RollbackToVersion(ctx context.Context, auth Authorization, path, version string) error ReadVersion(ctx context.Context, auth Authorization, p, version string) (io.ReadCloser, error) + GenerateToken(ctx context.Context, auth Authorization, path string, a *acl.Entry) (string, error) } // AttrType is the type of extended attribute, diff --git a/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index 0c0ae43d20..54c20d875c 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -187,13 +187,11 @@ func (c *Client) getRespError(rsp *erpc.NSResponse, err error) error { if err != nil { return err } - if rsp == nil || rsp.Error == nil || rsp.Error.Code == 0 { return nil } - err2 := errtypes.InternalError("Err from EOS: " + fmt.Sprintf("%#v", rsp.Error)) - return err2 + return errtypes.InternalError("Err from EOS: " + fmt.Sprintf("%#v", rsp.Error)) } // Common code to create and initialize a NSRequest @@ -1396,6 +1394,10 @@ func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, return c.Read(ctx, auth, versionFile) } +func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization, path string, a *acl.Entry) (string, error) { + return "", errtypes.NotSupported("TODO") +} + func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { log := appctx.GetLogger(ctx) log.Info().Str("func", "getVersionFolderInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("") diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index e4dbf93089..ae6e532b4d 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -336,9 +336,9 @@ func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (str } // resolve takes in a request path or request id and returns the unwrappedNominal path. -func (fs *eosfs) resolve(ctx context.Context, u *userpb.User, ref *provider.Reference) (string, error) { +func (fs *eosfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { if ref.ResourceId != nil { - p, err := fs.getPath(ctx, u, ref.ResourceId) + p, err := fs.getPath(ctx, ref.ResourceId) if err != nil { return "", err } @@ -353,13 +353,13 @@ func (fs *eosfs) resolve(ctx context.Context, u *userpb.User, ref *provider.Refe return "", fmt.Errorf("invalid reference %+v. at least resource_id or path must be set", ref) } -func (fs *eosfs) getPath(ctx context.Context, u *userpb.User, id *provider.ResourceId) (string, error) { +func (fs *eosfs) getPath(ctx context.Context, id *provider.ResourceId) (string, error) { fid, err := strconv.ParseUint(id.OpaqueId, 10, 64) if err != nil { return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId) } - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getRootAuth(ctx) if err != nil { return "", err } @@ -387,25 +387,36 @@ func (fs *eosfs) isShareFolderChild(ctx context.Context, p string) bool { } func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { - u, err := getUser(ctx) + fid, err := strconv.ParseUint(id.OpaqueId, 10, 64) if err != nil { - return "", errors.Wrap(err, "eosfs: no user in ctx") + return "", errors.Wrap(err, "eosfs: error parsing fileid string") } - // parts[0] = 868317, parts[1] = photos, ... - // FIXME REFERENCE ... umm ... 868317/photos? @ishank011 might be a leftover - parts := strings.Split(id.OpaqueId, "/") - fileID, err := strconv.ParseUint(parts[0], 10, 64) + u, err := getUser(ctx) if err != nil { - return "", errors.Wrap(err, "eosfs: error parsing fileid string") + return "", errors.Wrap(err, "eosfs: no user in ctx") + } + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + auth, err := fs.getRootAuth(ctx) + if err != nil { + return "", err + } + eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) + if err != nil { + return "", errors.Wrap(err, "eosfs: error getting file info by inode") + } + if perm := fs.permissionSet(ctx, eosFileInfo, nil); perm.GetPath { + return fs.unwrap(ctx, eosFileInfo.File) + } + return "", errtypes.PermissionDenied("eosfs: getting path for id not allowed") } - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return "", err } - eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fileID) + eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) if err != nil { return "", errors.Wrap(err, "eosfs: error getting file info by inode") } @@ -414,23 +425,23 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri } func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") + if len(md.Metadata) == 0 { + return errtypes.BadRequest("eosfs: no metadata set") } - auth, err := fs.getUserAuth(ctx, u) + p, err := fs.resolve(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return errors.Wrap(err, "eosfs: error resolving reference") } + fn := fs.wrap(ctx, p) - if len(md.Metadata) == 0 { - return errtypes.BadRequest("eosfs: no metadata set") + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") } - - p, err := fs.resolve(ctx, u, ref) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") + return errors.Wrap(err, "eosfs: error getting uid and gid for user") } for k, v := range md.Metadata { @@ -446,7 +457,7 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen // TODO(labkode): SetArbitraryMetadata does not has semantic for recursivity. // We set it to false - err := fs.c.SetAttr(ctx, auth, attr, false, p) + err := fs.c.SetAttr(ctx, auth, attr, false, fn) if err != nil { return errors.Wrap(err, "eosfs: error setting xattr in eos driver") } @@ -456,23 +467,23 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen } func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") + if len(keys) == 0 { + return errtypes.BadRequest("eosfs: no keys set") } - auth, err := fs.getUserAuth(ctx, u) + p, err := fs.resolve(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return errors.Wrap(err, "eosfs: error resolving reference") } + fn := fs.wrap(ctx, p) - if len(keys) == 0 { - return errtypes.BadRequest("eosfs: no keys set") + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") } - - p, err := fs.resolve(ctx, u, ref) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") + return errors.Wrap(err, "eosfs: error getting uid and gid for user") } for _, k := range keys { @@ -485,7 +496,7 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer Key: k, } - err := fs.c.UnsetAttr(ctx, auth, attr, p) + err := fs.c.UnsetAttr(ctx, auth, attr, fn) if err != nil { return errors.Wrap(err, "eosfs: error unsetting xattr in eos driver") } @@ -495,24 +506,22 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer } func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - fn := fs.wrap(ctx, p) - eosACL, err := fs.getEosACL(ctx, g) + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return err } - auth, err := fs.getUserAuth(ctx, u) + eosACL, err := fs.getEosACL(ctx, g) if err != nil { return err } @@ -562,11 +571,6 @@ func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, } func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - eosACLType, err := grants.GetACLType(g.Grantee.Type) if err != nil { return err @@ -589,14 +593,17 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr Type: eosACLType, } - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - fn := fs.wrap(ctx, p) - auth, err := fs.getUserAuth(ctx, u) + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return err } @@ -618,18 +625,17 @@ func (fs *eosfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *pr } func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { - u, err := getUser(ctx) - if err != nil { - return nil, err - } - - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } fn := fs.wrap(ctx, p) - auth, err := fs.getUserAuth(ctx, u) + u, err := getUser(ctx) + if err != nil { + return nil, err + } + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } @@ -669,15 +675,10 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr } func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) { - u, err := getUser(ctx) - if err != nil { - return nil, err - } - log := appctx.GetLogger(ctx) log.Info().Msg("eosfs: get md for ref:" + ref.String()) - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } @@ -691,7 +692,11 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st fn := fs.wrap(ctx, p) - auth, err := fs.getUserAuth(ctx, u) + u, err := getUser(ctx) + if err != nil { + return nil, err + } + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } @@ -705,14 +710,13 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st } func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) { + fn := fs.wrapShadow(ctx, p) + u, err := getUser(ctx) if err != nil { return nil, err } - - fn := fs.wrapShadow(ctx, p) - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } @@ -731,12 +735,8 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { log := appctx.GetLogger(ctx) - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } @@ -754,19 +754,17 @@ func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) { log := appctx.GetLogger(ctx) + fn := fs.wrap(ctx, p) u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } - fn := fs.wrap(ctx, p) - eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") @@ -809,20 +807,19 @@ func (fs *eosfs) listWithHome(ctx context.Context, home, p string) ([]*provider. } func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.ResourceInfo, error) { + fns := []string{fs.wrap(ctx, home), fs.wrapShadow(ctx, home)} + u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return nil, err - } - - fns := []string{fs.wrap(ctx, home), fs.wrapShadow(ctx, home)} - finfos := []*provider.ResourceInfo{} for _, fn := range fns { + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return nil, err + } eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") @@ -847,17 +844,17 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc } func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) { + fn := fs.wrapShadow(ctx, p) + u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } - fn := fs.wrapShadow(ctx, p) - eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") @@ -885,8 +882,7 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { if err != nil { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") } @@ -959,17 +955,17 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error { } func (fs *eosfs) createNominalHome(ctx context.Context) error { + home := fs.wrap(ctx, "/") + u, err := getUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, home) if err != nil { return err } - home := fs.wrap(ctx, "/") rootAuth, err := fs.getRootAuth(ctx) if err != nil { return nil @@ -1031,7 +1027,7 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, return nil } - chownAuth, err := fs.getUserAuth(ctx, u) + chownAuth, err := fs.getUserAuth(ctx, u, path) if err != nil { return err } @@ -1091,8 +1087,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, p) if err != nil { return err } @@ -1156,17 +1151,7 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U } func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return err - } - - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } @@ -1177,6 +1162,15 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { fn := fs.wrap(ctx, p) + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return err + } + return fs.c.Remove(ctx, auth, fn) } @@ -1191,12 +1185,13 @@ func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u) + fn := fs.wrapShadow(ctx, p) + + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return err } - fn := fs.wrapShadow(ctx, p) return fs.c.Remove(ctx, auth, fn) } @@ -1204,22 +1199,12 @@ func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { } func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return err - } - - oldPath, err := fs.resolve(ctx, u, oldRef) + oldPath, err := fs.resolve(ctx, oldRef) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } - newPath, err := fs.resolve(ctx, u, newRef) + newPath, err := fs.resolve(ctx, newRef) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } @@ -1230,20 +1215,20 @@ func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) e oldFn := fs.wrap(ctx, oldPath) newFn := fs.wrap(ctx, newPath) - return fs.c.Rename(ctx, auth, oldFn, newFn) -} -func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error { u, err := getUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, oldFn) if err != nil { return err } + return fs.c.Rename(ctx, auth, oldFn, newFn) +} + +func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error { if fs.isShareFolderRoot(ctx, oldPath) || fs.isShareFolderRoot(ctx, newPath) { return errtypes.PermissionDenied("eosfs: cannot move/rename the virtual share folder") } @@ -1258,21 +1243,21 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error oldfn := fs.wrapShadow(ctx, oldPath) newfn := fs.wrapShadow(ctx, newPath) - return fs.c.Rename(ctx, auth, oldfn, newfn) -} -func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { u, err := getUser(ctx) if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") + return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, oldfn) if err != nil { - return nil, err + return err } - p, err := fs.resolve(ctx, u, ref) + return fs.c.Rename(ctx, auth, oldfn, newfn) +} + +func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } @@ -1282,21 +1267,21 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read } fn := fs.wrap(ctx, p) - return fs.c.Read(ctx, auth, fn) -} -func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } - p, err := fs.resolve(ctx, u, ref) + return fs.c.Read(ctx, auth, fn) +} + +func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } @@ -1307,6 +1292,15 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] fn := fs.wrap(ctx, p) + u, err := getUser(ctx) + if err != nil { + return nil, errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return nil, err + } + eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing versions") @@ -1321,17 +1315,7 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] } func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return nil, err - } - - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") } @@ -1342,22 +1326,20 @@ func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, fn := fs.wrap(ctx, p) - fn = fs.wrap(ctx, fn) - return fs.c.ReadVersion(ctx, auth, fn, revisionKey) -} - -func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { u, err := getUser(ctx) if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") + return nil, errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { - return err + return nil, err } - p, err := fs.resolve(ctx, u, ref) + return fs.c.ReadVersion(ctx, auth, fn, revisionKey) +} + +func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eosfs: error resolving reference") } @@ -1368,6 +1350,15 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r fn := fs.wrap(ctx, p) + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return err + } + return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey) } @@ -1380,8 +1371,7 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1394,8 +1384,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*prov if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return nil, err } @@ -1425,8 +1414,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, r if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1493,13 +1481,13 @@ func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eoscli // permissionSet returns the permission set for the current user func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileInfo, owner *userpb.UserId) *provider.ResourcePermissions { u, ok := user.ContextGetUser(ctx) - if !ok || owner == nil || u.Id == nil { + if !ok || u.Id == nil { return &provider.ResourcePermissions{ // no permissions } } - if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { + if owner != nil && u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { return &provider.ResourcePermissions{ // owner has all permissions AddGrant: true, @@ -1523,7 +1511,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - auth, err := fs.getUserAuth(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return &provider.ResourcePermissions{ // no permissions @@ -1699,7 +1687,7 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User return getUserResp.User.Id, nil } -func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User) (eosclient.Authorization, error) { +func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, path string) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { return fs.singleUserAuth, nil @@ -1708,9 +1696,12 @@ func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User) (eosclient.Aut fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) return fs.singleUserAuth, err } - // if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { - // - // } + + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + // return fs.c.GenerateToken(ctx, auth, path, acl) + return eosclient.Authorization{Token: "temp-token"}, nil + } + return fs.extractUIDAndGID(u) } diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 491483ee69..9d20e01194 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -30,16 +30,7 @@ import ( ) func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eos: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u) - if err != nil { - return err - } - - p, err := fs.resolve(ctx, u, ref) + p, err := fs.resolve(ctx, ref) if err != nil { return errors.Wrap(err, "eos: error resolving reference") } @@ -71,6 +62,15 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC } fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eos: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return err + } return fs.c.Write(ctx, auth, fn, r) } From 0a805ad77f198c96309cca8e58160d726d49f118 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 8 Jul 2021 16:25:34 +0200 Subject: [PATCH 17/23] Add lightweight account ACLs --- pkg/eosclient/eosbinary/eosbinary.go | 71 ++++++++++++++++- pkg/storage/utils/acl/acl.go | 8 +- pkg/storage/utils/eosfs/eosfs.go | 115 ++++++++++++++++++++------- 3 files changed, 160 insertions(+), 34 deletions(-) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index bf5a487ea9..0449e182a6 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -42,7 +42,8 @@ import ( ) const ( - versionPrefix = ".sys.v#." + versionPrefix = ".sys.v#." + lwShareAttrKey = "reva.lwshare" ) const ( @@ -276,6 +277,34 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat if err != nil { return err } + + if a.Type == acl.TypeLightweight { + sysACL := "" + aclStr, ok := finfo.Attrs[lwShareAttrKey] + if ok { + acls, err := acl.Parse(aclStr, acl.ShortTextForm) + if err != nil { + return err + } + err = acls.SetEntry(a.Type, a.Qualifier, a.Permissions) + if err != nil { + return err + } + sysACL = acls.Serialize() + } else { + sysACL = a.CitrineSerialize() + } + sysACLAttr := &eosclient.Attribute{ + Type: SystemAttr, + Key: lwShareAttrKey, + Val: sysACL, + } + if err = c.SetAttr(ctx, auth, sysACLAttr, true, path); err != nil { + return err + } + return nil + } + sysACL := a.CitrineSerialize() args := []string{"acl"} @@ -306,6 +335,33 @@ func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authori return err } + if a.Type == acl.TypeLightweight { + sysACL := "" + aclStr, ok := finfo.Attrs[lwShareAttrKey] + if ok { + acls, err := acl.Parse(aclStr, acl.ShortTextForm) + if err != nil { + return err + } + acls.DeleteEntry(a.Type, a.Qualifier) + if err != nil { + return err + } + sysACL = acls.Serialize() + } else { + sysACL = a.CitrineSerialize() + } + sysACLAttr := &eosclient.Attribute{ + Type: SystemAttr, + Key: lwShareAttrKey, + Val: sysACL, + } + if err = c.SetAttr(ctx, auth, sysACLAttr, true, path); err != nil { + return err + } + return nil + } + sysACL := a.CitrineSerialize() args := []string{"acl"} if finfo.IsDir { @@ -951,6 +1007,19 @@ func (c *Client) mapToFileInfo(kv map[string]string) (*eosclient.FileInfo, error if err != nil { return nil, err } + lwACLStr, ok := kv[lwShareAttrKey] + if ok { + lwAcls, err := acl.Parse(lwACLStr, acl.ShortTextForm) + if err != nil { + return nil, err + } + for _, e := range lwAcls.Entries { + err = sysACL.SetEntry(e.Type, e.Qualifier, e.Permissions) + if err != nil { + return nil, err + } + } + } fi := &eosclient.FileInfo{ File: kv["file"], diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/utils/acl/acl.go index a24c6fa17a..1c493c7224 100644 --- a/pkg/storage/utils/acl/acl.go +++ b/pkg/storage/utils/acl/acl.go @@ -42,6 +42,8 @@ const ( // TypeUser indicates the qualifier identifies a user TypeUser = "u" + // TypeLightweight indicates the qualifier identifies a lightweight user + TypeLightweight = "lw" // TypeGroup indicates the qualifier identifies a group TypeGroup = "egroup" ) @@ -77,7 +79,7 @@ func isComment(line string) bool { func (m *ACLs) Serialize() string { sysACL := []string{} for _, e := range m.Entries { - sysACL = append(sysACL, e.serialize()) + sysACL = append(sysACL, e.CitrineSerialize()) } return strings.Join(sysACL, ShortTextForm) } @@ -135,7 +137,3 @@ func ParseEntry(singleSysACL string) (*Entry, error) { func (a *Entry) CitrineSerialize() string { return fmt.Sprintf("%s:%s=%s", a.Type, a.Qualifier, a.Permissions) } - -func (a *Entry) serialize() string { - return strings.Join([]string{a.Type, a.Qualifier, a.Permissions}, ":") -} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index ae6e532b4d..d5601b1c10 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -551,13 +551,19 @@ func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, var qualifier string if t == acl.TypeUser { - // since EOS Citrine ACLs are stored with uid, we need to convert username to - // uid only for users. - auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) - if err != nil { - return nil, err + // if the grantee is a lightweight account, we need to set it accordingly + if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + t = acl.TypeLightweight + qualifier = g.Grantee.GetUserId().OpaqueId + } else { + // since EOS Citrine ACLs are stored with uid, we need to convert username to + // uid only for users. + auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) + if err != nil { + return nil, err + } + qualifier = auth.Role.UID } - qualifier = auth.Role.UID } else { qualifier = g.Grantee.GetGroupId().OpaqueId } @@ -578,12 +584,18 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr var recipient string if eosACLType == acl.TypeUser { - // since EOS Citrine ACLs are stored with uid, we need to convert username to uid - auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) - if err != nil { - return err + // if the grantee is a lightweight account, we need to set it accordingly + if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + eosACLType = acl.TypeLightweight + recipient = g.Grantee.GetUserId().OpaqueId + } else { + // since EOS Citrine ACLs are stored with uid, we need to convert username to uid + auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) + if err != nil { + return err + } + recipient = auth.Role.UID } - recipient = auth.Role.UID } else { recipient = g.Grantee.GetGroupId().OpaqueId } @@ -648,7 +660,8 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr grantList := []*provider.Grant{} for _, a := range acls { var grantee *provider.Grantee - if a.Type == acl.TypeUser { + switch { + case a.Type == acl.TypeUser: // EOS Citrine ACLs are stored with uid for users. // This needs to be resolved to the user opaque ID. qualifier, err := fs.getUserIDGateway(ctx, a.Qualifier) @@ -659,12 +672,19 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr Id: &provider.Grantee_UserId{UserId: qualifier}, Type: grants.GetGranteeType(a.Type), } - } else { + case a.Type == acl.TypeLightweight: + a.Type = acl.TypeUser + grantee = &provider.Grantee{ + Id: &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: a.Qualifier}}, + Type: grants.GetGranteeType(a.Type), + } + default: grantee = &provider.Grantee{ Id: &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: a.Qualifier}}, Type: grants.GetGranteeType(a.Type), } } + grantList = append(grantList, &provider.Grant{ Grantee: grantee, Permissions: grants.GetGrantPermissionSet(a.Permissions, true), @@ -716,7 +736,9 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string if err != nil { return nil, err } - auth, err := fs.getUserAuth(ctx, u, fn) + + // lightweight accounts don't have share folders, so we're passing an empty string as path + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return nil, err } @@ -813,13 +835,14 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } + // lightweight accounts don't have home folders, so we're passing an empty string as path + auth, err := fs.getUserAuth(ctx, u, "") + if err != nil { + return nil, err + } finfos := []*provider.ResourceInfo{} for _, fn := range fns { - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return nil, err - } eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") @@ -850,7 +873,8 @@ func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*p if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, fn) + // lightweight accounts don't have share folders, so we're passing an empty string as path + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return nil, err } @@ -882,6 +906,7 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { if err != nil { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } + // lightweight accounts don't quota nodes, so we're passing an empty string as path auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") @@ -961,7 +986,7 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, home) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1027,7 +1052,7 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, return nil } - chownAuth, err := fs.getUserAuth(ctx, u, path) + chownAuth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1187,7 +1212,7 @@ func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { fn := fs.wrapShadow(ctx, p) - auth, err := fs.getUserAuth(ctx, u, fn) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1248,7 +1273,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, oldfn) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return err } @@ -1511,7 +1536,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - auth, err := fs.getUserAuth(ctx, u, "") + auth, err := fs.getUserAuth(ctx, u, eosFileInfo.File) if err != nil { return &provider.ResourcePermissions{ // no permissions @@ -1530,7 +1555,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - if (e.Type == acl.TypeUser && e.Qualifier == auth.Role.UID) || userInGroup { + if (e.Type == acl.TypeUser && e.Qualifier == auth.Role.UID) || (e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId) || userInGroup { mergePermissions(&perm, grants.GetGrantPermissionSet(e.Permissions, eosFileInfo.IsDir)) } } @@ -1687,7 +1712,7 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User return getUserResp.User.Id, nil } -func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, path string) (eosclient.Authorization, error) { +func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { return fs.singleUserAuth, nil @@ -1698,13 +1723,47 @@ func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, path string) ( } if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { - // return fs.c.GenerateToken(ctx, auth, path, acl) - return eosclient.Authorization{Token: "temp-token"}, nil + return fs.getEOSToken(ctx, u, fn) } return fs.extractUIDAndGID(u) } +func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) { + if fn == "" { + return eosclient.Authorization{}, errtypes.BadRequest("eosfs: path cannot be empty") + } + + rootAuth, err := fs.getRootAuth(ctx) + if err != nil { + return eosclient.Authorization{}, err + } + info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn) + if err != nil { + return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting file info by path") + } + auth := eosclient.Authorization{ + Role: eosclient.Role{ + UID: strconv.FormatUint(info.UID, 10), + GID: strconv.FormatUint(info.GID, 10), + }, + } + + var a *acl.Entry + for _, e := range info.SysACL.Entries { + if e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId { + a = e + break + } + } + + tkn, err := fs.c.GenerateToken(ctx, auth, fn, a) + if err != nil { + return eosclient.Authorization{}, err + } + return eosclient.Authorization{Token: tkn}, nil +} + func (fs *eosfs) getRootAuth(ctx context.Context) (eosclient.Authorization, error) { if fs.conf.ForceSingleUserMode { if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { From f8dd0e73737542c88213293f5c79f661a28a7099 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 8 Jul 2021 17:11:01 +0200 Subject: [PATCH 18/23] Add token cache --- pkg/eosclient/eosbinary/eosbinary.go | 9 ++++++++- pkg/eosclient/eosgrpc/eosgrpc.go | 1 + pkg/storage/utils/eosfs/config.go | 4 ++++ pkg/storage/utils/eosfs/eosfs.go | 20 ++++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index 0449e182a6..34286da4dd 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -31,6 +31,7 @@ import ( "strconv" "strings" "syscall" + "time" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/eosclient" @@ -116,6 +117,10 @@ type Options struct { // SecProtocol is the comma separated list of security protocols used by xrootd. // For example: "sss, unix" SecProtocol string + + // TokenExpiry stores in seconds the time after which generated tokens will expire + // Default is 3600 + TokenExpiry int } func (opt *Options) init() { @@ -691,8 +696,10 @@ func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, return c.Read(ctx, auth, versionFile) } +// GenerateToken returns a token on behalf of the resource owner to be used by lightweight accounts func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization, p string, a *acl.Entry) (string, error) { - args := []string{"token", "--permission", a.Permissions, "--tree", "--path", path.Clean(p) + "/"} + expiration := strconv.FormatInt(time.Now().Add(time.Duration(c.opt.TokenExpiry)*time.Second).Unix(), 10) + args := []string{"token", "--permission", a.Permissions, "--tree", "--path", path.Clean(p) + "/", "--expires", expiration} stdout, _, err := c.executeEOS(ctx, args, auth) return stdout, err } diff --git a/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index 54c20d875c..c002b6294a 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -1394,6 +1394,7 @@ func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, return c.Read(ctx, auth, versionFile) } +// GenerateToken returns a token on behalf of the resource owner to be used by lightweight accounts func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization, path string, a *acl.Entry) (string, error) { return "", errtypes.NotSupported("TODO") } diff --git a/pkg/storage/utils/eosfs/config.go b/pkg/storage/utils/eosfs/config.go index 743bf90f40..d86714e924 100644 --- a/pkg/storage/utils/eosfs/config.go +++ b/pkg/storage/utils/eosfs/config.go @@ -145,4 +145,8 @@ type Config struct { // HTTP connections to EOS: idle conections TTL IdleConnTimeout int `mapstructure:"idle_conn_timeout"` + + // TokenExpiry stores in seconds the time after which generated tokens will expire + // Default is 3600 + TokenExpiry int } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index d5601b1c10..a35573ad27 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -29,6 +29,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/bluele/gcache" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" @@ -126,6 +127,10 @@ func (c *Config) init() { c.UserIDCacheWarmupDepth = 2 } + if c.TokenExpiry == 0 { + c.TokenExpiry = 3600 + } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) } @@ -135,6 +140,7 @@ type eosfs struct { chunkHandler *chunking.ChunkHandler singleUserAuth eosclient.Authorization userIDCache gcache.Cache + tokenCache gcache.Cache } // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance @@ -198,6 +204,7 @@ func NewEOSFS(c *Config) (storage.FS, error) { conf: c, chunkHandler: chunking.NewChunkHandler(c.CacheDirectory), userIDCache: gcache.New(c.UserIDCacheSize).LFU().Build(), + tokenCache: gcache.New(c.UserIDCacheSize).LFU().Build(), } go eosfs.userIDcacheWarmup() @@ -1757,10 +1764,23 @@ func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eo } } + p := path.Clean(fn) + for p != "." && p != fs.conf.Namespace { + key := p + "!" + a.Permissions + if tknIf, err := fs.tokenCache.Get(key); err == nil { + return eosclient.Authorization{Token: tknIf.(string)}, nil + } + p = path.Dir(p) + } + tkn, err := fs.c.GenerateToken(ctx, auth, fn, a) if err != nil { return eosclient.Authorization{}, err } + + key := path.Clean(fn) + "!" + a.Permissions + _ = fs.tokenCache.SetWithExpire(key, tkn, time.Second*time.Duration(fs.conf.TokenExpiry)) + return eosclient.Authorization{Token: tkn}, nil } From 362d40f5c17eabfd2bbf5b16cb72dcf76ddd3b51 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 8 Jul 2021 17:37:33 +0200 Subject: [PATCH 19/23] Update changelog --- .../unreleased/{lw-user-types.md => lw-user-support.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename changelog/unreleased/{lw-user-types.md => lw-user-support.md} (52%) diff --git a/changelog/unreleased/lw-user-types.md b/changelog/unreleased/lw-user-support.md similarity index 52% rename from changelog/unreleased/lw-user-types.md rename to changelog/unreleased/lw-user-support.md index 9921f95b78..a81ba938ad 100644 --- a/changelog/unreleased/lw-user-types.md +++ b/changelog/unreleased/lw-user-support.md @@ -1,9 +1,9 @@ Enhancement: Add support for lightweight user types This PR adds support for assigning and consuming user type when setting/reading -users. These changes are further required to enable setting varying access -scopes for different types of users, such as lightweight accounts which can only -access resources shared with them. +users. On top of that, support for lightweight users is added. These users have +to be restricted to accessing only shares received by them, which is +accomplished by expanding the existing RBAC scope. https://github.com/cs3org/reva/pull/1744 https://github.com/cs3org/cs3apis/pull/120 From adf2cab0b0be803bea5123c2305fc03c8a08e9f2 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 9 Jul 2021 10:34:30 +0200 Subject: [PATCH 20/23] Refactor auth interceptor --- internal/grpc/interceptors/auth/auth.go | 173 -------------------- internal/grpc/interceptors/auth/scope.go | 200 +++++++++++++++++++++++ 2 files changed, 200 insertions(+), 173 deletions(-) create mode 100644 internal/grpc/interceptors/auth/scope.go diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 87b285e823..8151013c81 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -20,20 +20,11 @@ package auth import ( "context" - "strings" - authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/auth/scope" "github.com/cs3org/reva/pkg/errtypes" - statuspkg "github.com/cs3org/reva/pkg/rgrpc/status" - "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/token" tokenmgr "github.com/cs3org/reva/pkg/token/manager/registry" @@ -238,167 +229,3 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. return u, nil } - -func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string) error { - log := appctx.GetLogger(ctx) - - if ref, ok := extractRef(req); ok { - // Check if req is of type *provider.Reference_Path - // If yes, the request might be coming from a share where the accessor is - // trying to impersonate the owner, since the share manager doesn't know the - // share path. - if ref.GetPath() != "" { - log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) - for k := range tokenScope { - switch { - case strings.HasPrefix(k, "publicshare"): - var share link.PublicShare - err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) - if err != nil { - continue - } - if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { - return nil - } - - case strings.HasPrefix(k, "share"): - var share collaboration.Share - err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) - if err != nil { - continue - } - if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { - return nil - } - case strings.HasPrefix(k, "lightweight"): - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - continue - } - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil || shares.Status.Code != rpc.Code_CODE_OK { - log.Warn().Err(err).Msg("error listing received shares") - continue - } - for _, share := range shares.Shares { - if ok, err := checkResourcePath(ctx, ref, share.Share.ResourceId, gatewayAddr); err == nil && ok { - return nil - } - } - } - } - } else { - // ref has ID present - // The request might be coming from a share created for a lightweight account - // after the token was minted. - log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId()) - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return err - } - for k := range tokenScope { - if strings.HasPrefix(k, "lightweight") { - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil || shares.Status.Code != rpc.Code_CODE_OK { - log.Warn().Err(err).Msg("error listing received shares") - continue - } - for _, share := range shares.Shares { - if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { - return nil - } - } - } - } - } - } else if ref, ok := extractShareRef(req); ok { - // It's a share ref - // The request might be coming from a share created for a lightweight account - // after the token was minted. - log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref) - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return err - } - for k := range tokenScope { - if strings.HasPrefix(k, "lightweight") { - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil || shares.Status.Code != rpc.Code_CODE_OK { - log.Warn().Err(err).Msg("error listing received shares") - continue - } - for _, s := range shares.Shares { - if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId { - return nil - } - if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) && - utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) { - return nil - } - } - } - } - } - - return errtypes.PermissionDenied("access to resource not allowed within the assigned scope") -} - -func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return false, err - } - - // Since the resource ID is obtained from the scope, the current token - // has access to it. - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ResourceId: r}, - } - - statResponse, err := client.Stat(ctx, statReq) - if err != nil { - return false, err - } - if statResponse.Status.Code != rpc.Code_CODE_OK { - return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor") - } - - if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { - // The path corresponds to the resource to which the token has access. - // We allow access to it. - return true, nil - } - return false, nil -} - -func extractRef(req interface{}) (*provider.Reference, bool) { - switch v := req.(type) { - case *registry.GetStorageProvidersRequest: - return v.GetRef(), true - case *provider.StatRequest: - return v.GetRef(), true - case *provider.ListContainerRequest: - return v.GetRef(), true - case *provider.CreateContainerRequest: - return v.GetRef(), true - case *provider.DeleteRequest: - return v.GetRef(), true - case *provider.MoveRequest: - return v.GetSource(), true - case *provider.InitiateFileDownloadRequest: - return v.GetRef(), true - case *provider.InitiateFileUploadRequest: - return v.GetRef(), true - } - return nil, false -} - -func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) { - switch v := req.(type) { - case *collaboration.GetReceivedShareRequest: - return v.GetRef(), true - case *collaboration.UpdateReceivedShareRequest: - return v.GetRef(), true - } - return nil, false -} diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go new file mode 100644 index 0000000000..c938358b1b --- /dev/null +++ b/internal/grpc/interceptors/auth/scope.go @@ -0,0 +1,200 @@ +// 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 auth + +import ( + "context" + "strings" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + statuspkg "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/utils" +) + +func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string) error { + log := appctx.GetLogger(ctx) + + if ref, ok := extractRef(req); ok { + // Check if req is of type *provider.Reference_Path + // If yes, the request might be coming from a share where the accessor is + // trying to impersonate the owner, since the share manager doesn't know the + // share path. + if ref.GetPath() != "" { + log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) + for k := range tokenScope { + switch { + case strings.HasPrefix(k, "publicshare"): + var share link.PublicShare + err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return nil + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return nil + } + case strings.HasPrefix(k, "lightweight"): + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + continue + } + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue + } + for _, share := range shares.Shares { + if ok, err := checkResourcePath(ctx, ref, share.Share.ResourceId, gatewayAddr); err == nil && ok { + return nil + } + } + } + } + } else { + // ref has ID present + // The request might be coming from a share created for a lightweight account + // after the token was minted. + log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId()) + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return err + } + for k := range tokenScope { + if strings.HasPrefix(k, "lightweight") { + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue + } + for _, share := range shares.Shares { + if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { + return nil + } + } + } + } + } + } else if ref, ok := extractShareRef(req); ok { + // It's a share ref + // The request might be coming from a share created for a lightweight account + // after the token was minted. + log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref) + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return err + } + for k := range tokenScope { + if strings.HasPrefix(k, "lightweight") { + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + log.Warn().Err(err).Msg("error listing received shares") + continue + } + for _, s := range shares.Shares { + if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId { + return nil + } + if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) && + utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) { + return nil + } + } + } + } + } + + return errtypes.PermissionDenied("access to resource not allowed within the assigned scope") +} + +func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return false, err + } + + // Since the resource ID is obtained from the scope, the current token + // has access to it. + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ResourceId: r}, + } + + statResponse, err := client.Stat(ctx, statReq) + if err != nil { + return false, err + } + if statResponse.Status.Code != rpc.Code_CODE_OK { + return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor") + } + + if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { + // The path corresponds to the resource to which the token has access. + // We allow access to it. + return true, nil + } + return false, nil +} + +func extractRef(req interface{}) (*provider.Reference, bool) { + switch v := req.(type) { + case *registry.GetStorageProvidersRequest: + return v.GetRef(), true + case *provider.StatRequest: + return v.GetRef(), true + case *provider.ListContainerRequest: + return v.GetRef(), true + case *provider.CreateContainerRequest: + return v.GetRef(), true + case *provider.DeleteRequest: + return v.GetRef(), true + case *provider.MoveRequest: + return v.GetSource(), true + case *provider.InitiateFileDownloadRequest: + return v.GetRef(), true + case *provider.InitiateFileUploadRequest: + return v.GetRef(), true + } + return nil, false +} + +func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) { + switch v := req.(type) { + case *collaboration.GetReceivedShareRequest: + return v.GetRef(), true + case *collaboration.UpdateReceivedShareRequest: + return v.GetRef(), true + } + return nil, false +} From 4f8c3681e60228ac51853a67f9d44ad1b17c935f Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Mon, 12 Jul 2021 15:31:53 +0200 Subject: [PATCH 21/23] Skip quota check for lw accounts --- .../ocs/handlers/cloud/users/users.go | 37 +++++++++++-------- pkg/cbox/user/rest/rest.go | 5 ++- pkg/storage/utils/eosfs/eosfs.go | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 37f6232784..13a3852b31 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -125,28 +125,35 @@ func (h *Handler) handleUsers(w http.ResponseWriter, r *http.Request, u *userpb. ocdav.HandleErrorStatus(sublog, w, getHomeRes.Status) return } - - getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{Path: getHomeRes.Path}}) - if err != nil { - sublog.Error().Err(err).Msg("error calling GetQuota") - w.WriteHeader(http.StatusInternalServerError) - return - } - - if getQuotaRes.Status.Code != rpc.Code_CODE_OK { - ocdav.HandleErrorStatus(sublog, w, getQuotaRes.Status) - return + var total, used uint64 + var relative float32 + // lightweight accounts don't have access to their storage space + if u.Id.Type != userpb.UserType_USER_TYPE_LIGHTWEIGHT { + getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{Path: getHomeRes.Path}}) + if err != nil { + sublog.Error().Err(err).Msg("error calling GetQuota") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if getQuotaRes.Status.Code != rpc.Code_CODE_OK { + ocdav.HandleErrorStatus(sublog, w, getQuotaRes.Status) + return + } + total = getQuotaRes.TotalBytes + used = getQuotaRes.UsedBytes + relative = float32(float64(used) / float64(total)) } response.WriteOCSSuccess(w, r, &Users{ // ocs can only return the home storage quota Quota: &Quota{ - Free: int64(getQuotaRes.TotalBytes - getQuotaRes.UsedBytes), - Used: int64(getQuotaRes.UsedBytes), + Free: int64(total - used), + Used: int64(used), // TODO support negative values or flags for the quota to carry special meaning: -1 = uncalculated, -2 = unknown, -3 = unlimited // for now we can only report total and used - Total: int64(getQuotaRes.TotalBytes), - Relative: float32(float64(getQuotaRes.UsedBytes) / float64(getQuotaRes.TotalBytes)), + Total: int64(total), + Relative: relative, Definition: "default", }, DisplayName: u.DisplayName, diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 0272e40c02..70e3177dbb 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -178,10 +178,13 @@ func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]int name, _ := userData["displayName"].(string) uidNumber, _ := userData["uid"].(float64) gidNumber, _ := userData["gid"].(float64) + t, _ := userData["type"].(string) + userType := getUserType(t, upn) userID := &userpb.UserId{ OpaqueId: upn, Idp: m.conf.IDProvider, + Type: userType, } u := &userpb.User{ Id: userID, @@ -275,8 +278,8 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s uidNumber, _ := usrInfo["uid"].(float64) gidNumber, _ := usrInfo["gid"].(float64) t, _ := usrInfo["type"].(string) - userType := getUserType(t, upn) + if userType == userpb.UserType_USER_TYPE_APPLICATION || userType == userpb.UserType_USER_TYPE_FEDERATED { continue } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index a35573ad27..8bbb3ace2c 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -913,7 +913,7 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { if err != nil { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } - // lightweight accounts don't quota nodes, so we're passing an empty string as path + // lightweight accounts don't have quota nodes, so we're passing an empty string as path auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") From 4625fc0e7c23698571c1be7eaf35cc1c7eb2e5b4 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Mon, 12 Jul 2021 16:45:55 +0200 Subject: [PATCH 22/23] Skip reporting errors when caching user details --- pkg/cbox/user/rest/cache.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/cbox/user/rest/cache.go b/pkg/cbox/user/rest/cache.go index b89c2a8923..418cf8fa2b 100644 --- a/pkg/cbox/user/rest/cache.go +++ b/pkg/cbox/user/rest/cache.go @@ -132,19 +132,11 @@ func (m *manager) cacheUserDetails(u *userpb.User) error { } uid, err := extractUID(u) - if err != nil { - return err - } - - if err = m.setVal(userPrefix+"uid:"+uid, u.Id.OpaqueId, -1); err != nil { - return err - } - if err = m.setVal(userPrefix+"mail:"+u.Mail, u.Id.OpaqueId, -1); err != nil { - return err - } - if err = m.setVal(userPrefix+"username:"+u.Username, u.Id.OpaqueId, -1); err != nil { - return err + if err == nil { + _ = m.setVal(userPrefix+"uid:"+uid, u.Id.OpaqueId, -1) } + _ = m.setVal(userPrefix+"mail:"+u.Mail, u.Id.OpaqueId, -1) + _ = m.setVal(userPrefix+"username:"+u.Username, u.Id.OpaqueId, -1) return nil } From 7086a0785a2fae1b277d6c746edb809343df564c Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Mon, 12 Jul 2021 17:38:05 +0200 Subject: [PATCH 23/23] Remove uid and gid check when getting user from ctx --- pkg/cbox/group/rest/rest.go | 2 +- pkg/cbox/user/rest/rest.go | 2 +- pkg/storage/utils/eosfs/eosfs.go | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pkg/cbox/group/rest/rest.go b/pkg/cbox/group/rest/rest.go index 207d4bf943..63b9cab945 100644 --- a/pkg/cbox/group/rest/rest.go +++ b/pkg/cbox/group/rest/rest.go @@ -128,7 +128,7 @@ func (m *manager) getGroupByParam(ctx context.Context, param, val string) (map[s return nil, err } if len(responseData) != 1 { - return nil, errors.New("rest: group not found: " + param + ":" + val) + return nil, errors.New("rest: group not found: " + param + ": " + val) } userData, ok := responseData[0].(map[string]interface{}) diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 70e3177dbb..ecd854b3b6 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -144,7 +144,7 @@ func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[st } if len(users) != 1 { - return nil, errors.New("rest: user not found: " + param + ":" + val) + return nil, errors.New("rest: user not found: " + param + ": " + val) } return users[0], nil diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 8bbb3ace2c..a4affa4fe9 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -244,12 +244,6 @@ func getUser(ctx context.Context) (*userpb.User, error) { err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx") return nil, err } - if u.UidNumber == 0 { - return nil, errors.New("eosfs: invalid user id") - } - if u.GidNumber == 0 { - return nil, errors.New("eosfs: invalid group id") - } return u, nil }