diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md index 488c8b99ba..5052a056b6 100644 --- a/changelog/unreleased/bump-reva.md +++ b/changelog/unreleased/bump-reva.md @@ -5,3 +5,4 @@ bumps reva version https://github.com/owncloud/ocis/pull/7138 https://github.com/owncloud/ocis/pull/6427 https://github.com/owncloud/ocis/pull/7178 +https://github.com/owncloud/ocis/pull/7217 diff --git a/go.mod b/go.mod index 6f285d7544..1f04762a84 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.6.0 github.com/cs3org/go-cs3apis v0.0.0-20230516150832-730ac860c71d - github.com/cs3org/reva/v2 v2.16.1-0.20230904124812-2ebd3e92cdb9 + github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/egirna/icap-client v0.1.1 diff --git a/go.sum b/go.sum index 28884086df..7eb0df9a32 100644 --- a/go.sum +++ b/go.sum @@ -1013,8 +1013,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= -github.com/cs3org/reva/v2 v2.16.1-0.20230904124812-2ebd3e92cdb9 h1:YWkkoagYryRH56z5pw04Py0Ebg7spCZaWpsxjwGiNgU= -github.com/cs3org/reva/v2 v2.16.1-0.20230904124812-2ebd3e92cdb9/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g= +github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f h1:0mbvh+AvpYOp29R5LFgeqddyI0uKZnO/E3MyPEIaYdg= +github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/data/capabilities.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/data/capabilities.go index d2f5471f74..118e8b5dbc 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/data/capabilities.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/data/capabilities.go @@ -50,13 +50,14 @@ type CapabilitiesData struct { // Capabilities groups several capability aspects type Capabilities struct { - Core *CapabilitiesCore `json:"core" xml:"core"` - Checksums *CapabilitiesChecksums `json:"checksums" xml:"checksums"` - Files *CapabilitiesFiles `json:"files" xml:"files" mapstructure:"files"` - Dav *CapabilitiesDav `json:"dav" xml:"dav"` - FilesSharing *CapabilitiesFilesSharing `json:"files_sharing" xml:"files_sharing" mapstructure:"files_sharing"` - Spaces *Spaces `json:"spaces,omitempty" xml:"spaces,omitempty" mapstructure:"spaces"` - Graph *CapabilitiesGraph `json:"graph,omitempty" xml:"graph,omitempty" mapstructure:"graph"` + Core *CapabilitiesCore `json:"core" xml:"core"` + Checksums *CapabilitiesChecksums `json:"checksums" xml:"checksums"` + Files *CapabilitiesFiles `json:"files" xml:"files" mapstructure:"files"` + Dav *CapabilitiesDav `json:"dav" xml:"dav"` + FilesSharing *CapabilitiesFilesSharing `json:"files_sharing" xml:"files_sharing" mapstructure:"files_sharing"` + Spaces *Spaces `json:"spaces,omitempty" xml:"spaces,omitempty" mapstructure:"spaces"` + Graph *CapabilitiesGraph `json:"graph,omitempty" xml:"graph,omitempty" mapstructure:"graph"` + PasswordPolicies *CapabilitiesPasswordPolicies `json:"password_policies,omitempty" xml:"password_policies,omitempty" mapstructure:"password_policies"` Notifications *CapabilitiesNotifications `json:"notifications,omitempty" xml:"notifications,omitempty"` } @@ -85,6 +86,17 @@ type CapabilitiesGraph struct { Users CapabilitiesGraphUsers `json:"users" xml:"users" mapstructure:"users"` } +// CapabilitiesPasswordPolicies hold the password policies capabilities +type CapabilitiesPasswordPolicies struct { + MinCharacters int `json:"min_characters" xml:"min_characters" mapstructure:"min_characters"` + MaxCharacters int `json:"max_characters" xml:"max_characters" mapstructure:"max_characters"` + MinLowerCaseCharacters int `json:"min_lower_case_characters" xml:"min_lower_case_characters" mapstructure:"min_lower_case_characters"` + MinUpperCaseCharacters int `json:"min_upper_case_characters" xml:"min_upper_case_characters" mapstructure:"min_upper_case_characters"` + MinDigits int `json:"min_digits" xml:"min_digits" mapstructure:"min_digits"` + MinSpecialCharacters int `json:"min_special_characters" xml:"min_special_characters" mapstructure:"min_special_characters"` + SpecialCharacters string `json:"special_characters" xml:"special_characters" mapstructure:"special_characters"` +} + // CapabilitiesGraphUsers holds the graph user capabilities type CapabilitiesGraphUsers struct { ReadOnlyAttributes []string `json:"read_only_attributes" xml:"read_only_attributes" mapstructure:"read_only_attributes"` diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 907c233ead..bdb96e3d90 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -23,7 +23,6 @@ import ( "fmt" "net/http" "strconv" - "strings" permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -142,7 +141,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, } } - password := strings.TrimSpace(r.FormValue("password")) + password := r.FormValue("password") if h.enforcePassword(permKey) && len(password) == 0 { return nil, &ocsError{ Code: response.MetaBadRequest.StatusCode, @@ -150,6 +149,15 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Error: errors.New("missing required password"), } } + if len(password) > 0 { + if err := h.passwordValidator.Validate(password); err != nil { + return nil, &ocsError{ + Code: response.MetaBadRequest.StatusCode, + Message: "password validation failed", + Error: fmt.Errorf("password validation failed: %w", err), + } + } + } if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { // Single file shares should never have delete or create permissions @@ -460,7 +468,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar newPassword, ok := r.Form["password"] // enforcePassword if h.enforcePassword(permKey) { - if (!ok && !share.PasswordProtected) || (ok && len(strings.TrimSpace(newPassword[0])) == 0) { + if !ok && !share.PasswordProtected || ok && len(newPassword[0]) == 0 { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing required password", err) return } @@ -468,6 +476,13 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar // update or clear password if ok { + // skip validation if the clear password scenario + if len(newPassword[0]) > 0 { + if err := h.passwordValidator.Validate(newPassword[0]); err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, fmt.Errorf("missing required password %w", err).Error(), err) + return + } + } updatesFound = true logger.Info().Str("shares", "update").Msg("password updated") updates = append(updates, &link.UpdatePublicShareRequest_Update{ diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index afe888047b..562187df50 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -23,6 +23,7 @@ import ( "context" "encoding/json" "fmt" + "log" "mime" "net/http" "path" @@ -39,6 +40,7 @@ import ( 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" + "github.com/cs3org/reva/v2/pkg/password" "github.com/go-chi/chi/v5" "github.com/rs/zerolog" "google.golang.org/grpc/metadata" @@ -87,6 +89,7 @@ type Handler struct { deniable bool resharing bool publicPasswordEnforced passwordEnforced + passwordValidator password.Validator getClient GatewayClientGetter } @@ -122,7 +125,8 @@ func getCacheWarmupManager(c *config.Config) (sharecache.Warmup, error) { type GatewayClientGetter func() (gateway.GatewayAPIClient, error) // Init initializes this and any contained handlers -func (h *Handler) Init(c *config.Config) { +func (h *Handler) Init(c *config.Config) error { + var err error h.gatewayAddr = c.GatewaySvc h.machineAuthAPIKey = c.MachineAuthAPIKey h.storageRegistryAddr = c.StorageregistrySvc @@ -138,20 +142,29 @@ func (h *Handler) Init(c *config.Config) { h.deniable = c.EnableDenials h.resharing = resharing(c) h.publicPasswordEnforced = publicPwdEnforced(c) + h.passwordValidator, err = passwordPolicies(c) + if err != nil { + return err + } h.statCache = cache.GetStatCache(c.StatCacheStore, c.StatCacheNodes, c.StatCacheDatabase, "stat", time.Duration(c.StatCacheTTL)*time.Second, c.StatCacheSize) if c.CacheWarmupDriver != "" { cwm, err := getCacheWarmupManager(c) - if err == nil { - go h.startCacheWarmup(cwm) + if err != nil { + return err } + go h.startCacheWarmup(cwm) } h.getClient = h.getPoolClient + return nil } // InitWithGetter initializes the handler and adds the clientGetter func (h *Handler) InitWithGetter(c *config.Config, clientGetter GatewayClientGetter) { - h.Init(c) + err := h.Init(c) + if err != nil { + log.Fatal(err) + } h.getClient = clientGetter } @@ -1581,6 +1594,30 @@ func publicPwdEnforced(c *config.Config) passwordEnforced { return enf } +func passwordPolicies(c *config.Config) (password.Validator, error) { + var pv password.Validator + var err error + if c.Capabilities.Capabilities == nil || c.Capabilities.Capabilities.PasswordPolicies == nil { + pv, err = password.NewPasswordPolicies(0, 0, 0, 0, 0, "") + if err != nil { + return nil, fmt.Errorf("can't init the Password Policies %w", err) + } + return pv, nil + } + pv, err = password.NewPasswordPolicies( + c.Capabilities.Capabilities.PasswordPolicies.MinCharacters, + c.Capabilities.Capabilities.PasswordPolicies.MinLowerCaseCharacters, + c.Capabilities.Capabilities.PasswordPolicies.MinUpperCaseCharacters, + c.Capabilities.Capabilities.PasswordPolicies.MinDigits, + c.Capabilities.Capabilities.PasswordPolicies.MinSpecialCharacters, + c.Capabilities.Capabilities.PasswordPolicies.SpecialCharacters, + ) + if err != nil { + return nil, fmt.Errorf("can't init the Password Policies %w", err) + } + return pv, nil +} + // sufficientPermissions returns true if the `existing` permissions contain the `requested` permissions func sufficientPermissions(existing, requested *provider.ResourcePermissions, islink bool) bool { ep := conversions.RoleFromResourcePermissions(existing, islink).OCSPermissions() diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/ocs.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/ocs.go index 152bddca4a..09fcfa02d2 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/ocs.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/ocs.go @@ -104,7 +104,10 @@ func (s *svc) routerInit(log *zerolog.Logger) error { capabilitiesHandler.Init(s.c) usersHandler.Init(s.c) configHandler.Init(s.c) - sharesHandler.Init(s.c) + err := sharesHandler.Init(s.c) + if err != nil { + log.Fatal().Msg(err.Error()) + } shareesHandler.Init(s.c) s.router.Route("/v{version:(1|2)}.php", func(r chi.Router) { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go b/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go new file mode 100644 index 0000000000..9ebba7b783 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go @@ -0,0 +1,157 @@ +package password + +import ( + "errors" + "fmt" + "regexp" + "strings" + "unicode/utf8" +) + +// Validator describes the interface providing a password Validate method +type Validator interface { + Validate(str string) error +} + +// Policies represents a password validation rules +type Policies struct { + minCharacters int + minLowerCaseCharacters int + minUpperCaseCharacters int + minDigits int + minSpecialCharacters int + specialCharacters string + digitsRegexp *regexp.Regexp + specialCharactersRegexp *regexp.Regexp +} + +// NewPasswordPolicies returns a new NewPasswordPolicies instance +func NewPasswordPolicies(minCharacters, minLowerCaseCharacters, minUpperCaseCharacters, minDigits, minSpecialCharacters int, + specialCharacters string) (Validator, error) { + p := &Policies{ + minCharacters: minCharacters, + minLowerCaseCharacters: minLowerCaseCharacters, + minUpperCaseCharacters: minUpperCaseCharacters, + minDigits: minDigits, + minSpecialCharacters: minSpecialCharacters, + specialCharacters: specialCharacters, + } + + p.digitsRegexp = regexp.MustCompile("[0-9]") + if len(specialCharacters) > 0 { + var err error + p.specialCharactersRegexp, err = regexp.Compile(specialCharactersExp(specialCharacters)) + if err != nil { + return nil, err + } + } + return p, nil +} + +// Validate implements a password validation regarding the policy +func (s Policies) Validate(str string) error { + var allErr error + if !utf8.ValidString(str) { + return fmt.Errorf("the password contains invalid characters") + } + err := s.validateCharacters(str) + if err != nil { + allErr = errors.Join(allErr, err) + } + err = s.validateLowerCase(str) + if err != nil { + allErr = errors.Join(allErr, err) + } + err = s.validateUpperCase(str) + if err != nil { + allErr = errors.Join(allErr, err) + } + err = s.validateDigits(str) + if err != nil { + allErr = errors.Join(allErr, err) + } + err = s.validateSpecialCharacters(str) + if err != nil { + allErr = errors.Join(allErr, err) + } + if allErr != nil { + return allErr + } + return nil +} + +func (s Policies) validateCharacters(str string) error { + if s.count(str) < s.minCharacters { + return fmt.Errorf("at least %d characters are required", s.minCharacters) + } + return nil +} + +func (s Policies) validateLowerCase(str string) error { + if s.countLowerCaseCharacters(str) < s.minLowerCaseCharacters { + return fmt.Errorf("at least %d lowercase letters are required", s.minLowerCaseCharacters) + } + return nil +} + +func (s Policies) validateUpperCase(str string) error { + if s.countUpperCaseCharacters(str) < s.minUpperCaseCharacters { + return fmt.Errorf("at least %d uppercase letters are required", s.minUpperCaseCharacters) + } + return nil +} + +func (s Policies) validateDigits(str string) error { + if s.countDigits(str) < s.minDigits { + return fmt.Errorf("at least %d numbers are required", s.minDigits) + } + return nil +} + +func (s Policies) validateSpecialCharacters(str string) error { + if s.countSpecialCharacters(str) < s.minSpecialCharacters { + return fmt.Errorf("at least %d special characters are required. %s", s.minSpecialCharacters, s.specialCharacters) + } + return nil +} + +func (s Policies) count(str string) int { + return utf8.RuneCount([]byte(str)) +} + +func (s Policies) countLowerCaseCharacters(str string) int { + var count int + for _, c := range str { + if strings.ToLower(string(c)) == string(c) && strings.ToUpper(string(c)) != string(c) { + count++ + } + } + return count +} + +func (s Policies) countUpperCaseCharacters(str string) int { + var count int + for _, c := range str { + if strings.ToUpper(string(c)) == string(c) && strings.ToLower(string(c)) != string(c) { + count++ + } + } + return count +} + +func (s Policies) countDigits(str string) int { + return len(s.digitsRegexp.FindAllStringIndex(str, -1)) +} + +func (s Policies) countSpecialCharacters(str string) int { + if s.specialCharactersRegexp == nil { + return 0 + } + res := s.specialCharactersRegexp.FindAllStringIndex(str, -1) + return len(res) +} + +func specialCharactersExp(str string) string { + // escape the '-' character because it is a not meta-characters but, they are special inside of [] + return "[" + strings.ReplaceAll(regexp.QuoteMeta(str), "-", `\-`) + "]" +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go b/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go index 90547443fa..662a0b32f9 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go @@ -2,27 +2,30 @@ package utils import ( "context" + "encoding/json" + "errors" "fmt" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "google.golang.org/grpc/metadata" ) -// GetUser gets the specified user -func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) { - getUserResponse, err := gwc.GetUser(context.Background(), &user.GetUserRequest{UserId: userID}) - if err != nil { - return nil, err - } - if getUserResponse.Status.Code != rpc.Code_CODE_OK { - return nil, fmt.Errorf("error getting user: %s", getUserResponse.Status.Message) - } +// SpaceRole defines the user role on space +type SpaceRole func(*storageprovider.ResourcePermissions) bool - return getUserResponse.GetUser(), nil -} +// Possible roles in spaces +var ( + AllRole SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return true } + ViewerRole SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.Stat } + EditorRole SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.InitiateFileUpload } + ManagerRole SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.DenyGrant } +) // GetServiceUserContext returns an authenticated context of the given service user func GetServiceUserContext(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) { @@ -41,3 +44,174 @@ func GetServiceUserContext(serviceUserID string, gwc gateway.GatewayAPIClient, s return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil } + +// GetUser gets the specified user +func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) { + getUserResponse, err := gwc.GetUser(context.Background(), &user.GetUserRequest{UserId: userID}) + if err != nil { + return nil, err + } + if getUserResponse.Status.Code != rpc.Code_CODE_OK { + return nil, fmt.Errorf("error getting user: %s", getUserResponse.Status.Message) + } + + return getUserResponse.GetUser(), nil +} + +// GetSpace returns the given space +func GetSpace(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient) (*storageprovider.StorageSpace, error) { + res, err := gwc.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID)) + if err != nil { + return nil, err + } + + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + return nil, fmt.Errorf("error while getting space: (%v) %s", res.GetStatus().GetCode(), res.GetStatus().GetMessage()) + } + + if len(res.StorageSpaces) == 0 { + return nil, fmt.Errorf("error getting storage space %s: no space returned", spaceID) + } + + return res.StorageSpaces[0], nil +} + +// GetGroupMembers returns all members of the given group +func GetGroupMembers(ctx context.Context, groupID string, gwc gateway.GatewayAPIClient) ([]string, error) { + r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupID}}) + if err != nil { + return nil, err + } + + if r.GetStatus().GetCode() != rpc.Code_CODE_OK { + return nil, fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode()) + } + + users := make([]string, 0, len(r.GetGroup().GetMembers())) + for _, u := range r.GetGroup().GetMembers() { + users = append(users, u.GetOpaqueId()) + } + + return users, nil +} + +// ResolveID returns either the given userID or all members of the given groupID (if userID is nil) +func ResolveID(ctx context.Context, userid *user.UserId, groupid *group.GroupId, gwc gateway.GatewayAPIClient) ([]string, error) { + if userid != nil { + return []string{userid.GetOpaqueId()}, nil + } + + if ctx == nil { + return nil, errors.New("need ctx to resolve group id") + } + + return GetGroupMembers(ctx, groupid.GetOpaqueId(), gwc) +} + +// GetSpaceMembers returns all members of the given space that have at least the given role. `nil` role will be interpreted as all +func GetSpaceMembers(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient, role SpaceRole) ([]string, error) { + if ctx == nil { + return nil, errors.New("need authenticated context to find space members") + } + + space, err := GetSpace(ctx, spaceID, gwc) + if err != nil { + return nil, err + } + + var users []string + switch space.SpaceType { + case "personal": + users = append(users, space.GetOwner().GetId().GetOpaqueId()) + case "project": + if users, err = gatherProjectSpaceMembers(ctx, space, gwc, role); err != nil { + return nil, err + } + default: + // TODO: shares? other space types? + return nil, fmt.Errorf("unsupported space type: %s", space.SpaceType) + } + + return users, nil +} + +// GetResourceByID is a convenience method to get a resource by its resourceID +func GetResourceByID(ctx context.Context, resourceid *storageprovider.ResourceId, gwc gateway.GatewayAPIClient) (*storageprovider.ResourceInfo, error) { + return GetResource(ctx, &storageprovider.Reference{ResourceId: resourceid}, gwc) +} + +// GetResource returns a resource by reference +func GetResource(ctx context.Context, ref *storageprovider.Reference, gwc gateway.GatewayAPIClient) (*storageprovider.ResourceInfo, error) { + res, err := gwc.Stat(ctx, &storageprovider.StatRequest{Ref: ref}) + if err != nil { + return nil, err + } + + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + return nil, fmt.Errorf("unexpected status code while getting space: %v", res.GetStatus().GetCode()) + } + + return res.GetInfo(), nil +} + +func gatherProjectSpaceMembers(ctx context.Context, space *storageprovider.StorageSpace, gwc gateway.GatewayAPIClient, role SpaceRole) ([]string, error) { + var permissionsMap map[string]*storageprovider.ResourcePermissions + if err := ReadJSONFromOpaque(space.GetOpaque(), "grants", &permissionsMap); err != nil { + return nil, err + } + + groupsMap := make(map[string]struct{}) + if opaqueGroups, ok := space.Opaque.Map["groups"]; ok { + _ = json.Unmarshal(opaqueGroups.GetValue(), &groupsMap) + } + + if role == nil { + role = AllRole + } + + // we use a map to avoid duplicates + usermap := make(map[string]struct{}) + for id, perm := range permissionsMap { + if !role(perm) { + continue + } + + if _, isGroup := groupsMap[id]; !isGroup { + usermap[id] = struct{}{} + continue + } + + usrs, err := GetGroupMembers(ctx, id, gwc) + if err != nil { + // TODO: continue? + return nil, err + } + + for _, u := range usrs { + usermap[u] = struct{}{} + } + } + + users := make([]string, 0, len(usermap)) + for id := range usermap { + users = append(users, id) + } + + return users, nil +} + +func listStorageSpaceRequest(spaceID string) *storageprovider.ListStorageSpacesRequest { + return &storageprovider.ListStorageSpacesRequest{ + Opaque: AppendPlainToOpaque(nil, "unrestricted", "true"), + Filters: []*storageprovider.ListStorageSpacesRequest_Filter{ + { + Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID, + Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{ + Id: &storageprovider.StorageSpaceId{ + OpaqueId: spaceID, + }, + }, + }, + }, + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 20f7173fe1..8f3af3a0f5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -356,7 +356,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.16.1-0.20230904124812-2ebd3e92cdb9 +# github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f ## explicit; go 1.20 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime @@ -554,6 +554,7 @@ github.com/cs3org/reva/v2/pkg/ocm/share/manager/loader github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud github.com/cs3org/reva/v2/pkg/ocm/share/manager/registry github.com/cs3org/reva/v2/pkg/ocm/share/sender +github.com/cs3org/reva/v2/pkg/password github.com/cs3org/reva/v2/pkg/permission github.com/cs3org/reva/v2/pkg/permission/manager/demo github.com/cs3org/reva/v2/pkg/permission/manager/loader