Skip to content
This repository was archived by the owner on Jul 16, 2024. It is now read-only.

Commit 9761dd3

Browse files
gmgigi96vascoguita
authored andcommitted
Lightweight accounts (cs3org#3348)
1 parent 2b2c9da commit 9761dd3

File tree

10 files changed

+435
-206
lines changed

10 files changed

+435
-206
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Enhancement: Revamp lightweigth accounts
2+
3+
Re-implements the lighweight account scope check, making
4+
it more efficient.
5+
Also, the ACLs for the EOS storage driver for the lw
6+
accounts are set atomically.
7+
8+
https://github.com/cs3org/reva/pull/3348

internal/grpc/interceptors/auth/scope.go

+112-62
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package auth
2020

2121
import (
2222
"context"
23+
"path/filepath"
2324
"strings"
2425
"time"
2526

@@ -78,77 +79,136 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
7879
return nil
7980
}
8081

81-
case strings.HasPrefix(k, "lightweight"):
82-
if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil {
83-
return nil
84-
}
8582
}
8683
log.Err(err).Msgf("error resolving reference %s under scope %+v", ref.String(), k)
8784
}
8885

89-
} else if ref, ok := extractShareRef(req); ok {
90-
// It's a share ref
91-
// The request might be coming from a share created for a lightweight account
92-
// after the token was minted.
93-
log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref.String())
94-
for k := range tokenScope {
95-
if strings.HasPrefix(k, "lightweight") {
96-
// Check if this ID is cached
97-
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + ref.GetId().OpaqueId
98-
if _, err := scopeExpansionCache.Get(key); err == nil {
99-
return nil
100-
}
86+
}
10187

102-
shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
103-
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
104-
log.Warn().Err(err).Msg("error listing received shares")
105-
continue
106-
}
107-
for _, s := range shares.Shares {
108-
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + s.Share.Id.OpaqueId
109-
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)
110-
111-
if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId {
112-
return nil
113-
}
114-
if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) &&
115-
utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) {
116-
return nil
117-
}
118-
}
119-
}
120-
}
88+
if checkLightweightScope(ctx, req, tokenScope, client) {
89+
return nil
12190
}
12291

12392
return errtypes.PermissionDenied("access to resource not allowed within the assigned scope")
12493
}
12594

126-
func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
127-
// Check if this ref is cached
128-
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref)
129-
if _, err := scopeExpansionCache.Get(key); err == nil {
130-
return nil
95+
func hasLightweightScope(tokenScope map[string]*authpb.Scope) bool {
96+
for scope := range tokenScope {
97+
if strings.HasPrefix(scope, "lightweight") {
98+
return true
99+
}
131100
}
101+
return false
102+
}
132103

133-
shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
134-
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
135-
return errtypes.InternalError("error listing received shares")
104+
func checkLightweightScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, client gateway.GatewayAPIClient) bool {
105+
if !hasLightweightScope(tokenScope) {
106+
return false
136107
}
137108

138-
for _, share := range shares.Shares {
139-
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + resourceid.OwnCloudResourceIDWrap(share.Share.ResourceId)
140-
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)
109+
switch r := req.(type) {
110+
// Viewer role
111+
case *registry.GetStorageProvidersRequest:
112+
return true
113+
case *provider.StatRequest:
114+
return true
115+
case *provider.ListContainerRequest:
116+
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
117+
ListContainer: true,
118+
})
119+
case *provider.InitiateFileDownloadRequest:
120+
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
121+
InitiateFileDownload: true,
122+
})
123+
case *appprovider.OpenInAppRequest:
124+
return hasPermissions(ctx, client, &provider.Reference{ResourceId: r.ResourceInfo.Id}, &provider.ResourcePermissions{
125+
InitiateFileDownload: true,
126+
})
127+
case *gateway.OpenInAppRequest:
128+
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
129+
InitiateFileDownload: true,
130+
})
141131

142-
if ref.ResourceId != nil && utils.ResourceIDEqual(share.Share.ResourceId, ref.ResourceId) {
143-
return nil
132+
// Editor role
133+
case *provider.CreateContainerRequest:
134+
parent, err := parentOfResource(ctx, client, r.GetRef())
135+
if err != nil {
136+
return false
137+
}
138+
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
139+
CreateContainer: true,
140+
})
141+
case *provider.TouchFileRequest:
142+
parent, err := parentOfResource(ctx, client, r.GetRef())
143+
if err != nil {
144+
return false
144145
}
145-
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
146-
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
147-
return nil
146+
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
147+
InitiateFileDownload: true,
148+
})
149+
case *provider.DeleteRequest:
150+
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
151+
InitiateFileDownload: true,
152+
})
153+
case *provider.MoveRequest:
154+
return hasPermissions(ctx, client, r.Source, &provider.ResourcePermissions{
155+
InitiateFileDownload: true,
156+
}) && hasPermissions(ctx, client, r.Destination, &provider.ResourcePermissions{
157+
InitiateFileUpload: true,
158+
})
159+
case *provider.InitiateFileUploadRequest:
160+
parent, err := parentOfResource(ctx, client, r.GetRef())
161+
if err != nil {
162+
return false
148163
}
164+
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
165+
InitiateFileUpload: true,
166+
})
149167
}
150168

