From 26f7523ff81ef1208837840e6099f7d57ac63a25 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 7 Feb 2023 15:10:23 +0100 Subject: [PATCH] graph: Pass parsed odata request to the identity backend In preparation for some more advanced queries pass the parse odata request tVo the identity backend methods instead of the raw url.Values{}. This also add some helpers for validating $expand and $search queries to reject some unsupported queries. Also remove support for `$select=memberOf` and `$select=drive|drives` queries and stick to the technically correct `$expand=...`. --- services/graph/pkg/identity/backend.go | 5 +- services/graph/pkg/identity/cs3.go | 15 +++-- services/graph/pkg/identity/ldap.go | 37 +++++++---- services/graph/pkg/identity/ldap_test.go | 54 ++++++++-------- services/graph/pkg/identity/mocks/backend.go | 30 ++++----- services/graph/pkg/identity/odata.go | 41 ++++++++++++ services/graph/pkg/service/v0/drives.go | 4 +- services/graph/pkg/service/v0/users.go | 66 ++++++++++++++++---- 8 files changed, 177 insertions(+), 75 deletions(-) create mode 100644 services/graph/pkg/identity/odata.go diff --git a/services/graph/pkg/identity/backend.go b/services/graph/pkg/identity/backend.go index cca15dc56b..42aaea0525 100644 --- a/services/graph/pkg/identity/backend.go +++ b/services/graph/pkg/identity/backend.go @@ -4,6 +4,7 @@ import ( "context" "net/url" + "github.com/CiscoM31/godata" cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" @@ -25,8 +26,8 @@ type Backend interface { DeleteUser(ctx context.Context, nameOrID string) error // UpdateUser applies changes to given user, identified by username or id UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) - GetUser(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.User, error) - GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) + GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error) + GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) // CreateGroup creates the supplied group in the identity backend. CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go index 2a3335d3ce..d6eab4029d 100644 --- a/services/graph/pkg/identity/cs3.go +++ b/services/graph/pkg/identity/cs3.go @@ -4,6 +4,7 @@ import ( "context" "net/url" + "github.com/CiscoM31/godata" cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -39,7 +40,8 @@ func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.U return nil, errNotImplemented } -func (i *CS3) GetUser(ctx context.Context, userID string, queryParam url.Values) (*libregraph.User, error) { +// GetUser implements the Backend Interface. +func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataRequest) (*libregraph.User, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetUser") client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) @@ -67,7 +69,8 @@ func (i *CS3) GetUser(ctx context.Context, userID string, queryParam url.Values) return CreateUserModelFromCS3(res.User), nil } -func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { +// GetUsers implements the Backend Interface. +func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetUsers") client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) @@ -76,9 +79,9 @@ func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregrap return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - search := queryParam.Get("search") - if search == "" { - search = queryParam.Get("$search") + search, err := GetSearchValues(oreq.Query) + if err != nil { + return nil, err } res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{ @@ -107,6 +110,7 @@ func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregrap return users, nil } +// GetGroups implements the Backend Interface. func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetGroups") @@ -153,6 +157,7 @@ func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregr return nil, errorcode.New(errorcode.NotSupported, "not implemented") } +// GetGroup implements the Backend Interface. func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Values) (*libregraph.Group, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetGroup") diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index ade4a9e2d0..f7b7c11742 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -4,9 +4,8 @@ import ( "context" "errors" "fmt" - "net/url" - "strings" + "github.com/CiscoM31/godata" "github.com/go-ldap/ldap/v3" "github.com/gofrs/uuid" libregraph "github.com/owncloud/libre-graph-api-go" @@ -369,20 +368,27 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) { return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter) } -func (i *LDAP) GetUser(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.User, error) { +// GetUser implements the Backend Interface. +func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error) { logger := i.logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "ldap").Msg("GetUser") + e, err := i.getLDAPUserByNameOrID(nameOrID) if err != nil { return nil, err } + u := i.createUserModelFromLDAP(e) if u == nil { return nil, ErrNotFound } - sel := strings.Split(queryParam.Get("$select"), ",") - exp := strings.Split(queryParam.Get("$expand"), ",") - if slices.Contains(sel, "memberOf") || slices.Contains(exp, "memberOf") { + + exp, err := GetExpandValues(oreq.Query) + if err != nil { + return nil, err + } + + if slices.Contains(exp, "memberOf") { userGroups, err := i.getGroupsForUser(e.DN) if err != nil { return nil, err @@ -392,14 +398,21 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, queryParam url.Valu return u, nil } -func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { +// GetUsers implements the Backend Interface. +func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) { logger := i.logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "ldap").Msg("GetUsers") - search := queryParam.Get("search") - if search == "" { - search = queryParam.Get("$search") + search, err := GetSearchValues(oreq.Query) + if err != nil { + return nil, err } + + exp, err := GetExpandValues(oreq.Query) + if err != nil { + return nil, err + } + var userFilter string if search != "" { search = ldap.EscapeFilter(search) @@ -439,14 +452,12 @@ func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregra users := make([]*libregraph.User, 0, len(res.Entries)) for _, e := range res.Entries { - sel := strings.Split(queryParam.Get("$select"), ",") - exp := strings.Split(queryParam.Get("$expand"), ",") u := i.createUserModelFromLDAP(e) // Skip invalid LDAP users if u == nil { continue } - if slices.Contains(sel, "memberOf") || slices.Contains(exp, "memberOf") { + if slices.Contains(exp, "memberOf") { userGroups, err := i.getGroupsForUser(e.DN) if err != nil { return nil, err diff --git a/services/graph/pkg/identity/ldap_test.go b/services/graph/pkg/identity/ldap_test.go index bece82a5dc..b645e5828b 100644 --- a/services/graph/pkg/identity/ldap_test.go +++ b/services/graph/pkg/identity/ldap_test.go @@ -7,6 +7,7 @@ import ( "net/url" "testing" + "github.com/CiscoM31/godata" "github.com/go-ldap/ldap/v3" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" @@ -172,23 +173,24 @@ func TestGetUser(t *testing.T) { nil, ldap.NewError(ldap.LDAPResultSizeLimitExceeded, errors.New("mock"))) b, _ := getMockedBackend(lm, lconfig, &logger) - queryParamExpand := url.Values{ - "$expand": []string{"memberOf"}, + odataReqDefault, err := godata.ParseRequest(context.Background(), "", + url.Values{}) + if err != nil { + t.Errorf("Expected success got '%s'", err.Error()) } - queryParamSelect := url.Values{ - "$select": []string{"memberOf"}, + + odataReqExpand, err := godata.ParseRequest(context.Background(), "", + url.Values{"$expand": []string{"memberOf"}}) + if err != nil { + t.Errorf("Expected success got '%s'", err.Error()) } - _, err := b.GetUser(context.Background(), "fred", nil) + + _, err = b.GetUser(context.Background(), "fred", odataReqDefault) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } - _, err = b.GetUser(context.Background(), "fred", queryParamExpand) - if err == nil || err.Error() != "itemNotFound" { - t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) - } - - _, err = b.GetUser(context.Background(), "fred", queryParamSelect) + _, err = b.GetUser(context.Background(), "fred", odataReqExpand) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -199,17 +201,12 @@ func TestGetUser(t *testing.T) { Return( &ldap.SearchResult{}, nil) b, _ = getMockedBackend(lm, lconfig, &logger) - _, err = b.GetUser(context.Background(), "fred", nil) + _, err = b.GetUser(context.Background(), "fred", odataReqDefault) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } - _, err = b.GetUser(context.Background(), "fred", queryParamExpand) - if err == nil || err.Error() != "itemNotFound" { - t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) - } - - _, err = b.GetUser(context.Background(), "fred", queryParamSelect) + _, err = b.GetUser(context.Background(), "fred", odataReqExpand) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -224,21 +221,14 @@ func TestGetUser(t *testing.T) { nil) b, _ = getMockedBackend(lm, lconfig, &logger) - u, err := b.GetUser(context.Background(), "user", nil) + u, err := b.GetUser(context.Background(), "user", odataReqDefault) if err != nil { t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { t.Errorf("Expected GetUser to return a valid user") } - u, err = b.GetUser(context.Background(), "user", queryParamExpand) - if err != nil { - t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) - } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { - t.Errorf("Expected GetUser to return a valid user") - } - - u, err = b.GetUser(context.Background(), "user", queryParamSelect) + u, err = b.GetUser(context.Background(), "user", odataReqExpand) if err != nil { t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { @@ -266,8 +256,14 @@ func TestGetUsers(t *testing.T) { lm := &mocks.Client{} lm.On("Search", mock.Anything).Return(nil, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("mock"))) + odataReqDefault, err := godata.ParseRequest(context.Background(), "", + url.Values{}) + if err != nil { + t.Errorf("Expected success got '%s'", err.Error()) + } + b, _ := getMockedBackend(lm, lconfig, &logger) - _, err := b.GetUsers(context.Background(), url.Values{}) + _, err = b.GetUsers(context.Background(), odataReqDefault) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -275,7 +271,7 @@ func TestGetUsers(t *testing.T) { lm = &mocks.Client{} lm.On("Search", mock.Anything).Return(&ldap.SearchResult{}, nil) b, _ = getMockedBackend(lm, lconfig, &logger) - g, err := b.GetUsers(context.Background(), url.Values{}) + g, err := b.GetUsers(context.Background(), odataReqDefault) if err != nil { t.Errorf("Expected success, got '%s'", err.Error()) } else if g == nil || len(g) != 0 { diff --git a/services/graph/pkg/identity/mocks/backend.go b/services/graph/pkg/identity/mocks/backend.go index 5c192ab921..9fcdcd8c19 100644 --- a/services/graph/pkg/identity/mocks/backend.go +++ b/services/graph/pkg/identity/mocks/backend.go @@ -5,6 +5,8 @@ package mocks import ( context "context" + godata "github.com/CiscoM31/godata" + libregraph "github.com/owncloud/libre-graph-api-go" mock "github.com/stretchr/testify/mock" @@ -174,13 +176,13 @@ func (_m *Backend) GetGroups(ctx context.Context, queryParam url.Values) ([]*lib return r0, r1 } -// GetUser provides a mock function with given fields: ctx, nameOrID, queryParam -func (_m *Backend) GetUser(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.User, error) { - ret := _m.Called(ctx, nameOrID, queryParam) +// GetUser provides a mock function with given fields: ctx, nameOrID, oreq +func (_m *Backend) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error) { + ret := _m.Called(ctx, nameOrID, oreq) var r0 *libregraph.User - if rf, ok := ret.Get(0).(func(context.Context, string, url.Values) *libregraph.User); ok { - r0 = rf(ctx, nameOrID, queryParam) + if rf, ok := ret.Get(0).(func(context.Context, string, *godata.GoDataRequest) *libregraph.User); ok { + r0 = rf(ctx, nameOrID, oreq) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*libregraph.User) @@ -188,8 +190,8 @@ func (_m *Backend) GetUser(ctx context.Context, nameOrID string, queryParam url. } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, url.Values) error); ok { - r1 = rf(ctx, nameOrID, queryParam) + if rf, ok := ret.Get(1).(func(context.Context, string, *godata.GoDataRequest) error); ok { + r1 = rf(ctx, nameOrID, oreq) } else { r1 = ret.Error(1) } @@ -197,13 +199,13 @@ func (_m *Backend) GetUser(ctx context.Context, nameOrID string, queryParam url. return r0, r1 } -// GetUsers provides a mock function with given fields: ctx, queryParam -func (_m *Backend) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { - ret := _m.Called(ctx, queryParam) +// GetUsers provides a mock function with given fields: ctx, oreq +func (_m *Backend) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) { + ret := _m.Called(ctx, oreq) var r0 []*libregraph.User - if rf, ok := ret.Get(0).(func(context.Context, url.Values) []*libregraph.User); ok { - r0 = rf(ctx, queryParam) + if rf, ok := ret.Get(0).(func(context.Context, *godata.GoDataRequest) []*libregraph.User); ok { + r0 = rf(ctx, oreq) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*libregraph.User) @@ -211,8 +213,8 @@ func (_m *Backend) GetUsers(ctx context.Context, queryParam url.Values) ([]*libr } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, url.Values) error); ok { - r1 = rf(ctx, queryParam) + if rf, ok := ret.Get(1).(func(context.Context, *godata.GoDataRequest) error); ok { + r1 = rf(ctx, oreq) } else { r1 = ret.Error(1) } diff --git a/services/graph/pkg/identity/odata.go b/services/graph/pkg/identity/odata.go new file mode 100644 index 0000000000..3b9e5a9b67 --- /dev/null +++ b/services/graph/pkg/identity/odata.go @@ -0,0 +1,41 @@ +package identity + +import "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 +} + +// 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") + } + + return req.Search.Tree.Token.Value, nil +} diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 29a4e27ad8..0bd90a616c 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -322,6 +322,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, newDrive) } +// 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") @@ -594,7 +595,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa } else { var user libregraph.User if item := g.usersCache.Get(id); item == nil { - if requestedUser, err := g.identityBackend.GetUser(ctx, id, url.Values{}); err == nil { + if requestedUser, err := g.identityBackend.GetUser(ctx, id, &godata.GoDataRequest{}); err == nil { user = *requestedUser g.usersCache.Set(id, user, ttlcache.DefaultTTL) } @@ -881,6 +882,7 @@ func listStorageSpacesTypeFilter(spaceType string) *storageprovider.ListStorageS } } +// DeleteDrive deletes a storage drive (space). func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info().Msg("calling delete drive") diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 09ea4512bb..6320238b4f 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -32,6 +32,14 @@ import ( func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info().Msg("calling get user in /me") + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") + + 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 users: query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } u, ok := revactx.ContextGetUser(r.Context()) if !ok { @@ -39,7 +47,14 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") return } - exp := strings.Split(r.URL.Query().Get("$expand"), ",") + + exp, err := identity.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()) + return + } + var me *libregraph.User // We can just return the user from context unless we need to expand the group memberships if !slices.Contains(exp, "memberOf") { @@ -47,7 +62,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { } else { var err error logger.Debug().Msg("calling get user on backend") - me, err = g.identityBackend.GetUser(r.Context(), u.GetId().GetOpaqueId(), r.URL.Query()) + me, err = g.identityBackend.GetUser(r.Context(), u.GetId().GetOpaqueId(), odataReq) if err != nil { logger.Debug().Err(err).Interface("user", u).Msg("could not get user from backend") var errcode errorcode.Error @@ -111,7 +126,7 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { } logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users on backend") - users, err := g.identityBackend.GetUsers(r.Context(), r.URL.Query()) + users, err := g.identityBackend.GetUsers(r.Context(), odataReq) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users from backend") var errcode errorcode.Error @@ -123,8 +138,12 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { return } - // expand appRoleAssignments if requested - exp := strings.Split(r.URL.Query().Get("$expand"), ",") + exp, err := identity.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()) + return + } expandAppRoleAssignments := slices.Contains(exp, "appRoleAssignments") expandMemberOf := slices.Contains(exp, "memberOf") for _, u := range users { @@ -240,6 +259,9 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info().Msg("calling get user") + + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") + userID := chi.URLParam(r, "userID") userID, err := url.PathUnescape(userID) if err != nil { @@ -254,8 +276,22 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { return } + 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 users: query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + exp, err := identity.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()) + return + } + logger.Debug().Str("id", userID).Msg("calling get user from backend") - user, err := g.identityBackend.GetUser(r.Context(), userID, r.URL.Query()) + user, err := g.identityBackend.GetUser(r.Context(), userID, odataReq) if err != nil { logger.Debug().Err(err).Msg("could not get user: error fetching user from backend") @@ -267,10 +303,9 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { } return } - sel := strings.Split(r.URL.Query().Get("$select"), ",") - exp := strings.Split(r.URL.Query().Get("$expand"), ",") - listDrives := slices.Contains(sel, "drives") || slices.Contains(exp, "drives") - listDrive := slices.Contains(sel, "drive") || slices.Contains(exp, "drive") + + listDrives := slices.Contains(exp, "drives") + listDrive := slices.Contains(exp, "drive") // do we need to list all or only the personal drive filters := []*storageprovider.ListStorageSpacesRequest_Filter{} @@ -368,6 +403,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info().Msg("calling delete user") + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") userID := chi.URLParam(r, "userID") userID, err := url.PathUnescape(userID) if err != nil { @@ -381,8 +417,16 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") return } + + 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 users: query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + logger.Debug().Str("id", userID).Msg("calling get user on user backend") - user, err := g.identityBackend.GetUser(r.Context(), userID, r.URL.Query()) + user, err := g.identityBackend.GetUser(r.Context(), userID, odataReq) if err != nil { logger.Debug().Err(err).Str("userID", userID).Msg("failed to get user from backend") var errcode errorcode.Error