Skip to content

Commit

Permalink
Check public share child items in the auth scope handler
Browse files Browse the repository at this point in the history
This solution is by far not pretty. But this is the smallest change I
could find to support apps in public shares without completely rewriting
the scopes code.
If we want to use any app in a public share the app needs an
authenticated context to access the files in the share. This is working
fine since we can authenticate using the share token. But the resulting
JWT contains a scope refering to the share resource. That means for a
folder share it contains the resource ID of the shared folder. But if
the app wants to access a specific file in the share it will request
that file by ID. Because of that the old way of the public scope checker
doesn't work because it would check if we are accessing the ID of the
shared folder and not if we are accessing any file in that folder.
Because of this problem I added two additional Stat requests so that I
can check if the requested file is inside the shared
folder.
  • Loading branch information
David Christofas committed Oct 7, 2021
1 parent 85a2935 commit fd15ae1
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 28 deletions.
17 changes: 9 additions & 8 deletions internal/grpc/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"github.com/bluele/gcache"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/auth/scope"
Expand Down Expand Up @@ -209,16 +210,21 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.
return nil, err
}

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
}

if sharedconf.SkipUserGroupsInToken() && fetchUserGroups {
groups, err := getUserGroups(ctx, u, gatewayAddr)
groups, err := getUserGroups(ctx, u, client)
if err != nil {
return nil, err
}
u.Groups = groups
}

// Check if access to the resource is in the scope of the token
ok, err := scope.VerifyScope(ctx, tokenScope, req)
ok, err := scope.VerifyScope(ctx, tokenScope, req, client, mgr)
if err != nil {
return nil, errtypes.InternalError("error verifying scope of access token")
}
Expand All @@ -233,18 +239,13 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.
return u, nil
}

func getUserGroups(ctx context.Context, u *userpb.User, gatewayAddr string) ([]string, error) {
func getUserGroups(ctx context.Context, u *userpb.User, client gatewayv1beta1.GatewayAPIClient) ([]string, error) {
if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil {
log := appctx.GetLogger(ctx)
log.Info().Msgf("user groups found in cache %s", u.Id.OpaqueId)
return groupsIf.([]string), nil
}

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
}

res, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
if err != nil {
return nil, errors.Wrap(err, "gateway: error calling GetUserGroups")
Expand Down
2 changes: 1 addition & 1 deletion internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err
}

// ensure access to the resource is allowed
ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path)
ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path, client, tokenManager)
if err != nil {
log.Error().Err(err).Msg("error verifying scope of access token")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
6 changes: 5 additions & 1 deletion pkg/auth/scope/lightweight.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@
package scope

import (
"context"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/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/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
)

func lightweightAccountScope(scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
func lightweightAccountScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger, _ gatewayv1beta1.GatewayAPIClient, _ token.Manager) (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.
Expand Down
58 changes: 47 additions & 11 deletions pkg/auth/scope/publicshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,30 @@
package scope

import (
"context"
"fmt"
"strings"

appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "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"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
"google.golang.org/grpc/metadata"

ctxpkg "github.com/cs3org/reva/pkg/ctx"
)

func publicshareScope(scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger, client gatewayv1beta1.GatewayAPIClient, mgr token.Manager) (bool, error) {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
Expand All @@ -44,25 +52,25 @@ func publicshareScope(scope *authpb.Scope, resource interface{}, logger *zerolog
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *provider.StatRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *provider.ListContainerRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *provider.InitiateFileDownloadRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil

// Editor role
// TODO(ishank011): Add role checks,
// need to return appropriate status codes in the ocs/ocdav layers.
case *provider.CreateContainerRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *provider.DeleteRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *provider.MoveRequest:
return checkStorageRef(&share, v.GetSource()) && checkStorageRef(&share, v.GetDestination()), nil
return checkStorageRef(ctx, &share, v.GetSource(), client, mgr) && checkStorageRef(ctx, &share, v.GetDestination(), client, mgr), nil
case *provider.InitiateFileUploadRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef(), client, mgr), nil
case *appregistry.GetAppProvidersRequest:
return utils.ResourceIDEqual(share.ResourceId, v.ResourceInfo.Id), nil
case *appprovider.OpenInAppRequest:
Expand All @@ -79,10 +87,38 @@ func publicshareScope(scope *authpb.Scope, resource interface{}, logger *zerolog
return false, errtypes.InternalError(msg)
}

func checkStorageRef(s *link.PublicShare, r *provider.Reference) bool {
func checkStorageRef(ctx context.Context, s *link.PublicShare, r *provider.Reference, client gatewayv1beta1.GatewayAPIClient, mgr token.Manager) bool {
// r: <resource_id:<storage_id:$storageID opaque_id:$opaqueID> path:$path > >
if r.ResourceId != nil && r.Path == "" { // path must be empty
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId())
if utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) {
return true
}
shareStat, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: s.ResourceId}})
if err != nil || shareStat.Status.Code != rpcv1beta1.Code_CODE_OK {
return false
}

userResp, err := client.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{Claim: "userid", Value: shareStat.Info.Owner.OpaqueId})
if err != nil || userResp.Status.Code != rpcv1beta1.Code_CODE_OK {
return false
}

