Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling apps to work in public shares. #2143

Merged
merged 9 commits into from
Oct 15, 2021
Merged
6 changes: 6 additions & 0 deletions changelog/unreleased/public-share-app-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Change: Make apps able to work with public shares

Public share receivers were not possible to use apps in public shares because the apps couldn't load the files in the public shares. This has now been made possible by changing the scope checks for public shares.

https://github.com/owncloud/ocis/issues/2479
https://github.com/cs3org/reva/pull/2143
19 changes: 10 additions & 9 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,42 +210,42 @@ 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(tokenScope, req)
ok, err := scope.VerifyScope(ctx, tokenScope, req)
if err != nil {
return nil, errtypes.InternalError("error verifying scope of access token")
}
if ok {
return u, nil
}

if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr); err != nil {
if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr, mgr); err != nil {
return nil, err
}

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
103 changes: 76 additions & 27 deletions internal/grpc/interceptors/auth/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,33 @@ import (
"context"
"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"
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"
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"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"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/token"
"github.com/cs3org/reva/pkg/utils"
"google.golang.org/grpc/metadata"
)

func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string) error {
func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string, mgr token.Manager) error {
log := appctx.GetLogger(ctx)
client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return err
}

if ref, ok := extractRef(req); ok {
// Check if req is of type *provider.Reference_Path
Expand All @@ -53,7 +65,7 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
if err != nil {
continue
}
if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok {
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok {
return nil
}

Expand All @@ -63,37 +75,35 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
if err != nil {
continue
}
if ok, err := checkResourcePath(ctx, ref, share.ResourceId, gatewayAddr); err == nil && ok {
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); 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 {
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); 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())
// The request might be coming from
// - a resource present inside a shared folder, or
// - a share created for a lightweight account after the token was minted.

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return err
}
for k := range tokenScope {
if strings.HasPrefix(k, "lightweight") {
log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId())
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")
Expand All @@ -104,6 +114,15 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
return nil
}
}
} else if strings.HasPrefix(k, "publicshare") {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share)
if err != nil {
continue
}
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok {
return nil
}
}
}
}
Expand Down Expand Up @@ -140,32 +159,50 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
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
}

func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) (bool, error) {
// 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)
statResponse, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: parent}})
if err != nil {
return false, err
}
if statResponse.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
}
parentPath := statResponse.Info.Path

childPath := ref.GetPath()
if childPath == "" {
// We mint a token as the owner of the public share and try to stat the reference
// TODO(ishank011): We need to find a better alternative to this

userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner})
if err != nil || userResp.Status.Code != rpc.Code_CODE_OK {
return false, err
}

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

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
childStat, err := client.Stat(ctx, &provider.StatRequest{Ref: ref})
if err != nil {
return false, err
}
if childStat.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
}
childPath = statResponse.Info.Path
}
return false, nil

return strings.HasPrefix(childPath, parentPath), nil

}

func extractRef(req interface{}) (*provider.Reference, bool) {
Expand All @@ -186,6 +223,18 @@ func extractRef(req interface{}) (*provider.Reference, bool) {
return v.GetRef(), true
case *provider.InitiateFileUploadRequest:
return v.GetRef(), true
case *appprovider.OpenInAppRequest:
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
case *gateway.OpenInAppRequest:
return v.GetRef(), true
case *provider.SetArbitraryMetadataRequest:
return v.GetRef(), true
case *provider.UnsetArbitraryMetadataRequest:
return v.GetRef(), true

// App provider requests
case *appregistry.GetAppProvidersRequest:
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
}
return nil, false
}
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(tokenScope, r.URL.Path)
ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path)
if err != nil {
log.Error().Err(err).Msg("error verifying scope of access token")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
5 changes: 4 additions & 1 deletion pkg/auth/scope/lightweight.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
package scope

import (
"context"

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"
"github.com/rs/zerolog"
)

func lightweightAccountScope(scope *authpb.Scope, resource interface{}) (bool, error) {
func lightweightAccountScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (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
39 changes: 28 additions & 11 deletions pkg/auth/scope/publicshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@
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"
userv1beta1 "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"
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"
"github.com/rs/zerolog"
)

func publicshareScope(scope *authpb.Scope, resource interface{}) (bool, error) {
func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
Expand All @@ -41,36 +45,49 @@ func publicshareScope(scope *authpb.Scope, resource interface{}) (bool, error) {
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.StatRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &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 checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.DeleteRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.MoveRequest:
return checkStorageRef(&share, v.GetSource()) && checkStorageRef(&share, v.GetDestination()), nil
return checkStorageRef(ctx, &share, v.GetSource()) && checkStorageRef(ctx, &share, v.GetDestination()), nil
case *provider.InitiateFileUploadRequest:
return checkStorageRef(&share, v.GetRef()), nil
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.SetArbitraryMetadataRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.UnsetArbitraryMetadataRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil

// App provider requests
case *appregistry.GetDefaultAppProviderForMimeTypeRequest:
return true, nil

case *userv1beta1.GetUserByClaimRequest:
return true, nil

case *link.GetPublicShareRequest:
return checkPublicShareRef(&share, v.GetRef()), nil
case string:
return checkResourcePath(v), nil
}

return false, errtypes.InternalError(fmt.Sprintf("resource type assertion failed: %+v", resource))
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "publicshareScope").Msg(msg)
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) 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())
Expand Down
Loading