151-
return errtypes.PermissionDenied("request is not for a nested resource")
169+
return false
170+
}
171+
172+
func parentOfResource(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference) (*provider.Reference, error) {
173+
if utils.IsAbsolutePathReference(ref) {
174+
parent := filepath.Dir(ref.GetPath())
175+
info, err := stat(ctx, client, &provider.Reference{Path: parent})
176+
if err != nil {
177+
return nil, err
178+
}
179+
return &provider.Reference{ResourceId: info.Id}, nil
180+
}
181+
182+
info, err := stat(ctx, client, ref)
183+
if err != nil {
184+
return nil, err
185+
}
186+
return &provider.Reference{ResourceId: info.ParentId}, nil
187+
}
188+
189+
func stat(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference) (*provider.ResourceInfo, error) {
190+
statRes, err := client.Stat(ctx, &provider.StatRequest{
191+
Ref: ref,
192+
})
193+
194+
switch {
195+
case err != nil:
196+
return nil, err
197+
case statRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
198+
return nil, errtypes.NotFound(statRes.Status.Message)
199+
case statRes.Status.Code != rpc.Code_CODE_OK:
200+
return nil, errtypes.InternalError(statRes.Status.Message)
201+
}
202+
203+
return statRes.Info, nil
204+
}
205+
206+
func hasPermissions(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference, permissionSet *provider.ResourcePermissions) bool {
207+
info, err := stat(ctx, client, ref)
208+
if err != nil {
209+
return false
210+
}
211+
return utils.HasPermissions(info.PermissionSet, permissionSet)
152212
}
153213

154214
func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
@@ -329,16 +389,6 @@ func extractRef(req interface{}, tokenScope map[string]*authpb.Scope) (*provider
329389
return nil, false
330390
}
331391

