Skip to content

Commit

Permalink
Fix group capabilities (#4400)
Browse files Browse the repository at this point in the history
  • Loading branch information
javfg authored Dec 12, 2023
1 parent 1157337 commit b566d15
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 42 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/group-capabilities-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Bugfix: Fix group-based capabilities

The group-based capabilities require an authenticated endpoint, as we must query
the logged-in user's groups to get those. This PR moves them to the `getSelf`
endpoint in the user handler.

https://github.com/cs3org/reva/pull/4400
7 changes: 0 additions & 7 deletions internal/http/services/owncloud/ocs/data/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ type Capabilities struct {
Spaces *Spaces `json:"spaces,omitempty" mapstructure:"spaces" xml:"spaces,omitempty"`

Notifications *CapabilitiesNotifications `json:"notifications,omitempty" xml:"notifications,omitempty"`

GroupBased *CapabilitiesGroupBased `json:"group_based" mapstructure:"group_based" xml:"group_based"`
}

// Spaces lets a service configure its advertised options related to Storage Spaces.
Expand Down Expand Up @@ -228,11 +226,6 @@ type CapabilitiesNotifications struct {
Endpoints []string `json:"ocs-endpoints,omitempty" mapstructure:"endpoints" xml:"ocs-endpoints>element,omitempty"`
}

// CapabilitiesGroupBased holds capabilities based on the groups a user belongs to.
type CapabilitiesGroupBased struct {
Capabilities []string `json:"capabilities" mapstructure:"capabilities" xml:"capabilities"`
}

// Version holds version information.
type Version struct {
Major int `json:"major" xml:"major"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

// Package capabilities contains the capabilities handler.
package capabilities

import (
Expand All @@ -28,18 +29,16 @@ import (

// Handler renders the capability endpoint.
type Handler struct {
c data.CapabilitiesData
defaultUploadProtocol string
userAgentChunkingMap map[string]string
groupBasedCapabilities map[string][]string
c data.CapabilitiesData
defaultUploadProtocol string
userAgentChunkingMap map[string]string
}

// Init initializes this and any contained handlers.
func (h *Handler) Init(c *config.Config) {
h.c = c.Capabilities
h.defaultUploadProtocol = c.DefaultUploadProtocol
h.userAgentChunkingMap = c.UserAgentChunkingMap
h.groupBasedCapabilities = c.GroupBasedCapabilities

// capabilities
if h.c.Capabilities == nil {
Expand Down Expand Up @@ -213,12 +212,6 @@ func (h *Handler) Init(c *config.Config) {
// h.c.Capabilities.Notifications.Endpoints = []string{"list", "get", "delete"}
// }

// group based

if h.c.Capabilities.GroupBased == nil {
h.c.Capabilities.GroupBased = &data.CapabilitiesGroupBased{}
}

// version

if h.c.Version == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestMarshal(t *testing.T) {
},
}

jsonExpect := `{"capabilities":{"core":null,"checksums":null,"files":null,"dav":null,"files_sharing":{"api_enabled":true,"resharing":false,"resharing_default":false,"deny_access":false,"group_sharing":false,"auto_accept_share":false,"share_with_group_members_only":false,"share_with_membership_groups_only":false,"can_rename":false,"allow_custom":false,"search_min_length":0,"default_permissions":0,"user_enumeration":null,"federation":null,"public":null,"user":null},"group_based":null},"version":null}`
jsonExpect := `{"capabilities":{"core":null,"checksums":null,"files":null,"dav":null,"files_sharing":{"api_enabled":true,"resharing":false,"resharing_default":false,"deny_access":false,"group_sharing":false,"auto_accept_share":false,"share_with_group_members_only":false,"share_with_membership_groups_only":false,"can_rename":false,"allow_custom":false,"search_min_length":0,"default_permissions":0,"user_enumeration":null,"federation":null,"public":null,"user":null}},"version":null}`
xmlExpect := `<CapabilitiesData><capabilities><files_sharing><api_enabled>1</api_enabled><resharing>0</resharing><resharing_default>0</resharing_default><deny_access>0</deny_access><group_sharing>0</group_sharing><auto_accept_share>0</auto_accept_share><share_with_group_members_only>0</share_with_group_members_only><share_with_membership_groups_only>0</share_with_membership_groups_only><can_rename>0</can_rename><allow_custom>0</allow_custom><search_min_length>0</search_min_length><default_permissions>0</default_permissions></files_sharing></capabilities></CapabilitiesData>`

jsonData, err := json.Marshal(&cd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ import (
"strings"

"github.com/cs3org/reva/internal/http/services/owncloud/ocs/data"
"github.com/cs3org/reva/pkg/appctx"

"github.com/juliangruber/go-intersect"
)

type chunkProtocol string
Expand All @@ -36,7 +33,7 @@ var (
chunkTUS chunkProtocol = "tus"
)

func (h *Handler) getCapabilitiesForUserAgent(ctx context.Context, userAgent string) data.CapabilitiesData {
func (h *Handler) getCapabilitiesForUserAgent(_ context.Context, userAgent string) data.CapabilitiesData {
// Creating a copy of the capabilities struct is less expensive than taking a lock
c := *h.c.Capabilities
if userAgent != "" {
Expand All @@ -48,23 +45,9 @@ func (h *Handler) getCapabilitiesForUserAgent(ctx context.Context, userAgent str
}
}

c.GroupBased.Capabilities = []string{}
for capability, groups := range h.groupBasedCapabilities {
if ctxUserBelongsToGroups(ctx, groups) {
c.GroupBased.Capabilities = append(c.GroupBased.Capabilities, capability)
}
}

return data.CapabilitiesData{Capabilities: &c, Version: h.c.Version}
}

func ctxUserBelongsToGroups(ctx context.Context, groups []string) bool {
if user, ok := appctx.ContextGetUser(ctx); ok {
return len(intersect.Simple(groups, user.Groups)) > 0
}
return false
}

func setCapabilitiesForChunkProtocol(cp chunkProtocol, c *data.Capabilities) {
switch cp {
case chunkV1:
Expand Down
42 changes: 37 additions & 5 deletions internal/http/services/owncloud/ocs/handlers/cloud/users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

// Package users contains the users handler.
package users

import (
Expand All @@ -33,16 +34,19 @@ import (
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/go-chi/chi/v5"
"github.com/juliangruber/go-intersect"
)

// Handler renders user data for the user id given in the url path.
type Handler struct {
gatewayAddr string
gatewayAddr string
capabilitiesGroupBased map[string][]string
}

// Init initializes this and any contained handlers.
func (h *Handler) Init(c *config.Config) {
h.gatewayAddr = c.GatewaySvc
h.capabilitiesGroupBased = c.GroupBasedCapabilities
}

// GetGroups handles GET requests on /cloud/users/groups
Expand All @@ -68,9 +72,13 @@ type Users struct {
UserType string `json:"user-type" xml:"user-type"`
// FIXME home should never be exposed ... even in oc 10
// home
TwoFactorAuthEnabled bool `json:"two_factor_auth_enabled" xml:"two_factor_auth_enabled"`
TwoFactorAuthEnabled bool `json:"two_factor_auth_enabled" xml:"two_factor_auth_enabled"`
GroupBasedCapabilities CapabilitiesGroupBased `json:"group-capabilities" xml:"group-capabilities"`
}

// CapabilitiesGroupBased holds capabilities based on the groups a user belongs to.
type CapabilitiesGroupBased []string

// Groups holds group data.
type Groups struct {
Groups []string `json:"groups" xml:"groups>element"`
Expand Down Expand Up @@ -134,6 +142,25 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) {
relative = float32(float64(used)/float64(total)) * 100
}

gw, err := pool.GetGatewayServiceClient(pool.Endpoint(h.gatewayAddr))
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting gateway client", fmt.Errorf("error getting gateway client"))
return
}

userGroups, err := gw.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting user groups", fmt.Errorf("error getting user groups"))
return
}

groupBasedCapabilities := CapabilitiesGroupBased{}
for capability, groups := range h.capabilitiesGroupBased {
if userBelongsToGroups(userGroups.Groups, groups) {
groupBasedCapabilities = append(groupBasedCapabilities, capability)
}
}

response.WriteOCSSuccess(w, r, &Users{
// ocs can only return the home storage quota
Quota: &Quota{
Expand All @@ -145,8 +172,13 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) {
Relative: relative,
Definition: "default",
},
DisplayName: u.DisplayName,
Email: u.Mail,
UserType: conversions.UserTypeString(u.Id.Type),
DisplayName: u.DisplayName,
Email: u.Mail,
UserType: conversions.UserTypeString(u.Id.Type),
GroupBasedCapabilities: groupBasedCapabilities,
})
}

func userBelongsToGroups(userGroups, groups []string) bool {
return len(intersect.Simple(groups, userGroups)) > 0
}

0 comments on commit b566d15

Please sign in to comment.