From 981e8fe5a368fd80448fc5a3ba1ebfc43807424e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 26 Mar 2025 14:39:32 +0100 Subject: [PATCH] do not automatically expand drive root permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/graph/pkg/identity/cs3.go | 5 +- services/graph/pkg/identity/ldap.go | 7 +- services/graph/pkg/identity/ldap_group.go | 11 +- services/graph/pkg/identity/odata.go | 63 ------ services/graph/pkg/odata/odata.go | 104 +++++++++ services/graph/pkg/odata/odata_test.go | 160 +++++++++++++ services/graph/pkg/service/v0/drives.go | 239 ++++++++++++-------- services/graph/pkg/service/v0/graph_test.go | 38 +++- services/graph/pkg/service/v0/users.go | 72 +++--- 9 files changed, 507 insertions(+), 192 deletions(-) delete mode 100644 services/graph/pkg/identity/odata.go create mode 100644 services/graph/pkg/odata/odata.go create mode 100644 services/graph/pkg/odata/odata_test.go diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go index df882d070c..de375df599 100644 --- a/services/graph/pkg/identity/cs3.go +++ b/services/graph/pkg/identity/cs3.go @@ -13,6 +13,7 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/shared" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" + "github.com/opencloud-eu/opencloud/services/graph/pkg/odata" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" libregraph "github.com/opencloud-eu/libre-graph-api-go" ) @@ -81,7 +82,7 @@ func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libr return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - search, err := GetSearchValues(oreq.Query) + search, err := odata.GetSearchValues(oreq.Query) if err != nil { return nil, err } @@ -132,7 +133,7 @@ func (i *CS3) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - search, err := GetSearchValues(oreq.Query) + search, err := odata.GetSearchValues(oreq.Query) if err != nil { return nil, err } diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index 1937af80b2..9ca26bf51b 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -19,6 +19,7 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" + "github.com/opencloud-eu/opencloud/services/graph/pkg/odata" ) const ( @@ -563,7 +564,7 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData } } - exp, err := GetExpandValues(oreq.Query) + exp, err := odata.GetExpandValues(oreq.Query) if err != nil { return nil, err } @@ -593,12 +594,12 @@ func (i *LDAP) FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filt return nil, err } - search, err := GetSearchValues(oreq.Query) + search, err := odata.GetSearchValues(oreq.Query) if err != nil { return nil, err } - exp, err := GetExpandValues(oreq.Query) + exp, err := odata.GetExpandValues(oreq.Query) if err != nil { return nil, err } diff --git a/services/graph/pkg/identity/ldap_group.go b/services/graph/pkg/identity/ldap_group.go index 49d4d83a53..846a6298e9 100644 --- a/services/graph/pkg/identity/ldap_group.go +++ b/services/graph/pkg/identity/ldap_group.go @@ -15,6 +15,7 @@ import ( libregraph "github.com/opencloud-eu/libre-graph-api-go" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" + "github.com/opencloud-eu/opencloud/services/graph/pkg/odata" ) type groupAttributeMap struct { @@ -59,17 +60,17 @@ func (i *LDAP) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*li logger := i.logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "ldap").Msg("GetGroups") - search, err := GetSearchValues(oreq.Query) + search, err := odata.GetSearchValues(oreq.Query) if err != nil { return nil, err } var expandMembers bool - exp, err := GetExpandValues(oreq.Query) + exp, err := odata.GetExpandValues(oreq.Query) if err != nil { return nil, err } - sel, err := GetSelectValues(oreq.Query) + sel, err := odata.GetSelectValues(oreq.Query) if err != nil { return nil, err } @@ -146,7 +147,7 @@ func (i *LDAP) GetGroupMembers(ctx context.Context, groupID string, req *godata. logger := i.logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "ldap").Msg("GetGroupMembers") - exp, err := GetExpandValues(req.Query) + exp, err := odata.GetExpandValues(req.Query) if err != nil { return nil, err } @@ -156,7 +157,7 @@ func (i *LDAP) GetGroupMembers(ctx context.Context, groupID string, req *godata. return nil, err } - searchTerm, err := GetSearchValues(req.Query) + searchTerm, err := odata.GetSearchValues(req.Query) if err != nil { return nil, err } diff --git a/services/graph/pkg/identity/odata.go b/services/graph/pkg/identity/odata.go deleted file mode 100644 index 698b84e69e..0000000000 --- a/services/graph/pkg/identity/odata.go +++ /dev/null @@ -1,63 +0,0 @@ -package identity - -import ( - "strings" - - "github.com/CiscoM31/godata" -) - -// GetExpandValues extracts the values of the $expand query parameter and -// returns them in a []string, rejects any $expand value that consists of more -// than just a single path segment -func GetExpandValues(req *godata.GoDataQuery) ([]string, error) { - if req == nil || req.Expand == nil { - return []string{}, nil - } - expand := make([]string, 0, len(req.Expand.ExpandItems)) - for _, item := range req.Expand.ExpandItems { - if item.Filter != nil || item.At != nil || item.Search != nil || - item.OrderBy != nil || item.Skip != nil || item.Top != nil || - item.Select != nil || item.Compute != nil || item.Expand != nil || - item.Levels != 0 { - return []string{}, godata.NotImplementedError("options for $expand not supported") - } - if len(item.Path) > 1 { - return []string{}, godata.NotImplementedError("multiple segments in $expand not supported") - } - expand = append(expand, item.Path[0].Value) - } - return expand, nil -} - -// GetSelectValues extracts the values of the $select query parameter and -// returns them in a []string, rejects any $select value that consists of more -// than just a single path segment -func GetSelectValues(req *godata.GoDataQuery) ([]string, error) { - if req == nil || req.Select == nil { - return []string{}, nil - } - sel := make([]string, 0, len(req.Select.SelectItems)) - for _, item := range req.Select.SelectItems { - if len(item.Segments) > 1 { - return []string{}, godata.NotImplementedError("multiple segments in $select not supported") - } - sel = append(sel, item.Segments[0].Value) - } - return sel, nil -} - -// GetSearchValues extracts the value of the $search query parameter and returns -// it as a string. Rejects any search query that is more than just a simple string -func GetSearchValues(req *godata.GoDataQuery) (string, error) { - if req == nil || req.Search == nil { - return "", nil - } - - // Only allow simple search queries for now - if len(req.Search.Tree.Children) != 0 { - return "", godata.NotImplementedError("complex search queries are not supported") - } - - searchValue := strings.Trim(req.Search.Tree.Token.Value, "\"") - return searchValue, nil -} diff --git a/services/graph/pkg/odata/odata.go b/services/graph/pkg/odata/odata.go new file mode 100644 index 0000000000..ae40d85a19 --- /dev/null +++ b/services/graph/pkg/odata/odata.go @@ -0,0 +1,104 @@ +package odata + +import ( + "strings" + + "github.com/CiscoM31/godata" +) + +// GetExpandValues extracts the values of the $expand query parameter and +// returns them in a []string, rejecting any $expand value that consists of more +// than just a single path segment. +func GetExpandValues(req *godata.GoDataQuery) ([]string, error) { + if req == nil || req.Expand == nil { + return []string{}, nil + } + + var expand []string + for _, item := range req.Expand.ExpandItems { + paths, err := collectExpandPaths(item, "") + if err != nil { + return nil, err + } + expand = append(expand, paths...) + } + + return expand, nil +} + +// collectExpandPaths recursively collects all valid expand paths from the given item. +func collectExpandPaths(item *godata.ExpandItem, prefix string) ([]string, error) { + if err := validateExpandItem(item); err != nil { + return nil, err + } + + // Build the current path + currentPath := prefix + if len(item.Path) > 0 { + if len(item.Path) > 1 { + return nil, godata.NotImplementedError("multiple segments in $expand not supported") + } + if currentPath == "" { + currentPath = item.Path[0].Value + } else { + currentPath += "." + item.Path[0].Value + } + } + + // Collect all paths, including nested ones + paths := []string{currentPath} + if item.Expand != nil { + for _, subItem := range item.Expand.ExpandItems { + subPaths, err := collectExpandPaths(subItem, currentPath) + if err != nil { + return nil, err + } + paths = append(paths, subPaths...) + } + } + + return paths, nil +} + +// validateExpandItem checks if an expand item contains unsupported options. +func validateExpandItem(item *godata.ExpandItem) error { + if item.Filter != nil || item.At != nil || item.Search != nil || + item.OrderBy != nil || item.Skip != nil || item.Top != nil || + item.Select != nil || item.Compute != nil || item.Levels != 0 { + return godata.NotImplementedError("options for $expand not supported") + } + return nil +} + +// GetSelectValues extracts the values of the $select query parameter and +// returns them in a []string, rejects any $select value that consists of more +// than just a single path segment +func GetSelectValues(req *godata.GoDataQuery) ([]string, error) { + if req == nil || req.Select == nil { + return []string{}, nil + } + sel := make([]string, 0, len(req.Select.SelectItems)) + for _, item := range req.Select.SelectItems { + if len(item.Segments) > 1 { + return []string{}, godata.NotImplementedError("multiple segments in $select not supported") + } + sel = append(sel, item.Segments[0].Value) + } + return sel, nil +} + +// GetSearchValues extracts the value of the $search query parameter and returns +// it as a string. Rejects any search query that is more than just a simple string +func GetSearchValues(req *godata.GoDataQuery) (string, error) { + if req == nil || req.Search == nil { + return "", nil + } + + // Only allow simple search queries for now + if len(req.Search.Tree.Children) != 0 { + return "", godata.NotImplementedError("complex search queries are not supported") + } + + searchValue := strings.Trim(req.Search.Tree.Token.Value, "\"") + return searchValue, nil +} diff --git a/services/graph/pkg/odata/odata_test.go b/services/graph/pkg/odata/odata_test.go new file mode 100644 index 0000000000..462b1969f1 --- /dev/null +++ b/services/graph/pkg/odata/odata_test.go @@ -0,0 +1,160 @@ +package odata + +import ( + "testing" + + "github.com/CiscoM31/godata" + "github.com/stretchr/testify/assert" +) + +func TestGetExpandValues(t *testing.T) { + t.Run("NilRequest", func(t *testing.T) { + result, err := GetExpandValues(nil) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("EmptyExpand", func(t *testing.T) { + req := &godata.GoDataQuery{} + result, err := GetExpandValues(req) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("SinglePathSegment", func(t *testing.T) { + req := &godata.GoDataQuery{ + Expand: &godata.GoDataExpandQuery{ + ExpandItems: []*godata.ExpandItem{ + {Path: []*godata.Token{{Value: "orders"}}}, + }, + }, + } + result, err := GetExpandValues(req) + assert.NoError(t, err) + assert.Equal(t, []string{"orders"}, result) + }) + + t.Run("MultiplePathSegments", func(t *testing.T) { + req := &godata.GoDataQuery{ + Expand: &godata.GoDataExpandQuery{ + ExpandItems: []*godata.ExpandItem{ + {Path: []*godata.Token{{Value: "orders"}, {Value: "details"}}}, + }, + }, + } + result, err := GetExpandValues(req) + assert.Error(t, err) + assert.Empty(t, result) + }) + + t.Run("NestedExpand", func(t *testing.T) { + req := &godata.GoDataQuery{ + Expand: &godata.GoDataExpandQuery{ + ExpandItems: []*godata.ExpandItem{ + { + Path: []*godata.Token{{Value: "items"}}, + Expand: &godata.GoDataExpandQuery{ + ExpandItems: []*godata.ExpandItem{ + { + Path: []*godata.Token{{Value: "subitem"}}, + Expand: &godata.GoDataExpandQuery{ + ExpandItems: []*godata.ExpandItem{ + {Path: []*godata.Token{{Value: "subsubitems"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + } + result, err := GetExpandValues(req) + assert.NoError(t, err) + assert.Subset(t, result, []string{"items", "items.subitem", "items.subitem.subsubitems"}, "must contain all levels of expansion") + }) +} + +func TestGetSelectValues(t *testing.T) { + t.Run("NilRequest", func(t *testing.T) { + result, err := GetSelectValues(nil) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("EmptySelect", func(t *testing.T) { + req := &godata.GoDataQuery{} + result, err := GetSelectValues(req) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("SinglePathSegment", func(t *testing.T) { + req := &godata.GoDataQuery{ + Select: &godata.GoDataSelectQuery{ + SelectItems: []*godata.SelectItem{ + {Segments: []*godata.Token{{Value: "name"}}}, + }, + }, + } + result, err := GetSelectValues(req) + assert.NoError(t, err) + assert.Equal(t, []string{"name"}, result) + }) + + t.Run("MultiplePathSegments", func(t *testing.T) { + req := &godata.GoDataQuery{ + Select: &godata.GoDataSelectQuery{ + SelectItems: []*godata.SelectItem{ + {Segments: []*godata.Token{{Value: "name"}, {Value: "first"}}}, + }, + }, + } + result, err := GetSelectValues(req) + assert.Error(t, err) + assert.Empty(t, result) + }) +} + +func TestGetSearchValues(t *testing.T) { + t.Run("NilRequest", func(t *testing.T) { + result, err := GetSearchValues(nil) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("EmptySearch", func(t *testing.T) { + req := &godata.GoDataQuery{} + result, err := GetSearchValues(req) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("SimpleSearch", func(t *testing.T) { + req := &godata.GoDataQuery{ + Search: &godata.GoDataSearchQuery{ + Tree: &godata.ParseNode{ + Token: &godata.Token{Value: "test"}, + }, + }, + } + result, err := GetSearchValues(req) + assert.NoError(t, err) + assert.Equal(t, "test", result) + }) + + t.Run("ComplexSearch", func(t *testing.T) { + req := &godata.GoDataQuery{ + Search: &godata.GoDataSearchQuery{ + Tree: &godata.ParseNode{ + Children: []*godata.ParseNode{ + {Token: &godata.Token{Value: "test"}}, + }, + }, + }, + } + result, err := GetSearchValues(req) + assert.Error(t, err) + assert.Empty(t, result) + }) +} diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 89a352cab3..b91a1c96aa 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "path" + "slices" "sort" "strconv" "strings" @@ -32,6 +33,7 @@ import ( v0 "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/settings/v0" settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" + "github.com/opencloud-eu/opencloud/services/graph/pkg/odata" settingsServiceExt "github.com/opencloud-eu/opencloud/services/settings/pkg/store/defaults" ) @@ -168,31 +170,63 @@ func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) { } } +func sanitizePath(path string, apiVersion APIVersion) string { + switch apiVersion { + case APIVersion_1: + return strings.TrimPrefix(path, "/graph/v1.0/") + case APIVersion_1_Beta_1: + return strings.TrimPrefix(path, "/graph/v1beta1.0/") + default: + return path + } +} + +// parseDriveRequest parses the odata request and returns the parsed request and a boolean indicating if the request should expand root driveItems. +func parseDriveRequest(r *http.Request) (*godata.GoDataRequest, bool, error) { + odataReq, err := godata.ParseRequest(r.Context(), sanitizePath(r.URL.Path, APIVersion_1), r.URL.Query()) + if err != nil { + return nil, false, errorcode.New(errorcode.InvalidRequest, err.Error()) + } + exp, err := odata.GetExpandValues(odataReq.Query) + if err != nil { + return nil, false, errorcode.New(errorcode.InvalidRequest, err.Error()) + } + expandPermissions := false + if slices.Contains(exp, "root.permissions") { + expandPermissions = true + } + return odataReq, expandPermissions, nil +} + // getDrives implements the Service interface. func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, error) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info(). - Interface("query", r.URL.Query()). - Bool("unrestricted", unrestricted). - Msg("calling get drives") - sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") - // Parse the request with odata parser - odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) - if err != nil { - logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: query error") - return nil, errorcode.New(errorcode.InvalidRequest, err.Error()) - } ctx := r.Context() + log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Bool("unrestricted", unrestricted).Logger() + log.Debug().Msg("calling get drives") + + webDavBaseURL, err := g.getWebDavBaseURL() + if err != nil { + log.Error().Err(err).Msg("could not get drives: error parsing url") + return nil, errorcode.New(errorcode.GeneralException, err.Error()) + } + + log = log.With().Str("url", webDavBaseURL.String()).Logger() + + odataReq, expandPermissions, err := parseDriveRequest(r) + if err != nil { + log.Debug().Err(err).Msg("could not get drives: error parsing odata request") + return nil, err + } filters, err := generateCs3Filters(odataReq) if err != nil { - logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: error parsing filters") + log.Debug().Err(err).Msg("could not get drives: error parsing filters") return nil, errorcode.New(errorcode.NotSupported, err.Error()) } if !unrestricted { user, ok := revactx.ContextGetUser(r.Context()) if !ok { - logger.Debug().Msg("could not create drive: invalid user") + log.Debug().Msg("could not create drive: invalid user") return nil, errorcode.New(errorcode.AccessDenied, "invalid user") } filters = append(filters, &storageprovider.ListStorageSpacesRequest_Filter{ @@ -203,39 +237,32 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi }) } - logger.Debug(). + log.Debug(). Interface("filters", filters). - Bool("unrestricted", unrestricted). Msg("calling list storage spaces on backend") res, err := g.ListStorageSpacesWithFilters(ctx, filters, unrestricted) switch { case err != nil: - logger.Error().Err(err).Msg("could not get drives: transport error") + log.Error().Err(err).Msg("could not get drives: transport error") return nil, errorcode.New(errorcode.GeneralException, err.Error()) case res.Status.Code != cs3rpc.Code_CODE_OK: if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { // ok, empty return return nil, nil } - logger.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error") + log.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error") return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) } - webDavBaseURL, err := g.getWebDavBaseURL() + spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions) if err != nil { - logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drives: error parsing url") - return nil, errorcode.New(errorcode.GeneralException, err.Error()) - } - - spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion) - if err != nil { - logger.Debug().Err(err).Msg("could not get drives: error parsing grpc response") + log.Debug().Err(err).Msg("could not get drives: error parsing grpc response") return nil, errorcode.New(errorcode.GeneralException, err.Error()) } spaces, err = sortSpaces(odataReq, spaces) if err != nil { - logger.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query") + log.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query") return nil, errorcode.New(errorcode.InvalidRequest, err.Error()) } @@ -245,15 +272,32 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi // GetSingleDrive does a lookup of a single space by spaceId func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - logger := g.logger.SubloggerWithRequestID(ctx) - logger.Info().Interface("query", r.URL.Query()).Msg("calling get drive") + log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger() + log.Debug().Msg("calling get drive") rid, err := parseIDParam(r, "driveID") if err != nil { errorcode.RenderError(w, r, err) return } - log := logger.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger() + + log = log.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger() + + webDavBaseURL, err := g.getWebDavBaseURL() + if err != nil { + log.Error().Err(err).Msg("could not get drive: error parsing webdav base url") + errorcode.RenderError(w, r, err) + return + } + + log = log.With().Str("url", webDavBaseURL.String()).Logger() + + _, expandPermissions, err := parseDriveRequest(r) + if err != nil { + log.Debug().Err(err).Msg("could not get drives: error parsing odata request") + errorcode.RenderError(w, r, err) + return + } log.Debug().Msg("calling list storage spaces with id filter") @@ -281,13 +325,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) { return } - webDavBaseURL, err := g.getWebDavBaseURL() - if err != nil { - log.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drive: error parsing webdav base url") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1) + spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions) if err != nil { log.Debug().Err(err).Msg("could not get drive: error parsing grpc response") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) @@ -324,14 +362,28 @@ func (g Graph) canCreateSpace(ctx context.Context, ownPersonalHome bool) bool { // CreateDrive creates a storage drive (space). func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling create drive") - ctx := r.Context() + log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger() + log.Debug().Msg("calling create drive") + + webDavBaseURL, err := g.getWebDavBaseURL() + if err != nil { + log.Error().Err(err).Msg("could not create drive: error parsing webdav base url") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + log = log.With().Str("url", webDavBaseURL.String()).Logger() + + _, expandPermissions, err := parseDriveRequest(r) + if err != nil { + log.Debug().Err(err).Msg("could not create drive: error parsing odata request") + errorcode.RenderError(w, r, err) + } us, ok := revactx.ContextGetUser(ctx) if !ok { - logger.Debug().Msg("could not create drive: invalid user") + log.Debug().Msg("could not create drive: invalid user") errorcode.NotAllowed.Render(w, r, http.StatusUnauthorized, "invalid user") return } @@ -339,7 +391,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { // TODO determine if the user tries to create his own personal space and pass that as a boolean canCreateSpace := g.canCreateSpace(ctx, false) if !canCreateSpace { - logger.Debug().Bool("cancreatespace", canCreateSpace).Msg("could not create drive: insufficient permissions") + log.Debug().Bool("cancreatespace", canCreateSpace).Msg("could not create drive: insufficient permissions") // if the permission is not existing for the user in context we can assume we don't have it. Return 401. errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "insufficient permissions to create a space.") return @@ -347,20 +399,20 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { - logger.Error().Err(err).Msg("could not select next gateway client") + log.Error().Err(err).Msg("could not select next gateway client") errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") return } drive := libregraph.Drive{} if err := StrictJSONUnmarshal(r.Body, &drive); err != nil { - logger.Debug().Err(err).Interface("body", r.Body).Msg("could not create drive: invalid body schema definition") + log.Debug().Err(err).Interface("body", r.Body).Msg("could not create drive: invalid body schema definition") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid body schema definition") return } spaceName := strings.TrimSpace(drive.Name) if err := validateSpaceName(spaceName); err != nil { - logger.Debug().Str("name", spaceName).Err(err).Msg("could not create drive: name validation failed") + log.Debug().Str("name", spaceName).Err(err).Msg("could not create drive: name validation failed") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid spacename: %s", err.Error())) return } @@ -373,7 +425,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { case "", _spaceTypeProject: driveType = _spaceTypeProject default: - logger.Debug().Str("type", driveType).Msg("could not create drive: drives of this type cannot be created via this api") + log.Debug().Str("type", driveType).Msg("could not create drive: drives of this type cannot be created via this api") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "drives of this type cannot be created via this api") return } @@ -398,39 +450,32 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { resp, err := gatewayClient.CreateStorageSpace(ctx, &csr) if err != nil { - logger.Error().Err(err).Msg("could not create drive: transport error") + log.Error().Err(err).Msg("could not create drive: transport error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } if resp.GetStatus().GetCode() != cs3rpc.Code_CODE_OK { if resp.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED { - logger.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: permission denied") + log.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: permission denied") errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "permission denied") return } if resp.GetStatus().GetCode() == cs3rpc.Code_CODE_INVALID_ARGUMENT { - logger.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: bad request") + log.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: bad request") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage()) return } - logger.Debug().Interface("grpcmessage", csr).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not create drive: grpc error") + log.Debug().Interface("grpcmessage", csr).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not create drive: grpc error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, resp.GetStatus().GetMessage()) return } - webDavBaseURL, err := g.getWebDavBaseURL() - if err != nil { - logger.Error().Str("url", webDavBaseURL.String()).Err(err).Msg("could not create drive: error parsing webdav base url") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - space := resp.GetStorageSpace() if t := r.URL.Query().Get(TemplateParameter); t != "" && driveType == _spaceTypeProject { loc := l10n.MustGetUserLocale(ctx, us.GetId().GetOpaqueId(), r.Header.Get(HeaderAcceptLanguage), g.valueService) if err := g.applySpaceTemplate(ctx, gatewayClient, space.GetRoot(), t, loc); err != nil { - logger.Error().Err(err).Msg("could not apply template to space") + log.Error().Err(err).Msg("could not apply template to space") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } @@ -438,20 +483,20 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { // refetch the drive to get quota information - should we calculate this ourselves to avoid the extra call? space, err = utils.GetSpace(ctx, space.GetId().GetOpaqueId(), gatewayClient) if err != nil { - logger.Error().Err(err).Msg("could not refetch space") + log.Error().Err(err).Msg("could not refetch space") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } } - spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1) + spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, expandPermissions) if err != nil { - logger.Debug().Err(err).Msg("could not get drive: error parsing grpc response") + log.Debug().Err(err).Msg("could not get drive: error parsing grpc response") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } if len(spaces) == 0 { - logger.Error().Msg("could not convert space") + log.Error().Msg("could not convert space") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not convert space") return } @@ -462,8 +507,8 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { // UpdateDrive updates the properties of a storage drive (space). func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling update drive") + log := g.logger.SubloggerWithRequestID(r.Context()).With().Interface("query", r.URL.Query()).Logger() + log.Debug().Msg("calling update drive") rid, err := parseIDParam(r, "driveID") if err != nil { @@ -471,9 +516,26 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { return } + log = log.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger() + + webDavBaseURL, err := g.getWebDavBaseURL() + if err != nil { + log.Error().Err(err).Interface("url", webDavBaseURL.String()).Msg("could not update drive: error parsing url") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + log = log.With().Str("url", webDavBaseURL.String()).Logger() + + _, expandPermissions, err := parseDriveRequest(r) + if err != nil { + log.Debug().Err(err).Msg("could not create drive: error parsing odata request") + errorcode.RenderError(w, r, err) + } + drive := libregraph.DriveUpdate{} if err = StrictJSONUnmarshal(r.Body, &drive); err != nil { - logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update drive, invalid request body") + log.Debug().Err(err).Interface("body", r.Body).Msg("could not update drive, invalid request body") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: error: %v", err.Error())) return } @@ -526,7 +588,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { if drive.GetName() != "" { spacename := strings.TrimSpace(drive.GetName()) if err := validateSpaceName(spacename); err != nil { - logger.Info().Err(err).Msg("could not update drive: spacename invalid") + log.Info().Err(err).Msg("could not update drive: spacename invalid") errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error()) return } @@ -552,12 +614,12 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { canSetSpaceQuota, err := g.canSetSpaceQuota(r.Context(), user, dt) if err != nil { - logger.Error().Err(err).Msg("could not update drive: failed to check if the user can set space quota") + log.Error().Err(err).Msg("could not update drive: failed to check if the user can set space quota") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } if !canSetSpaceQuota { - logger.Debug(). + log.Debug(). Bool("cansetspacequota", canSetSpaceQuota). Msg("could not update drive: user is not allowed to set the space quota") errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "user is not allowed to set the space quota") @@ -568,10 +630,10 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { } } - logger.Debug().Interface("payload", updateSpaceRequest).Msg("calling update space on backend") + log.Debug().Interface("payload", updateSpaceRequest).Msg("calling update space on backend") resp, err := gatewayClient.UpdateStorageSpace(r.Context(), updateSpaceRequest) if err != nil { - logger.Error().Err(err).Msg("could not update drive: transport error") + log.Error().Err(err).Msg("could not update drive: transport error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "transport error") return } @@ -579,38 +641,31 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { if resp.GetStatus().GetCode() != cs3rpc.Code_CODE_OK { switch resp.Status.GetCode() { case cs3rpc.Code_CODE_NOT_FOUND: - logger.Debug().Interface("id", rid).Msg("could not update drive: drive not found") + log.Debug().Msg("could not update drive: drive not found") errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found") return case cs3rpc.Code_CODE_PERMISSION_DENIED: - logger.Debug().Interface("id", rid).Msg("could not update drive, permission denied") + log.Debug().Msg("could not update drive, permission denied") errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found") return case cs3rpc.Code_CODE_INVALID_ARGUMENT: - logger.Debug().Interface("id", rid).Msg("could not update drive, invalid argument") + log.Debug().Msg("could not update drive, invalid argument") errorcode.NotAllowed.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage()) return case cs3rpc.Code_CODE_UNIMPLEMENTED: - logger.Debug().Interface("id", rid).Msg("could not delete drive: delete not implemented for this type of drive") + log.Debug().Msg("could not delete drive: delete not implemented for this type of drive") errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "drive cannot be updated") return default: - logger.Debug().Interface("id", rid).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not update drive: grpc error") + log.Debug().Str("grpc", resp.GetStatus().GetMessage()).Msg("could not update drive: grpc error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "grpc error") return } } - webDavBaseURL, err := g.getWebDavBaseURL() + spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, expandPermissions) if err != nil { - logger.Error().Err(err).Interface("url", webDavBaseURL.String()).Msg("could not update drive: error parsing url") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - - spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1) - if err != nil { - logger.Debug().Err(err).Msg("could not update drive: error parsing grpc response") + log.Debug().Err(err).Msg("could not update drive: error parsing grpc response") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } @@ -619,7 +674,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, spaces[0]) } -func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion) ([]*libregraph.Drive, error) { +func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) ([]*libregraph.Drive, error) { errg, ctx := errgroup.WithContext(ctx) work := make(chan *storageprovider.StorageSpace, len(storageSpaces)) results := make(chan *libregraph.Drive, len(storageSpaces)) @@ -649,7 +704,7 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces // skip OCM shares they are no supposed to show up in the drives list continue } - res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion) + res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions) if err != nil { return err } @@ -733,7 +788,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor return res, err } -func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion) (*libregraph.Drive, error) { +func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) (*libregraph.Drive, error) { logger := g.logger.SubloggerWithRequestID(ctx) if space.Root == nil { logger.Error().Msg("unable to parse space: space has no root") @@ -745,18 +800,20 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa } spaceID := storagespace.FormatResourceID(spaceRid) - permissions := g.cs3SpacePermissionsToLibreGraph(ctx, space, apiVersion) - drive := &libregraph.Drive{ Id: libregraph.PtrString(spaceID), Name: space.Name, //"createdDateTime": "string (timestamp)", // TODO read from StorageSpace ... needs Opaque for now DriveType: &space.SpaceType, + // we currently always expandt the root because it carries the deleted property that indiccates if a space is trashed Root: &libregraph.DriveItem{ - Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)), - Permissions: permissions, + Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)), }, } + if expandPermissions { + drive.Root.Permissions = g.cs3SpacePermissionsToLibreGraph(ctx, space, apiVersion) + } + if space.SpaceType == _spaceTypeMountpoint { var remoteItem *libregraph.RemoteItem grantID := storageprovider.ResourceId{ diff --git a/services/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go index 365fe7326d..eb09d89d1e 100644 --- a/services/graph/pkg/service/v0/graph_test.go +++ b/services/graph/pkg/service/v0/graph_test.go @@ -489,8 +489,42 @@ var _ = Describe("Graph", func() { Expect(libreError.Error.Message).To(Equal("internal quota error")) Expect(libreError.Error.Code).To(Equal(errorcode.GeneralException.String())) }) + It("omit permissions by default", func() { + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Times(1).Return(&provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{ + { + Opaque: utils.AppendJSONToOpaque(nil, "grants", map[string]provider.ResourcePermissions{ + "1": *conversions.NewManagerRole().CS3ResourcePermissions(), + }), + Root: &provider.ResourceId{}, + }, + }, + }, nil) + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{ + Status: status.NewNotFound(ctx, "not found"), + }, nil) + gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{ + Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"), + }, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userprovider.GetUserResponse{ + Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"), + }, nil) + + r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) + r = r.WithContext(ctx) + rr := httptest.NewRecorder() + svc.GetDrivesV1(rr, r) + + Expect(rr.Code).To(Equal(http.StatusOK)) + + jsonData := gjson.Get(rr.Body.String(), "value") + + Expect(jsonData.Get("#").Num).To(Equal(float64(1))) + Expect(jsonData.Get("0.root.permissions").Exists()).To(BeFalse()) + }) }) - DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1", + DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1 expands root permissions", func(check func(gjson.Result), resourcePermissions provider.ResourcePermissions) { permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settingssvc.GetPermissionByIDResponse{ Permission: &v0.Permission{ @@ -518,7 +552,7 @@ var _ = Describe("Graph", func() { Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"), }, nil) - r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) + r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1.0/me/drives?$expand=root($expand=permissions)", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() svc.GetDrivesV1Beta1(rr, r) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index f1cbea4a49..fc6c15c2b8 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -24,6 +24,7 @@ import ( settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" "github.com/opencloud-eu/opencloud/services/graph/pkg/identity" + "github.com/opencloud-eu/opencloud/services/graph/pkg/odata" ocsettingssvc "github.com/opencloud-eu/opencloud/services/settings/pkg/service/v0" "github.com/opencloud-eu/opencloud/services/settings/pkg/store/defaults" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" @@ -53,7 +54,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { return } - exp, err := identity.GetExpandValues(odataReq.Query) + exp, err := odata.GetExpandValues(odataReq.Query) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) @@ -119,74 +120,85 @@ func (g Graph) fetchAppRoleAssignments(ctx context.Context, accountuuid string) // GetUserDrive implements the Service interface. func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Debug().Interface("query", r.URL.Query()).Msg("calling get user drive") + ctx := r.Context() + log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger() + log.Debug().Msg("calling get user drive") + + webDavBaseURL, err := g.getWebDavBaseURL() + if err != nil { + log.Error().Err(err).Msg("could not get personal drive: error parsing webdav base url") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + log = log.With().Str("url", webDavBaseURL.String()).Logger() userID, err := url.PathUnescape(chi.URLParam(r, "userID")) if err != nil { - logger.Debug().Err(err).Str("userID", chi.URLParam(r, "userID")).Msg("could not get drive: unescaping drive id failed") + log.Debug().Err(err).Str("userID", chi.URLParam(r, "userID")).Msg("could not get drive: unescaping drive id failed") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") return } + log = log.With().Str("userID", userID).Logger() + + _, expandPermissions, err := parseDriveRequest(r) + if err != nil { + log.Debug().Err(err).Msg("could not get drives: error parsing odata request") + errorcode.RenderError(w, r, err) + return + } + if userID == "" { - u, ok := revactx.ContextGetUser(r.Context()) + u, ok := revactx.ContextGetUser(ctx) if !ok { - logger.Debug().Msg("could not get user: user not in context") + log.Debug().Msg("could not get user: user not in context") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") return } userID = u.GetId().GetOpaqueId() } - logger.Debug().Str("userID", userID).Msg("calling list storage spaces with user and personal filter") - ctx := r.Context() + log.Debug().Msg("calling list storage spaces with user and personal filter") filters := []*storageprovider.ListStorageSpacesRequest_Filter{listStorageSpacesTypeFilter("personal"), listStorageSpacesUserFilter(userID)} res, err := g.ListStorageSpacesWithFilters(ctx, filters, true) switch { case err != nil: - logger.Error().Err(err).Msg("could not get drive: transport error") + log.Error().Err(err).Msg("could not get drive: transport error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return case res.Status.Code != cs3rpc.Code_CODE_OK: if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { // the client is doing a lookup for a specific space, therefore we need to return // not found to the caller - logger.Debug().Str("userID", userID).Msg("could not get personal drive for user: not found") + log.Debug().Msg("could not get personal drive for user: not found") errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found") return } - logger.Debug(). - Str("userID", userID). + log.Debug(). Str("grpcmessage", res.GetStatus().GetMessage()). Msg("could not get personal drive for user: grpc error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) return } - webDavBaseURL, err := g.getWebDavBaseURL() + spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions) if err != nil { - logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get personal drive: error parsing webdav base url") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1) - if err != nil { - logger.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response") + log.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } switch num := len(spaces); { case num == 0: - logger.Debug().Str("userID", userID).Msg("could not get personal drive: no drive returned from storage") + log.Debug().Msg("could not get personal drive: no drive returned from storage") errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "no drive returned from storage") return case num == 1: render.Status(r, http.StatusOK) render.JSON(w, r, spaces[0]) default: - logger.Debug().Int("number", num).Msg("could not get personal drive: expected to find a single drive but fetched more") + log.Debug().Int("number", num).Msg("could not get personal drive: expected to find a single drive but fetched more") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get personal drive: expected to find a single drive but fetched more") return } @@ -301,7 +313,7 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { users = finalUsers } - exp, err := identity.GetExpandValues(odataReq.Query) + exp, err := odata.GetExpandValues(odataReq.Query) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) @@ -449,7 +461,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { return } - exp, err := identity.GetExpandValues(odataReq.Query) + exp, err := odata.GetExpandValues(odataReq.Query) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) @@ -466,6 +478,8 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { listDrives := slices.Contains(exp, "drives") listDrive := slices.Contains(exp, "drive") + expandDrivePermissions := slices.Contains(exp, "drive.root.permissions") + expandDrivesPermissions := slices.Contains(exp, "drives.root.permissions") // do we need to list all or only the personal drive filters := []*storageprovider.ListStorageSpacesRequest_Filter{} @@ -526,7 +540,13 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { user.Drive = &libregraph.Drive{} } for _, sp := range lspr.GetStorageSpaces() { - d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1) + expandPermissions := false + if sp.GetSpaceType() == "personal" && sp.GetOwner().GetId().GetOpaqueId() != user.GetId() { + expandPermissions = expandDrivePermissions + } else { + expandPermissions = expandDrivesPermissions + } + d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions) if err != nil { logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive") continue @@ -1023,7 +1043,7 @@ func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDa if err != nil { return nil, errorcode.New(errorcode.GeneralException, err.Error()) } - term, err := identity.GetSearchValues(odataReq.Query) + term, err := odata.GetSearchValues(odataReq.Query) if err != nil { return nil, errorcode.New(errorcode.InvalidRequest, err.Error()) }