From edf08c148f22980764c43d091557a08929b05f62 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 1 Jun 2021 17:35:20 +0200 Subject: [PATCH] Expand scopes when minting tokens and add checks for shares and lw scopes --- internal/grpc/interceptors/auth/auth.go | 91 ++++++++---- .../grpc/services/gateway/authprovider.go | 75 +++++++++- pkg/auth/scope/lightweight.go | 134 +++++------------- pkg/auth/scope/scope.go | 1 + pkg/auth/scope/share.go | 2 +- 5 files changed, 173 insertions(+), 130 deletions(-) diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index f7cc18e0e88..4953e78238a 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -24,6 +24,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" @@ -243,46 +244,74 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. // Currently, we only check for public shares, but this will be extended // for OCM shares, guest accounts, etc. log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath()) - var share link.PublicShare - var publicShareScope []byte for k := range tokenScope { - if strings.HasPrefix(k, "publicshare") { - publicShareScope = tokenScope[k].Resource.Value - break + switch { + case strings.HasPrefix(k, "publicshare"): + var share link.PublicShare + err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err = utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) + if err != nil { + continue + } + if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + case strings.HasPrefix(k, "lightweight"): + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + continue + } + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + continue + } + for _, share := range shares.Shares { + if ok, err := checkResourcePath(ctx, ref, share.Share.ResourceId, gatewayAddr); err == nil && ok { + return u, nil + } + } } } - err = utils.UnmarshalJSONToProtoV1(publicShareScope, &share) - if err != nil { - return nil, err - } + } + } - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return nil, err - } + return nil, err +} - // Since the public share is obtained from the scope, the current token - // has access to it. - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{Id: share.ResourceId}, - }, - } +func checkResourcePath(ctx context.Context, ref *provider.Reference, r *provider.ResourceId, gatewayAddr string) (bool, error) { + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return false, err + } - statResponse, err := client.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { - return nil, err - } + // Since the public share is obtained from the scope, the current token + // has access to it. + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{Id: r}, + }, + } - if strings.HasPrefix(ref.GetPath(), statResponse.Info.Path) { - // The path corresponds to the resource to which the token has access. - // We allow access to it. - return u, nil - } - } + statResponse, err := client.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + return false, err } - return nil, 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 true, nil + } + return false, nil } func extractRef(req interface{}) (*provider.Reference, bool) { diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 847d0761e19..4d7cdd46271 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -21,19 +21,24 @@ package gateway import ( "context" "fmt" + "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth/scope" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" tokenpkg "github.com/cs3org/reva/pkg/token" userpkg "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" "google.golang.org/grpc/metadata" ) @@ -93,7 +98,15 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } - token, err := s.tokenmgr.MintToken(ctx, res.User, res.TokenScope) + scope, err := s.expandScopes(ctx, res.TokenScope) + if err != nil { + err = errors.Wrap(err, "authsvc: error expanding token scope") + return &gateway.AuthenticateResponse{ + Status: status.NewUnauthenticated(ctx, err, "error expanding access token scope"), + }, nil + } + + token, err := s.tokenmgr.MintToken(ctx, res.User, scope) if err != nil { err = errors.Wrap(err, "authsvc: error in MintToken") res := &gateway.AuthenticateResponse{ @@ -191,3 +204,63 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (provider.P return nil, errtypes.InternalError("gateway: error finding an auth provider for type: " + authType) } + +func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + newMap := make(map[string]*authpb.Scope) + for k, v := range scopeMap { + newMap[k] = v + switch { + case strings.HasPrefix(k, "publicshare"): + var share link.PublicShare + err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + + case strings.HasPrefix(k, "share"): + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, &share) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + + case strings.HasPrefix(k, "lightweight"): + shares, err := s.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + return nil, err + } + for _, share := range shares.Shares { + newMap, err = scope.AddShareScope(share.Share, v.Role, newMap) + if err != nil { + return nil, err + } + newMap, err = s.statAndAddResource(ctx, share.Share.ResourceId, v.Role, newMap) + if err != nil { + return nil, err + } + } + } + } + return newMap, nil +} + +func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.ResourceId, role authpb.Role, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + statReq := &storageprovider.StatRequest{ + Ref: &storageprovider.Reference{ + Spec: &storageprovider.Reference_Id{Id: r}, + }, + } + statResponse, err := s.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + return nil, err + } + return scope.AddResourceInfoScope(statResponse.Info, role, scopeMap) +} diff --git a/pkg/auth/scope/lightweight.go b/pkg/auth/scope/lightweight.go index aaf2942f3fd..8a92b68f9c2 100644 --- a/pkg/auth/scope/lightweight.go +++ b/pkg/auth/scope/lightweight.go @@ -18,100 +18,40 @@ package scope -// import ( -// "fmt" -// "strings" -// -// authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" -// provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" -// registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" -// types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" -// "github.com/cs3org/reva/pkg/errtypes" -// "github.com/cs3org/reva/pkg/utils" -// ) -// -// func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) { -// var r provider.ResourceInfo -// err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r) -// if err != nil { -// return false, err -// } -// -// switch v := resource.(type) { -// // Viewer role -// case *registry.GetStorageProvidersRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.StatRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.ListContainerRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.InitiateFileDownloadRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// -// // Editor role -// // TODO(ishank011): Add role checks, -// // need to return appropriate status codes in the ocs/ocdav layers. -// case *provider.CreateContainerRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.DeleteRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// case *provider.MoveRequest: -// return checkResourceInfo(&r, v.GetSource()) && checkResourceInfo(&r, v.GetDestination()), nil -// case *provider.InitiateFileUploadRequest: -// return checkResourceInfo(&r, v.GetRef()), nil -// -// case string: -// return checkPath(v), nil -// } -// -// return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource)) -// } -// -// func checkResourceInfo(inf *provider.ResourceInfo, ref *provider.Reference) bool { -// // ref: > -// if ref.GetId() != nil { -// return inf.Id.StorageId == ref.GetId().StorageId && inf.Id.OpaqueId == ref.GetId().OpaqueId -// } -// // ref: -// if strings.HasPrefix(ref.GetPath(), inf.Path) { -// return true -// } -// return false -// } -// -// func checkPath(path string) bool { -// paths := []string{ -// "/dataprovider", -// "/data", -// } -// for _, p := range paths { -// if strings.HasPrefix(path, p) { -// return true -// } -// } -// return false -// } -// -// // AddLightweightAccountScope adds the scope to allow access to lightweight user. -// func AddLightweightAccountScope(scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { -// ref := &provider.Reference{ -// Spec: &provider.Reference_Path{ -// Path: "/", -// }, -// } -// val, err := utils.MarshalProtoV1ToJSON(ref) -// if err != nil { -// return nil, err -// } -// if scopes == nil { -// scopes = make(map[string]*authpb.Scope) -// } -// scopes["resourceinfo:"+r.Id.String()] = &authpb.Scope{ -// Resource: &types.OpaqueEntry{ -// Decoder: "json", -// Value: val, -// }, -// Role: role, -// } -// return scopes, nil -// } +import ( + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/utils" +) + +func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) { + // Lightweight accounts have access to resources shared with them. + // These cannot be resolved from here, but need to be added to the scope from + // where the call to mint tokens is made. + return false, nil +} + +// AddLightweightAccountScope adds the scope to allow access to lightweight user. +func AddLightweightAccountScope(role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { + ref := &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: "/", + }, + } + val, err := utils.MarshalProtoV1ToJSON(ref) + if err != nil { + return nil, err + } + if scopes == nil { + scopes = make(map[string]*authpb.Scope) + } + scopes["lightweight"] = &authpb.Scope{ + Resource: &types.OpaqueEntry{ + Decoder: "json", + Value: val, + }, + Role: role, + } + return scopes, nil +} diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 2f4df8dba5b..4758e9468e7 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -32,6 +32,7 @@ var supportedScopes = map[string]Verifier{ "publicshare": publicshareScope, "resourceinfo": resourceinfoScope, "share": shareScope, + "lightweight": lightweightAccountScope, } // VerifyScope is the function to be called when dismantling tokens to check if diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go index f6204730ca3..def1de04f93 100644 --- a/pkg/auth/scope/share.go +++ b/pkg/auth/scope/share.go @@ -104,7 +104,7 @@ func checkSharePath(path string) bool { return false } -// AddPShareScope adds the scope to allow access to a user/group share and +// AddShareScope adds the scope to allow access to a user/group share and // the shared resource. func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { val, err := utils.MarshalProtoV1ToJSON(share)