332-
func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
333-
switch v := req.(type) {
334-
case *collaboration.GetReceivedShareRequest:
335-
return v.GetRef(), true
336-
case *collaboration.UpdateReceivedShareRequest:
337-
return &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: v.GetShare().GetShare().GetId()}}, true
338-
}
339-
return nil, false
340-
}
341-
342392
func getRefKey(ref *provider.Reference) string {
343393
if ref.Path != "" {
344394
return ref.Path

pkg/auth/scope/lightweight.go

+2
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ func checkLightweightPath(path string) bool {
5656
"/ocs/v1.php/cloud/user",
5757
"/remote.php/webdav",
5858
"/remote.php/dav/files",
59+
"/thumbnails",
5960
"/app/open",
6061
"/app/new",
6162
"/archiver",
6263
"/dataprovider",
6364
"/data",
65+
"/app/open",
6466
}
6567
for _, p := range paths {
6668
if strings.HasPrefix(path, p) {

pkg/eosclient/eosbinary/eosbinary.go

+28-63
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import (
4545

4646
const (
4747
versionPrefix = ".sys.v#."
48-
lwShareAttrKey = "reva.lwshare"
4948
userACLEvalKey = "eval.useracl"
5049
favoritesKey = "http://owncloud.org/ns/favorite"
5150
)
@@ -280,30 +279,6 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat
280279
return err
281280
}
282281

283-
if a.Type == acl.TypeLightweight {
284-
sysACL := ""
285-
aclStr, ok := finfo.Attrs["sys."+lwShareAttrKey]
286-
if ok {
287-
acls, err := acl.Parse(aclStr, acl.ShortTextForm)
288-
if err != nil {
289-
return err
290-
}
291-
err = acls.SetEntry(a.Type, a.Qualifier, a.Permissions)
292-
if err != nil {
293-
return err
294-
}
295-
sysACL = acls.Serialize()
296-
} else {
297-
sysACL = a.CitrineSerialize()
298-
}
299-
sysACLAttr := &eosclient.Attribute{
300-
Type: eosclient.SystemAttr,
301-
Key: lwShareAttrKey,
302-
Val: sysACL,
303-
}
304-
return c.SetAttr(ctx, auth, sysACLAttr, false, finfo.IsDir, path)
305-
}
306-
307282
sysACL := a.CitrineSerialize()
308283
args := []string{"acl", "--sys"}
309284
if finfo.IsDir {
@@ -330,30 +305,6 @@ func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authori
330305
return err
331306
}
332307

333-
if a.Type == acl.TypeLightweight {
334-
sysACL := ""
335-
aclStr, ok := finfo.Attrs["sys."+lwShareAttrKey]
336-
if ok {
337-
acls, err := acl.Parse(aclStr, acl.ShortTextForm)
338-
if err != nil {
339-
return err
340-
}
341-
acls.DeleteEntry(a.Type, a.Qualifier)
342-
if err != nil {
343-
return err
344-
}
345-
sysACL = acls.Serialize()
346-
} else {
347-
sysACL = a.CitrineSerialize()
348-
}
349-
sysACLAttr := &eosclient.Attribute{
350-
Type: eosclient.SystemAttr,
351-
Key: lwShareAttrKey,
352-
Val: sysACL,
353-
}
354-
return c.SetAttr(ctx, auth, sysACLAttr, false, finfo.IsDir, path)
355-
}
356-
357308
sysACL := a.CitrineSerialize()
358309
args := []string{"acl", "--sys"}
359310
if finfo.IsDir {
@@ -645,6 +596,34 @@ func (c *Client) GetAttr(ctx context.Context, auth eosclient.Authorization, key,
645596
return attr, nil
646597
}
647598

599+
// GetAttrs returns all the attributes of a resource
600+
func (c *Client) GetAttrs(ctx context.Context, auth eosclient.Authorization, path string) ([]*eosclient.Attribute, error) {
601+
info, err := c.getRawFileInfoByPath(ctx, auth, path)
602+
if err != nil {
603+
return nil, err
604+
}
605+
if !info.IsDir {
606+
path = getVersionFolder(path)
607+
}
608+
609+
args := []string{"attr", "ls", path}
610+
attrOut, _, err := c.executeEOS(ctx, args, auth)
611+
if err != nil {
612+
return nil, err
613+
}
614+
615+
attrsStr := strings.Split(attrOut, "\n")
616+
attrs := make([]*eosclient.Attribute, 0, len(attrsStr))
617+
for _, line := range attrsStr {
618+
attr, err := deserializeAttribute(line)
619+
if err != nil {
620+
return nil, err
621+
}
622+
attrs = append(attrs, attr)
623+
}
624+
return attrs, nil
625+
}
626+
648627
func deserializeAttribute(attrStr string) (*eosclient.Attribute, error) {
649628
// the string is in the form sys.forced.checksum="adler"
650629
keyValue := strings.SplitN(strings.TrimSpace(attrStr), "=", 2) // keyValue = ["sys.forced.checksum", "\"adler\""]
@@ -1222,20 +1201,6 @@ func (c *Client) mapToFileInfo(ctx context.Context, kv, attrs map[string]string,
12221201
}
12231202
}
12241203

1225-
// Read lightweight ACLs recognized by the sys.reva.lwshare attr
1226-
if lwACLStr, ok := attrs["sys."+lwShareAttrKey]; ok {
1227-
lwAcls, err := acl.Parse(lwACLStr, acl.ShortTextForm)
1228-
if err != nil {
1229-
return nil, err
1230-
}
1231-
for _, e := range lwAcls.Entries {
1232-
err = sysACL.SetEntry(e.Type, e.Qualifier, e.Permissions)
1233-
if err != nil {
1234-
return nil, err
1235-
}
1236-
}
1237-
}
1238-
12391204
// Read the favorite attr
12401205
if parseFavoriteKey {
12411206
parseAndSetFavoriteAttr(ctx, attrs)

pkg/eosclient/eosclient.go

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type EOSClient interface {
3939
SetAttr(ctx context.Context, auth Authorization, attr *Attribute, errorIfExists, recursive bool, path string) error
4040
UnsetAttr(ctx context.Context, auth Authorization, attr *Attribute, recursive bool, path string) error
4141
GetAttr(ctx context.Context, auth Authorization, key, path string) (*Attribute, error)
42+
GetAttrs(ctx context.Context, auth Authorization, path string) ([]*Attribute, error)
4243
GetQuota(ctx context.Context, username string, rootAuth Authorization, path string) (*QuotaInfo, error)
4344
SetQuota(ctx context.Context, rooAuth Authorization, info *SetQuotaInfo) error
4445
Touch(ctx context.Context, auth Authorization, path string) error

0 commit comments

Comments
 (0)