scope, err := AddOwnerScope(map[string]*authpb.Scope{})
if err != nil {
return false
}
token, err := mgr.MintToken(ctx, userResp.User, scope)
if err != nil {
return false
}

ctx = metadata.AppendToOutgoingContext(context.Background(), ctxpkg.TokenHeader, token)
refStat, err := client.Stat(ctx, &provider.StatRequest{Ref: r})
if err != nil || refStat.Status.Code != rpcv1beta1.Code_CODE_OK {
return false
}

return strings.HasPrefix(refStat.Info.Path, shareStat.Info.Path)
}

// r: <path:"/public/$token" >
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/receivedshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
package scope

import (
"context"
"fmt"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/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/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
)

func receivedShareScope(scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger, _ gatewayv1beta1.GatewayAPIClient, _ token.Manager) (bool, error) {
var share collaboration.ReceivedShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/resourceinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@
package scope

import (
"context"
"fmt"
"strings"

appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
"github.com/rs/zerolog"

types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/token"
"github.com/cs3org/reva/pkg/utils"
)

func resourceinfoScope(scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
func resourceinfoScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger, _ gatewayv1beta1.GatewayAPIClient, _ token.Manager) (bool, error) {
var r provider.ResourceInfo
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &r)
if err != nil {
Expand Down
8 changes: 5 additions & 3 deletions pkg/auth/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import (
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/token"
"github.com/rs/zerolog"
)

// Verifier is the function signature which every scope verifier should implement.
type Verifier func(*authpb.Scope, interface{}, *zerolog.Logger) (bool, error)
type Verifier func(context.Context, *authpb.Scope, interface{}, *zerolog.Logger, gatewayv1beta1.GatewayAPIClient, token.Manager) (bool, error)

var supportedScopes = map[string]Verifier{
"user": userScope,
Expand All @@ -41,12 +43,12 @@ var supportedScopes = map[string]Verifier{

// VerifyScope is the function to be called when dismantling tokens to check if
// the token has access to a particular resource.
func VerifyScope(ctx context.Context, scopeMap map[string]*authpb.Scope, resource interface{}) (bool, error) {
func VerifyScope(ctx context.Context, scopeMap map[string]*authpb.Scope, resource interface{}, client gatewayv1beta1.GatewayAPIClient, mgr token.Manager) (bool, error) {
logger := appctx.GetLogger(ctx)
for k, scope := range scopeMap {
for s, f := range supportedScopes {
if strings.HasPrefix(k, s) {
if valid, err := f(scope, resource, logger); err == nil && valid {
if valid, err := f(ctx, scope, resource, logger, client, mgr); err == nil && valid {
return true, nil
}
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
package scope

import (
"context"
"fmt"
"strings"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/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/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
)

func shareScope(scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
func shareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger, _ gatewayv1beta1.GatewayAPIClient, _ token.Manager) (bool, error) {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion pkg/auth/scope/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@
package scope

import (
"context"

authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/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/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
)

func userScope(scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
func userScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger, _ gatewayv1beta1.GatewayAPIClient, _ token.Manager) (bool, error) {
// Always return true. Registered users can access all paths.
// TODO(ishank011): Add checks for read/write permissions.
return true, nil
Expand Down

0 comments on commit fd15ae1

Please sign in to comment.