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

Fix group capabilities #4400

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
}