diff --git a/changelog/unreleased/lw-user-support.md b/changelog/unreleased/lw-user-support.md new file mode 100644 index 0000000000..a81ba938ad --- /dev/null +++ b/changelog/unreleased/lw-user-support.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. 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 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/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/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..426ba07b75 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,12 +26,25 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "localhost:20080" + "idp": "localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", "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 b7618b4686..8151013c81 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -20,17 +20,11 @@ package auth import ( "context" - "strings" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - 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" - "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" @@ -119,7 +113,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. @@ -190,7 +184,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. @@ -215,7 +209,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 @@ -230,77 +223,9 @@ 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 ref, ok := extractRef(req); ok { - 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()) - var share link.PublicShare - var publicShareScope []byte - for k := range tokenScope { - if strings.HasPrefix(k, "publicshare") { - publicShareScope = tokenScope[k].Resource.Value - break - } - } - err = utils.UnmarshalJSONToProtoV1(publicShareScope, &share) - if err != nil { - return nil, err - } - - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - 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, - }, - } - - statResponse, err := client.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { - return nil, err - } - - 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 - } - } + if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr); err != nil { + return nil, err } - return nil, errtypes.PermissionDenied("access token has invalid scope") -} - -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 + return u, nil } 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 +} diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 847d0761e1..11928bd720 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -21,19 +21,25 @@ 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" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/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,6 +99,11 @@ 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") @@ -102,7 +113,27 @@ 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 { + 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") + 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{ + Status: status.NewUnauthenticated(ctx, err, "error creating access token"), + } + return res, nil + } + + 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, @@ -191,3 +222,74 @@ 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) { + log := appctx.GetLogger(ctx) + 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 { + 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 { + log.Warn().Err(err).Msgf("error expanding publicshare resource scope %+v", share.ResourceId) + continue + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) + if err != nil { + 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 { + log.Warn().Err(err).Msgf("error expanding share resource scope %+v", share.ResourceId) + continue + } + + case strings.HasPrefix(k, "lightweight"): + shares, err := s.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 { + 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 resource scope %+v", share.Share.ResourceId) + continue + } + } + } + } + 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{ResourceId: r}, + } + statResponse, err := s.Stat(ctx, statReq) + 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) +} 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/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/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/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/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index d6807d0286..a4d18f6964 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.GetOwnerScope() - 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 != 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 + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + } + return c.User, scopes, nil } } return nil, nil, errtypes.InvalidCredentials(clientID) @@ -72,6 +80,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 +94,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 +108,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..0f9fd612ba 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 { @@ -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..e12c907126 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.GetOwnerScope() - 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 != 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 + } + } 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 32e31cf5d7..1727076d0f 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, // TODO: assign the appropriate user type } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { @@ -216,14 +217,22 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: gidNumber, } - scope, err := scope.GetOwnerScope() - if err != nil { - return nil, nil, err + var scopes map[string]*authpb.Scope + 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 + } + } 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 6958828ab3..dd717bec85 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 { @@ -171,12 +172,20 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) GidNumber: int64(gid), } - scope, err := scope.GetOwnerScope() - if err != nil { - return nil, nil, err + var scopes map[string]*authpb.Scope + 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 + } + } else { + scopes, err = scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } } - return u, scope, nil + return u, scopes, nil } func (am *mgr) getOAuthCtx(ctx context.Context) context.Context { 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..9390743b24 --- /dev/null +++ b/pkg/auth/scope/lightweight.go @@ -0,0 +1,58 @@ +// 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 ( + 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" +) + +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. + // From here, we only allow ListReceivedShares calls + if _, ok := resource.(*collaboration.ListReceivedSharesRequest); ok { + return true, nil + } + 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/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/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/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..e88d0acc43 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -28,9 +28,12 @@ import ( type Verifier func(*authpb.Scope, interface{}) (bool, error) var supportedScopes = map[string]Verifier{ - "user": userScope, - "publicshare": publicshareScope, - "resourceinfo": resourceinfoScope, + "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 @@ -39,11 +42,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..de26f62a02 --- /dev/null +++ b/pkg/auth/scope/share.go @@ -0,0 +1,127 @@ +// 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", + "/remote.php/webdav", + "/remote.php/dav/files", + } + for _, p := range paths { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + +// 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) + 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 } 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/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 } diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index e20927e5bd..ecd854b3b6 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -128,19 +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) + } } - if userData["type"].(string) == "Application" || strings.HasPrefix(userData["upn"].(string), "guest") { - return nil, errors.New("rest: guest 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) { @@ -171,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, @@ -258,7 +268,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 +277,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 +391,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 + +} diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index 8d5207f896..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" @@ -42,7 +43,8 @@ import ( ) const ( - versionPrefix = ".sys.v#." + versionPrefix = ".sys.v#." + lwShareAttrKey = "reva.lwshare" ) const ( @@ -115,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() { @@ -146,19 +152,21 @@ 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 } -// 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 +211,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 +277,41 @@ 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 } + + 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{"-r", rootUID, rootGID, "acl"} + args := []string{"acl"} if finfo.IsDir { args = append(args, "--sys", "--recursive") @@ -276,27 +322,53 @@ 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 } + 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{"-r", rootUID, rootGID, "acl"} + args := []string{"acl"} if finfo.IsDir { args = append(args, "--sys", "--recursive") } else { @@ -305,25 +377,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 +410,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 +422,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 +432,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 +444,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 +455,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 +465,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 +477,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 +486,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 +505,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 +518,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 +528,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 +540,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 +592,21 @@ 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 +614,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 +628,30 @@ 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("-ODeos.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 +659,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 +684,34 @@ 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) { +// 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) { + 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 +} + +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 +719,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 } @@ -921,6 +1014,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/eosclient/eosclient.go b/pkg/eosclient/eosclient.go index 1f4b03313c..8bd2038bc5 100644 --- a/pkg/eosclient/eosclient.go +++ b/pkg/eosclient/eosclient.go @@ -27,34 +27,35 @@ 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) + GenerateToken(ctx context.Context, auth Authorization, path string, a *acl.Entry) (string, error) } // AttrType is the type of extended attribute, @@ -119,3 +120,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/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index ea4db75db3..c002b6294a 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 @@ -210,30 +187,28 @@ 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 -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 +220,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 +246,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 +263,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 +298,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 +312,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 +347,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 +373,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 +387,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 +416,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 +437,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 +477,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 +489,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 +519,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 +531,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 +560,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 +571,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 +616,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 +626,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 +658,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 +683,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 +705,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 +744,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 +769,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 +795,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 +805,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 +840,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 +882,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 +892,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 +925,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 +934,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 +960,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 +969,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 +997,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 +1006,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 +1050,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 +1060,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 +1073,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 +1146,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 +1160,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 +1180,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 +1194,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 +1208,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 +1255,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 +1290,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 +1316,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 +1328,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 +1352,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 +1361,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 +1375,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 +1386,30 @@ 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) +} + +// 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") } -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 +1417,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 83% rename from pkg/eosclient/eosgrpc/eos_http/eoshttp.go rename to pkg/eosclient/eosgrpc/eoshttp.go index 6b1340e572..e7631b8951 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" @@ -28,16 +28,16 @@ import ( "net/url" "os" "strconv" - "strings" "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 +79,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 +122,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 is nil") + } + + opt.init() cert, err := tls.LoadX509KeyPair(opt.ClientCertFile, opt.ClientKeyFile) if err != nil { return nil, err @@ -143,49 +164,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 +198,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 +221,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 { @@ -244,25 +233,17 @@ func (c *Client) buildFullURL(urlpath, uid, gid string) (string, error) { 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(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 +251,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 +298,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 +343,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 +392,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 +442,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 +456,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/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/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/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/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 81b927b8c6..a4affa4fe9 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,16 +127,20 @@ func (c *Config) init() { c.UserIDCacheWarmupDepth = 2 } + if c.TokenExpiry == 0 { + c.TokenExpiry = 3600 + } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) } 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 gcache.Cache + tokenCache gcache.Cache } // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance @@ -151,25 +156,29 @@ func NewEOSFS(c *Config) (storage.FS, error) { } var eosClient eosclient.EOSClient + 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, + 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 = eosgrpc.New(eosClientOpts) + eosClient, err = eosgrpc.New(eosClientOpts, eosHTTPOpts) } else { eosClientOpts := &eosbinary.Options{ XrdcopyBinary: c.XrdcopyBinary, @@ -183,7 +192,11 @@ func NewEOSFS(c *Config) (storage.FS, error) { SecProtocol: c.SecProtocol, VersionInvariant: c.VersionInvariant, } - eosClient = eosbinary.New(eosClientOpts) + eosClient, err = eosbinary.New(eosClientOpts) + } + + if err != nil { + return nil, errors.Wrap(err, "error initializing eosclient") } eosfs := &eosfs{ @@ -191,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() @@ -202,12 +216,12 @@ func (fs *eosfs) userIDcacheWarmup() { if !fs.conf.EnableHome { ctx := context.Background() paths := []string{fs.wrap(ctx, "/")} - uid, gid, _ := fs.getRootUIDAndGID(ctx) + auth, _ := fs.getRootAuth(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) @@ -230,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 } @@ -329,9 +337,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 } @@ -346,18 +354,18 @@ 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) } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getRootAuth(ctx) 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") } @@ -380,25 +388,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") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { return "", err } - eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, uid, gid, fileID) + eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) if err != nil { return "", errors.Wrap(err, "eosfs: error getting file info by inode") } @@ -407,23 +426,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") } - uid, gid, err := fs.getUserUIDAndGID(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 { @@ -439,7 +458,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, fn) if err != nil { return errors.Wrap(err, "eosfs: error setting xattr in eos driver") } @@ -449,23 +468,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") } - uid, gid, err := fs.getUserUIDAndGID(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 { @@ -478,7 +497,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, fn) if err != nil { return errors.Wrap(err, "eosfs: error unsetting xattr in eos driver") } @@ -488,34 +507,32 @@ 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 } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + eosACL, err := fs.getEosACL(ctx, g) if err != nil { return err } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(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") } @@ -535,11 +552,18 @@ 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. - qualifier, _, 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 } } else { qualifier = g.Grantee.GetGroupId().OpaqueId @@ -554,11 +578,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 @@ -566,10 +585,17 @@ 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()) - 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 } } else { recipient = g.Grantee.GetGroupId().OpaqueId @@ -580,24 +606,27 @@ 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) - uid, gid, err := fs.getUserUIDAndGID(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 } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(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") } @@ -609,23 +638,22 @@ 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) - uid, gid, err := fs.getUserUIDAndGID(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 } - acls, err := fs.c.ListACLs(ctx, uid, gid, fn) + acls, err := fs.c.ListACLs(ctx, auth, fn) if err != nil { return nil, err } @@ -633,7 +661,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) @@ -644,12 +673,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), @@ -660,15 +696,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") } @@ -682,12 +713,16 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st fn := fs.wrap(ctx, p) - uid, gid, err := fs.getUserUIDAndGID(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 } - eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, uid, gid, fn) + eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err } @@ -696,19 +731,20 @@ 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) - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + // 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 } - eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, uid, gid, fn) + eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err } @@ -722,12 +758,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") } @@ -745,20 +777,18 @@ 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") } - - uid, gid, err := fs.getUserUIDAndGID(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, uid, gid, fn) + eosFileInfos, err := fs.c.List(ctx, auth, fn) if err != nil { return nil, errors.Wrap(err, "eosfs: error listing") } @@ -800,21 +830,21 @@ 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") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + // 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 } - fns := []string{fs.wrap(ctx, home), fs.wrapShadow(ctx, home)} - 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") } @@ -838,18 +868,19 @@ 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") } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + // 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 } - 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") } @@ -876,18 +907,18 @@ func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) { if err != nil { return 0, 0, errors.Wrap(err, "eosfs: no user in ctx") } - - uid, _, err := fs.getUserUIDAndGID(ctx, u) + // 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: no uid in ctx") + return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") } - rootUID, rootGID, err := fs.getRootUIDAndGID(ctx) + rootAuth, err := fs.getRootAuth(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, auth.Role.UID, rootAuth, fs.conf.QuotaNode) if err != nil { err := errors.Wrap(err, "eosfs: error getting quota") return 0, 0, err @@ -925,7 +956,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.getRootAuth(ctx) if err != nil { return nil } @@ -934,7 +965,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") @@ -950,23 +981,23 @@ 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") } - - home := fs.wrap(ctx, "/") - rootuid, rootgid, err := fs.getRootUIDAndGID(ctx) + auth, err := fs.getUserAuth(ctx, u, "") if err != nil { - return nil + return err } - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + rootAuth, err := fs.getRootAuth(ctx) if err != nil { - return err + return nil } - _, err = fs.c.GetFileInfoByPath(ctx, rootuid, rootgid, home) + _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home) if err == nil { // home already exists return nil } @@ -984,14 +1015,14 @@ 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, } - 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 +1048,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.getRootAuth(ctx) if err != nil { return nil } - chownUID, chownGID, err := fs.getUserUIDAndGID(ctx, u) + chownAuth, err := fs.getUserAuth(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 +1098,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") } @@ -1082,8 +1113,7 @@ func (fs *eosfs) CreateDir(ctx context.Context, p string) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u, p) if err != nil { return err } @@ -1095,7 +1125,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 +1145,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.getRootAuth(ctx) if err != nil { return nil } @@ -1132,13 +1162,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 } @@ -1147,17 +1177,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") - } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1168,7 +1188,16 @@ func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { fn := fs.wrap(ctx, p) - return fs.c.Remove(ctx, uid, gid, fn) + 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) } func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { @@ -1182,35 +1211,26 @@ 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) + fn := fs.wrapShadow(ctx, p) + + auth, err := fs.getUserAuth(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) } 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") - } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1221,20 +1241,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, uid, gid, 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") } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1249,21 +1269,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, uid, gid, 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") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u, "") 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") } @@ -1273,21 +1293,21 @@ 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) -} -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") } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1298,7 +1318,16 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] fn := fs.wrap(ctx, p) - eosRevisions, err := fs.c.ListVersions(ctx, uid, gid, fn) + 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") } @@ -1312,17 +1341,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") - } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1333,22 +1352,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, uid, gid, 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") } - - uid, gid, err := fs.getUserUIDAndGID(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") } @@ -1359,7 +1376,16 @@ 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) + 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) } func (fs *eosfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { @@ -1371,13 +1397,12 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(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) { @@ -1385,13 +1410,12 @@ func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*prov if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(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") } @@ -1416,13 +1440,12 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, r if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") } - - uid, gid, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(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) { @@ -1484,13 +1507,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, @@ -1514,7 +1537,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } } - uid, _, err := fs.getUserUIDAndGID(ctx, u) + auth, err := fs.getUserAuth(ctx, u, eosFileInfo.File) if err != nil { return &provider.ResourcePermissions{ // no permissions @@ -1533,7 +1556,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) || (e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId) || userInGroup { mergePermissions(&perm, grants.GetGrantPermissionSet(e.Permissions, eosFileInfo.IsDir)) } } @@ -1631,29 +1654,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") + 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 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) (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 +1713,81 @@ 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) getUserAuth(ctx context.Context, u *userpb.User, fn string) (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 } + + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + return fs.getEOSToken(ctx, u, fn) + } + return fs.extractUIDAndGID(u) } -func (fs *eosfs) getRootUIDAndGID(ctx context.Context) (string, string, error) { +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 + } + } + + 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 +} + +func (fs *eosfs) getRootAuth(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{UID: "0", GID: "0"}}, nil } type eosSysMetadata struct { diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 74b3f923ec..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") - } - uid, gid, err := fs.getUserUIDAndGID(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,7 +62,16 @@ 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) + + 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) } func (fs *eosfs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { 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..fe14e70103 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, }, } @@ -81,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 0a19454e81..61f1276cbb 100644 --- a/tests/integration/grpc/userprovider_test.go +++ b/tests/integration/grpc/userprovider_test.go @@ -54,11 +54,12 @@ 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"}) 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()) @@ -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{