From 3ba91f0b6206b9a9559897067a301d7c2b739541 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 26 May 2021 16:13:27 +0200 Subject: [PATCH] Add resource info scope --- internal/grpc/services/loader/loader.go | 1 + .../services/userprovider/userprovider.go | 2 +- pkg/auth/manager/publicshares/publicshares.go | 28 +- pkg/auth/scope/publicshare.go | 28 +- pkg/auth/scope/resourceinfo.go | 110 +++++++ pkg/auth/scope/scope.go | 49 +-- pkg/auth/scope/user.go | 29 +- pkg/publicshare/manager/memory/memory_test.go | 302 +++++++++--------- 8 files changed, 329 insertions(+), 220 deletions(-) create mode 100644 pkg/auth/scope/resourceinfo.go diff --git a/internal/grpc/services/loader/loader.go b/internal/grpc/services/loader/loader.go index 253765573b9..118eeed39ee 100644 --- a/internal/grpc/services/loader/loader.go +++ b/internal/grpc/services/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load core gRPC services. + _ "github.com/cs3org/reva/internal/grpc/services/applicationauth" _ "github.com/cs3org/reva/internal/grpc/services/appprovider" _ "github.com/cs3org/reva/internal/grpc/services/appregistry" _ "github.com/cs3org/reva/internal/grpc/services/authprovider" diff --git a/internal/grpc/services/userprovider/userprovider.go b/internal/grpc/services/userprovider/userprovider.go index fb244990dec..465185d4e6f 100644 --- a/internal/grpc/services/userprovider/userprovider.go +++ b/internal/grpc/services/userprovider/userprovider.go @@ -92,7 +92,7 @@ func (s *service) Close() error { } func (s *service) UnprotectedEndpoints() []string { - return []string{"/cs3.identity.user.v1beta1.UserAPI/GetUser"} + return []string{"/cs3.identity.user.v1beta1.UserAPI/GetUser", "/cs3.identity.user.v1beta1.UserAPI/GetUserByClaim"} } func (s *service) Register(ss *grpc.Server) { diff --git a/pkg/auth/manager/publicshares/publicshares.go b/pkg/auth/manager/publicshares/publicshares.go index 3dc074f1d38..0b2df90ee75 100644 --- a/pkg/auth/manager/publicshares/publicshares.go +++ b/pkg/auth/manager/publicshares/publicshares.go @@ -31,9 +31,9 @@ import ( types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" + "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/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -127,33 +127,17 @@ func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user return nil, nil, err } - scope, err := m.getScope(ctx, publicShareResponse.GetShare()) - if err != nil { - return nil, nil, err - } - - return getUserResponse.GetUser(), scope, nil -} - -func (m *manager) getScope(ctx context.Context, share *link.PublicShare) (map[string]*authpb.Scope, error) { + share := publicShareResponse.GetShare() role := authpb.Role_ROLE_VIEWER if share.Permissions.Permissions.InitiateFileUpload { role = authpb.Role_ROLE_EDITOR } - - val, err := utils.MarshalProtoV1ToJSON(share) + scope, err := scope.GetPublicShareScope(share, role) if err != nil { - return nil, err + return nil, nil, err } - return map[string]*authpb.Scope{ - "publicshare": &authpb.Scope{ - Resource: &types.OpaqueEntry{ - Decoder: "json", - Value: val, - }, - Role: role, - }, - }, nil + + return getUserResponse.GetUser(), scope, nil } // ErrPasswordNotProvided is returned when the public share is password protected, but there was no password on the request diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index b88f48c78a3..1b7b686631e 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -26,6 +26,7 @@ import ( 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" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/utils" ) @@ -63,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(&share, v), nil + return checkPath(v), nil } return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) @@ -86,15 +87,20 @@ func checkPublicShareRef(s *link.PublicShare, ref *link.PublicShareReference) bo return ref.GetToken() == s.Token } -func checkPath(s *link.PublicShare, path string) bool { - paths := []string{ - "/dataprovider", - "/data", - } - for _, p := range paths { - if strings.HasPrefix(path, p) { - return true - } +// GetPublicShareScope returns 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) { + val, err := utils.MarshalProtoV1ToJSON(share) + if err != nil { + return nil, err } - return false + return map[string]*authpb.Scope{ + "publicshare:" + share.Id.OpaqueId: &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: role, + }, + }, nil } diff --git a/pkg/auth/scope/resourceinfo.go b/pkg/auth/scope/resourceinfo.go new file mode 100644 index 00000000000..3d75938bce4 --- /dev/null +++ b/pkg/auth/scope/resourceinfo.go @@ -0,0 +1,110 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package scope + +import ( + "fmt" + "strings" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" +) + +func resourceinfoScope(scope *authpb.Scope, resource interface{}) (bool, error) { + var r provider.ResourceInfo + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r) + if err != nil { + return false, err + } + + switch v := resource.(type) { + // Viewer role + case *registry.GetStorageProvidersRequest: + return checkResourceInfo(&r, v.GetRef()), nil + case *provider.StatRequest: + return checkResourceInfo(&r, v.GetRef()), nil + case *provider.ListContainerRequest: + return checkResourceInfo(&r, v.GetRef()), nil + case *provider.InitiateFileDownloadRequest: + return checkResourceInfo(&r, v.GetRef()), nil + + // Editor role + // TODO(ishank011): Add role checks, + // need to return appropriate status codes in the ocs/ocdav layers. + case *provider.CreateContainerRequest: + return checkResourceInfo(&r, v.GetRef()), nil + case *provider.DeleteRequest: + return checkResourceInfo(&r, v.GetRef()), nil + case *provider.MoveRequest: + return checkResourceInfo(&r, v.GetSource()) && checkResourceInfo(&r, v.GetDestination()), nil + case *provider.InitiateFileUploadRequest: + return checkResourceInfo(&r, v.GetRef()), nil + + case string: + return checkPath(v), nil + } + + return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) +} + +func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool { + // ref: > + if ref.GetId() != nil { + return inf.Id.StorageId == ref.GetId().StorageId && inf.Id.OpaqueId == ref.GetId().OpaqueId + } + // ref: + if strings.HasPrefix(ref.GetPath(), inf.Path) { + return true + } + return false +} + +func checkPath(path string) bool { + paths := []string{ + "/dataprovider", + "/data", + } + for _, p := range paths { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + +// 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) { + 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, + }, + }, nil +} diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 42ba65c11f7..35bdb29cfc5 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -19,54 +19,35 @@ package scope import ( + "strings" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/utils" ) // Verifier is the function signature which every scope verifier should implement. type Verifier func(*authpb.Scope, interface{}) (bool, error) var supportedScopes = map[string]Verifier{ - "user": userScope, - "publicshare": publicshareScope, + "user": userScope, + "publicshare": publicshareScope, + "resourceinfo": resourceinfoScope, } // VerifyScope is the function to be called when dismantling tokens to check if // the token has access to a particular resource. func VerifyScope(scopeMap map[string]*authpb.Scope, resource interface{}) (bool, error) { for k, scope := range scopeMap { - verifierFunc := supportedScopes[k] - valid, err := verifierFunc(scope, resource) - if err != nil { - continue - } - if valid { - return true, nil + for s, f := range supportedScopes { + if strings.HasPrefix(k, s) { + valid, err := f(scope, resource) + if err != nil { + continue + } + if valid { + return true, nil + } + } } } return false, nil } - -// GetOwnerScope returns the default owner scope with access to all resources. -func GetOwnerScope() (map[string]*authpb.Scope, error) { - ref := &provider.Reference{ - Spec: &provider.Reference_Path{ - 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, - }, - }, nil -} diff --git a/pkg/auth/scope/user.go b/pkg/auth/scope/user.go index f885a43ba8b..42ebf47728c 100644 --- a/pkg/auth/scope/user.go +++ b/pkg/auth/scope/user.go @@ -18,10 +18,37 @@ package scope -import authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" +import ( + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/utils" +) func userScope(scope *authpb.Scope, resource interface{}) (bool, error) { // Always return true. Registered users can access all paths. // TODO(ishank011): Add checks for read/write permissions. return true, nil } + +// GetOwnerScope returns the default owner scope with access to all resources. +func GetOwnerScope() (map[string]*authpb.Scope, error) { + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + 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, + }, + }, nil +} diff --git a/pkg/publicshare/manager/memory/memory_test.go b/pkg/publicshare/manager/memory/memory_test.go index 78a6b0a61ec..4b503bf996e 100644 --- a/pkg/publicshare/manager/memory/memory_test.go +++ b/pkg/publicshare/manager/memory/memory_test.go @@ -18,154 +18,154 @@ package memory -// import ( -// "context" -// "testing" - -// userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" -// link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" -// provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" -// types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" -// ) - -// func TestMemoryProvider(t *testing.T) { -// // table driven tests is perhaps more readable -// // setup a new public shares manager -// manager, err := New(make(map[string]interface{})) -// if err != nil { -// t.Error(err) -// } - -// // Setup dat -// user := userpb.User{} -// rInfo := provider.ResourceInfo{} -// grant := link.Grant{} - -// rInfo.ArbitraryMetadata = &provider.ArbitraryMetadata{ -// Metadata: map[string]string{ -// "name": "woof", -// }, -// } - -// // create a new public share -// share, _ := manager.CreatePublicShare(context.Background(), &user, &rInfo, &grant) - -// // store its token for further retrieval -// shareToken := share.GetToken() - -// // Test updating a public share. -// existingRefToken := link.PublicShareReference{ -// Spec: &link.PublicShareReference_Token{ -// Token: shareToken, -// }, -// } - -// nonExistingPublicShareRef := link.PublicShareReference{ -// Spec: &link.PublicShareReference_Token{Token: "somethingsomething"}, -// } - -// updatedMtime := &types.Timestamp{Seconds: uint64(46800)} - -// newGrant := link.Grant{ -// Permissions: &link.PublicSharePermissions{}, -// Expiration: updatedMtime, -// } - -// // attempt to update an invalid public share. we expect an error -// _, err = manager.UpdatePublicShare(context.Background(), &user, &nonExistingPublicShareRef, &newGrant) -// if err == nil { -// t.Error(err) -// } - -// // update an existing public share -// updatedShare, err := manager.UpdatePublicShare(context.Background(), &user, &existingRefToken, &newGrant) -// if err != nil { -// t.Error(err) -// } - -// // verify the expiration was updated to 01/01/1970 @ 1:00pm (UTC) -// if updatedShare.Expiration == updatedMtime { -// t.Error("") -// } - -// // test getting an invalid token -// _, err = manager.GetPublicShareByToken(context.Background(), "xxxxxxxx") -// if err == nil { -// t.Error(err) -// } - -// // test getting a valid token -// fetchedPs, err := manager.GetPublicShareByToken(context.Background(), shareToken) -// if err != nil { -// t.Error(err) -// } - -// if fetchedPs.GetToken() != shareToken { -// t.Error("mismatching public share tokens") -// } - -// // test listing public shares -// listPs, err := manager.ListPublicShares(context.Background(), &user, nil, &rInfo) -// if err != nil { -// t.Error(err) -// } - -// if len(listPs) != 1 { -// t.Errorf("expected list of length 1, but got %v", len(listPs)) -// } - -// // holds a reference of hte public share with the previously fetched token -// publicShareRef := link.PublicShareReference{ -// Spec: &link.PublicShareReference_Token{Token: shareToken}, -// } - -// // error expected -// _, err = manager.GetPublicShare(context.Background(), &user, &nonExistingPublicShareRef) -// if err == nil { -// t.Error(err) -// } - -// // expected error to be nil -// pShare, err := manager.GetPublicShare(context.Background(), &user, &publicShareRef) -// if err != nil { -// t.Error(err) -// } - -// existingRefID := link.PublicShareReference{ -// Spec: &link.PublicShareReference_Id{ -// Id: pShare.GetId(), -// }, -// } - -// nonExistingRefID := link.PublicShareReference{ -// Spec: &link.PublicShareReference_Id{ -// Id: &link.PublicShareId{ -// OpaqueId: "doesnt_exist", -// }, -// }, -// } - -// // get public share by ID... we don't expect an error -// _, err = manager.GetPublicShare(context.Background(), &user, &existingRefID) -// if err != nil { -// t.Error(err) -// } - -// // get public share by ID... we expect an error -// _, err = manager.GetPublicShare(context.Background(), &user, &nonExistingRefID) -// if err == nil { -// t.Error(err) -// } - -// // attempts to revoke a public share that does not exist, we expect an error -// err = manager.RevokePublicShare(context.Background(), &user, "ref_does_not_exist") -// if err == nil { -// t.Error("expected a failure when revoking a public share that does not exist") -// } - -// // revoke an existing public share -// err = manager.RevokePublicShare(context.Background(), &user, fetchedPs.GetToken()) -// if err != nil { -// t.Error(err) -// } -// } +import ( + "context" + "testing" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" +) + +func TestMemoryProvider(t *testing.T) { + // table driven tests is perhaps more readable + // setup a new public shares manager + manager, err := New(make(map[string]interface{})) + if err != nil { + t.Error(err) + } + + // Setup dat + user := userpb.User{} + rInfo := provider.ResourceInfo{} + grant := link.Grant{} + + rInfo.ArbitraryMetadata = &provider.ArbitraryMetadata{ + Metadata: map[string]string{ + "name": "woof", + }, + } + + // create a new public share + share, _ := manager.CreatePublicShare(context.Background(), &user, &rInfo, &grant) + + // store its token for further retrieval + shareToken := share.GetToken() + + // Test updating a public share. + existingRefToken := link.PublicShareReference{ + Spec: &link.PublicShareReference_Token{ + Token: shareToken, + }, + } + + nonExistingPublicShareRef := link.PublicShareReference{ + Spec: &link.PublicShareReference_Token{Token: "somethingsomething"}, + } + + updatedMtime := &types.Timestamp{Seconds: uint64(46800)} + + newGrant := link.Grant{ + Permissions: &link.PublicSharePermissions{}, + Expiration: updatedMtime, + } + + // attempt to update an invalid public share. we expect an error + _, err = manager.UpdatePublicShare(context.Background(), &user, &nonExistingPublicShareRef, &newGrant) + if err == nil { + t.Error(err) + } + + // update an existing public share + updatedShare, err := manager.UpdatePublicShare(context.Background(), &user, &existingRefToken, &newGrant) + if err != nil { + t.Error(err) + } + + // verify the expiration was updated to 01/01/1970 @ 1:00pm (UTC) + if updatedShare.Expiration == updatedMtime { + t.Error("") + } + + // test getting an invalid token + _, err = manager.GetPublicShareByToken(context.Background(), "xxxxxxxx") + if err == nil { + t.Error(err) + } + + // test getting a valid token + fetchedPs, err := manager.GetPublicShareByToken(context.Background(), shareToken) + if err != nil { + t.Error(err) + } + + if fetchedPs.GetToken() != shareToken { + t.Error("mismatching public share tokens") + } + + // test listing public shares + listPs, err := manager.ListPublicShares(context.Background(), &user, nil, &rInfo) + if err != nil { + t.Error(err) + } + + if len(listPs) != 1 { + t.Errorf("expected list of length 1, but got %v", len(listPs)) + } + + // holds a reference of hte public share with the previously fetched token + publicShareRef := link.PublicShareReference{ + Spec: &link.PublicShareReference_Token{Token: shareToken}, + } + + // error expected + _, err = manager.GetPublicShare(context.Background(), &user, &nonExistingPublicShareRef) + if err == nil { + t.Error(err) + } + + // expected error to be nil + pShare, err := manager.GetPublicShare(context.Background(), &user, &publicShareRef) + if err != nil { + t.Error(err) + } + + existingRefID := link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: pShare.GetId(), + }, + } + + nonExistingRefID := link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: "doesnt_exist", + }, + }, + } + + // get public share by ID... we don't expect an error + _, err = manager.GetPublicShare(context.Background(), &user, &existingRefID) + if err != nil { + t.Error(err) + } + + // get public share by ID... we expect an error + _, err = manager.GetPublicShare(context.Background(), &user, &nonExistingRefID) + if err == nil { + t.Error(err) + } + + // attempts to revoke a public share that does not exist, we expect an error + err = manager.RevokePublicShare(context.Background(), &user, "ref_does_not_exist") + if err == nil { + t.Error("expected a failure when revoking a public share that does not exist") + } + + // revoke an existing public share + err = manager.RevokePublicShare(context.Background(), &user, fetchedPs.GetToken()) + if err != nil { + t.Error(err) + } +}