From d96031b7d347a292c852fbde77045df70ae6209f Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 9 Nov 2023 14:30:24 +0100 Subject: [PATCH 01/16] add preffered language Signed-off-by: Christian Richter --- services/graph/pkg/identity/ldap.go | 32 ++++++++++++++---------- services/graph/pkg/service/v0/service.go | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index e3283605a66..d8fcfed4b6d 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -72,14 +72,15 @@ type LDAP struct { } type userAttributeMap struct { - displayName string - id string - mail string - userName string - givenName string - surname string - accountEnabled string - userType string + displayName string + id string + mail string + userName string + givenName string + surname string + accountEnabled string + userType string + preferredLanguage string } type ldapAttributeValues map[string][]string @@ -300,11 +301,12 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph. mr := ldap.ModifyRequest{DN: e.DN} properties := map[string]string{ - i.userAttributeMap.displayName: user.GetDisplayName(), - i.userAttributeMap.mail: user.GetMail(), - i.userAttributeMap.surname: user.GetSurname(), - i.userAttributeMap.givenName: user.GetGivenName(), - i.userAttributeMap.userType: user.GetUserType(), + i.userAttributeMap.displayName: user.GetDisplayName(), + i.userAttributeMap.mail: user.GetMail(), + i.userAttributeMap.surname: user.GetSurname(), + i.userAttributeMap.givenName: user.GetGivenName(), + i.userAttributeMap.userType: user.GetUserType(), + i.userAttributeMap.preferredLanguage: user.GetPreferredLanguage(), } for attribute, value := range properties { @@ -394,6 +396,7 @@ func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) { i.userAttributeMap.givenName, i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, + i.userAttributeMap.preferredLanguage, } filter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass) @@ -521,6 +524,7 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) { i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, + i.userAttributeMap.preferredLanguage, } return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter) } @@ -600,6 +604,7 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib i.userAttributeMap.givenName, i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, + i.userAttributeMap.preferredLanguage, }, nil, ) @@ -856,6 +861,7 @@ func (i *LDAP) getUserAttrTypes() []string { "userPassword", i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, + i.userAttributeMap.preferredLanguage, } } diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index a02a25d62ca..78dd42f2a72 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -225,6 +225,7 @@ func NewService(opts ...Option) (Graph, error) { r.Post("/exportPersonalData", svc.ExportPersonalData) r.With(requireAdmin).Delete("/", svc.DeleteUser) r.With(requireAdmin).Patch("/", svc.PatchUser) + r.With(requireAdmin).Patch("/language/{language}", svc.SetUserLanguage) if svc.roleService != nil { r.With(requireAdmin).Route("/appRoleAssignments", func(r chi.Router) { r.Get("/", svc.ListAppRoleAssignments) From 388e9f266a58c5039c0c3c155951e9a6f8035b9a Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 9 Nov 2023 14:44:09 +0100 Subject: [PATCH 02/16] restore deleted changes Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/language.go | 108 ++++++++++++++++++ .../graph/pkg/service/v0/language_test.go | 89 +++++++++++++++ services/graph/pkg/service/v0/service.go | 8 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 services/graph/pkg/service/v0/language.go create mode 100644 services/graph/pkg/service/v0/language_test.go diff --git a/services/graph/pkg/service/v0/language.go b/services/graph/pkg/service/v0/language.go new file mode 100644 index 00000000000..10d7535f107 --- /dev/null +++ b/services/graph/pkg/service/v0/language.go @@ -0,0 +1,108 @@ +package svc + +import ( + "github.com/CiscoM31/godata" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "net/http" + "strings" +) + +// GetOwnLanguage returns the language of the current user. +func (g Graph) GetOwnLanguage(w http.ResponseWriter, r *http.Request) { + logger := g.logger.SubloggerWithRequestID(r.Context()) + g.logger.Debug().Msg("Calling GetOwnLanguage") + 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 { + logger.Debug().Msg("could not get user: user not in context") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") + return + } + + 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") + errorcode.RenderError(w, r, err) + return + } + + // TODO: make sure that this actually returns the stored language + lang, ok := me.GetLanguageOk() + if !ok { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, nil) + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, lang) +} + +// SetOwnLanguage sets the language of the current user. +func (g Graph) SetOwnLanguage(w http.ResponseWriter, r *http.Request) { + logger := g.logger.SubloggerWithRequestID(r.Context()) + g.logger.Debug().Msg("Calling SetOwnLanguage") + 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 { + logger.Debug().Msg("could not get user: user not in context") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") + return + } + + 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") + errorcode.RenderError(w, r, err) + return + } + + lang := chi.URLParam(r, "language") + me.SetLanguage(lang) + // TODO: persist this change + render.Status(r, http.StatusNoContent) +} + +func (g Graph) SetUserLanguage(w http.ResponseWriter, r *http.Request) { + logger := g.logger.SubloggerWithRequestID(r.Context()) + g.logger.Debug().Msg("Calling SetUserLanguage") + 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 + } + + user, err := g.identityBackend.GetUser(r.Context(), chi.URLParam(r, "userID"), odataReq) + if err != nil { + logger.Debug().Err(err).Interface("user", user.GetId()).Msg("could not get user from backend") + errorcode.RenderError(w, r, err) + return + } + + lang := chi.URLParam(r, "language") + user.SetLanguage(lang) + // TODO: persist this change + render.Status(r, http.StatusNoContent) +} diff --git a/services/graph/pkg/service/v0/language_test.go b/services/graph/pkg/service/v0/language_test.go new file mode 100644 index 00000000000..070dbb52395 --- /dev/null +++ b/services/graph/pkg/service/v0/language_test.go @@ -0,0 +1,89 @@ +package svc_test + +import ( + libregraph "github.com/owncloud/libre-graph-api-go" + "net/http/httptest" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/graph/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +var _ = Describe("Language", func() { + var ( + svc service.Service + //ctx context.Context + cfg *config.Config + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + eventsPublisher mocks.Publisher + identityBackend *identitymocks.Backend + + rr *httptest.ResponseRecorder + ) + + BeforeEach(func() { + eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + identityBackend = &identitymocks.Backend{} + + rr = httptest.NewRecorder() + //ctx = context.Background() + + cfg = defaults.FullDefaultConfig() + cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests + cfg.TokenManager.JWTSecret = "loremipsum" + cfg.Commons = &shared.Commons{} + cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + + svc, _ = service.NewService( + service.Config(cfg), + service.WithGatewaySelector(gatewaySelector), + service.EventsPublisher(&eventsPublisher), + service.WithIdentityBackend(identityBackend), + ) + + }) + + It("should return the language of the current user", func() { + user := libregraph.NewUser() + user.SetId("disallowed") + user.SetDisplayName("foobar") + user.SetLanguage("en-EN") + + r := httptest.NewRequest("GET", "/graph/v1.0/me/language", nil) + svc.(*service.Gradddddddddddph).GetOwnLanguage(rr, r) + Expect(rr.Code).To(Equal(200)) + Expect(rr.Body.String()).To(Equal("en-EN")) + + }) + + It("should set the language of the current user", func() { + r := httptest.NewRequest("PUT", "/graph/v1.0/me/language/en-EN", nil) + svc.(*service.Graph).SetOwnLanguage(rr, r) + Expect(rr.Code).To(Equal(204)) + svc.(*service.Graph).GetOwnLanguage(rr, r) + Expect(rr.Code).To(Equal(200)) + Expect(rr.Body.String()).To(Equal("en-EN")) + }) +}) diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 78dd42f2a72..8a4728aed41 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -212,7 +212,13 @@ func NewService(opts ...Option) (Graph, error) { r.Route("/me", func(r chi.Router) { r.Get("/", svc.GetMe) r.Get("/drive", svc.GetUserDrive) - r.Get("/drives", svc.GetDrives) + r.Route("/drives", func(r chi.Router) { + r.Get("/", svc.GetDrives) + }) + r.Route("/language", func(r chi.Router) { + r.Get("/", svc.GetOwnLanguage) + r.Post("/{language}", svc.SetOwnLanguage) + }) r.Get("/drive/root/children", svc.GetRootDriveChildren) r.Post("/changePassword", svc.ChangeOwnPassword) }) From a3f037a53e1b2021f61d35bd93084448fd41fba7 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 9 Nov 2023 14:45:36 +0100 Subject: [PATCH 03/16] refactor to api changes Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/language.go | 6 +++--- services/graph/pkg/service/v0/language_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/graph/pkg/service/v0/language.go b/services/graph/pkg/service/v0/language.go index 10d7535f107..3727c55a284 100644 --- a/services/graph/pkg/service/v0/language.go +++ b/services/graph/pkg/service/v0/language.go @@ -38,7 +38,7 @@ func (g Graph) GetOwnLanguage(w http.ResponseWriter, r *http.Request) { } // TODO: make sure that this actually returns the stored language - lang, ok := me.GetLanguageOk() + lang, ok := me.GetPreferredLanguageOk() if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, nil) @@ -77,7 +77,7 @@ func (g Graph) SetOwnLanguage(w http.ResponseWriter, r *http.Request) { } lang := chi.URLParam(r, "language") - me.SetLanguage(lang) + me.SetPreferredLanguage(lang) // TODO: persist this change render.Status(r, http.StatusNoContent) } @@ -102,7 +102,7 @@ func (g Graph) SetUserLanguage(w http.ResponseWriter, r *http.Request) { } lang := chi.URLParam(r, "language") - user.SetLanguage(lang) + user.SetPreferredLanguage(lang) // TODO: persist this change render.Status(r, http.StatusNoContent) } diff --git a/services/graph/pkg/service/v0/language_test.go b/services/graph/pkg/service/v0/language_test.go index 070dbb52395..ce046c34881 100644 --- a/services/graph/pkg/service/v0/language_test.go +++ b/services/graph/pkg/service/v0/language_test.go @@ -69,10 +69,10 @@ var _ = Describe("Language", func() { user := libregraph.NewUser() user.SetId("disallowed") user.SetDisplayName("foobar") - user.SetLanguage("en-EN") + user.SetPreferredLanguage("en-EN") r := httptest.NewRequest("GET", "/graph/v1.0/me/language", nil) - svc.(*service.Gradddddddddddph).GetOwnLanguage(rr, r) + svc.(*service.Graph).GetOwnLanguage(rr, r) Expect(rr.Code).To(Equal(200)) Expect(rr.Body.String()).To(Equal("en-EN")) From 174097214fb3fab912894af3b35bc61d1089e5e7 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 13 Nov 2023 13:20:36 +0100 Subject: [PATCH 04/16] Persist preferred language for user Signed-off-by: Christian Richter Co-authored-by: Julian Koberg Co-authored-by: Michael Barz --- services/graph/pkg/server/http/server.go | 3 + services/graph/pkg/service/v0/graph.go | 1 + services/graph/pkg/service/v0/language.go | 108 ------------------ .../graph/pkg/service/v0/language_test.go | 89 --------------- services/graph/pkg/service/v0/option.go | 8 ++ services/graph/pkg/service/v0/service.go | 7 +- services/graph/pkg/service/v0/users.go | 103 ++++++++++++++++- 7 files changed, 113 insertions(+), 206 deletions(-) delete mode 100644 services/graph/pkg/service/v0/language.go delete mode 100644 services/graph/pkg/service/v0/language_test.go diff --git a/services/graph/pkg/server/http/server.go b/services/graph/pkg/server/http/server.go index 027599665fc..113ffac6002 100644 --- a/services/graph/pkg/server/http/server.go +++ b/services/graph/pkg/server/http/server.go @@ -83,6 +83,7 @@ func Server(opts ...Option) (http.Service, error) { // how do we secure the api? var requireAdminMiddleware func(stdhttp.Handler) stdhttp.Handler var roleService svc.RoleService + var valueService settingssvc.ValueService var gatewaySelector pool.Selectable[gateway.GatewayAPIClient] grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(options.Config.GRPCClientTLS), grpc.WithTraceProvider(options.TraceProvider))...) if err != nil { @@ -95,6 +96,7 @@ func Server(opts ...Option) (http.Service, error) { account.JWTSecret(options.Config.TokenManager.JWTSecret), )) roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient) + valueService = settingssvc.NewValueService("com.owncloud.api.settings", grpcClient) gatewaySelector, err = pool.GatewaySelector( options.Config.Reva.Address, append( @@ -133,6 +135,7 @@ func Server(opts ...Option) (http.Service, error) { svc.Middleware(middlewares...), svc.EventsPublisher(publisher), svc.WithRoleService(roleService), + svc.WithValueService(valueService), svc.WithRequireAdminMiddleware(requireAdminMiddleware), svc.WithGatewaySelector(gatewaySelector), svc.WithSearchService(searchsvc.NewSearchProviderService("com.owncloud.api.search", grpcClient)), diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index ffb174c6613..36e3c3b7b43 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -70,6 +70,7 @@ type Graph struct { gatewaySelector pool.Selectable[gateway.GatewayAPIClient] roleService RoleService permissionsService Permissions + valueService settingssvc.ValueService specialDriveItemsCache *ttlcache.Cache[string, interface{}] identityCache identity.IdentityCache eventsPublisher events.Publisher diff --git a/services/graph/pkg/service/v0/language.go b/services/graph/pkg/service/v0/language.go deleted file mode 100644 index 3727c55a284..00000000000 --- a/services/graph/pkg/service/v0/language.go +++ /dev/null @@ -1,108 +0,0 @@ -package svc - -import ( - "github.com/CiscoM31/godata" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" - "net/http" - "strings" -) - -// GetOwnLanguage returns the language of the current user. -func (g Graph) GetOwnLanguage(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - g.logger.Debug().Msg("Calling GetOwnLanguage") - 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 { - logger.Debug().Msg("could not get user: user not in context") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") - return - } - - 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") - errorcode.RenderError(w, r, err) - return - } - - // TODO: make sure that this actually returns the stored language - lang, ok := me.GetPreferredLanguageOk() - if !ok { - render.Status(r, http.StatusNotFound) - render.JSON(w, r, nil) - return - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, lang) -} - -// SetOwnLanguage sets the language of the current user. -func (g Graph) SetOwnLanguage(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - g.logger.Debug().Msg("Calling SetOwnLanguage") - 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 { - logger.Debug().Msg("could not get user: user not in context") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") - return - } - - 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") - errorcode.RenderError(w, r, err) - return - } - - lang := chi.URLParam(r, "language") - me.SetPreferredLanguage(lang) - // TODO: persist this change - render.Status(r, http.StatusNoContent) -} - -func (g Graph) SetUserLanguage(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - g.logger.Debug().Msg("Calling SetUserLanguage") - 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 - } - - user, err := g.identityBackend.GetUser(r.Context(), chi.URLParam(r, "userID"), odataReq) - if err != nil { - logger.Debug().Err(err).Interface("user", user.GetId()).Msg("could not get user from backend") - errorcode.RenderError(w, r, err) - return - } - - lang := chi.URLParam(r, "language") - user.SetPreferredLanguage(lang) - // TODO: persist this change - render.Status(r, http.StatusNoContent) -} diff --git a/services/graph/pkg/service/v0/language_test.go b/services/graph/pkg/service/v0/language_test.go deleted file mode 100644 index ce046c34881..00000000000 --- a/services/graph/pkg/service/v0/language_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package svc_test - -import ( - libregraph "github.com/owncloud/libre-graph-api-go" - "net/http/httptest" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - "github.com/owncloud/ocis/v2/services/graph/mocks" - "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" - identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" - service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" -) - -var _ = Describe("Language", func() { - var ( - svc service.Service - //ctx context.Context - cfg *config.Config - gatewayClient *cs3mocks.GatewayAPIClient - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - eventsPublisher mocks.Publisher - identityBackend *identitymocks.Backend - - rr *httptest.ResponseRecorder - ) - - BeforeEach(func() { - eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") - gatewayClient = &cs3mocks.GatewayAPIClient{} - gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( - "GatewaySelector", - "com.owncloud.api.gateway", - func(cc *grpc.ClientConn) gateway.GatewayAPIClient { - return gatewayClient - }, - ) - - identityBackend = &identitymocks.Backend{} - - rr = httptest.NewRecorder() - //ctx = context.Background() - - cfg = defaults.FullDefaultConfig() - cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests - cfg.TokenManager.JWTSecret = "loremipsum" - cfg.Commons = &shared.Commons{} - cfg.GRPCClientTLS = &shared.GRPCClientTLS{} - - svc, _ = service.NewService( - service.Config(cfg), - service.WithGatewaySelector(gatewaySelector), - service.EventsPublisher(&eventsPublisher), - service.WithIdentityBackend(identityBackend), - ) - - }) - - It("should return the language of the current user", func() { - user := libregraph.NewUser() - user.SetId("disallowed") - user.SetDisplayName("foobar") - user.SetPreferredLanguage("en-EN") - - r := httptest.NewRequest("GET", "/graph/v1.0/me/language", nil) - svc.(*service.Graph).GetOwnLanguage(rr, r) - Expect(rr.Code).To(Equal(200)) - Expect(rr.Body.String()).To(Equal("en-EN")) - - }) - - It("should set the language of the current user", func() { - r := httptest.NewRequest("PUT", "/graph/v1.0/me/language/en-EN", nil) - svc.(*service.Graph).SetOwnLanguage(rr, r) - Expect(rr.Code).To(Equal(204)) - svc.(*service.Graph).GetOwnLanguage(rr, r) - Expect(rr.Code).To(Equal(200)) - Expect(rr.Body.String()).To(Equal("en-EN")) - }) -}) diff --git a/services/graph/pkg/service/v0/option.go b/services/graph/pkg/service/v0/option.go index 9ed23fb4a12..f68633b48be 100644 --- a/services/graph/pkg/service/v0/option.go +++ b/services/graph/pkg/service/v0/option.go @@ -31,6 +31,7 @@ type Options struct { IdentityEducationBackend identity.EducationBackend RoleService RoleService PermissionService Permissions + ValueService settingssvc.ValueService RoleManager *roles.Manager EventsPublisher events.Publisher SearchService searchsvc.SearchProviderService @@ -106,6 +107,13 @@ func WithRoleService(val RoleService) Option { } } +// WithValueService provides a function to set the ValueService option. +func WithValueService(val settingssvc.ValueService) Option { + return func(o *Options) { + o.ValueService = val + } +} + // WithSearchService provides a function to set the SearchService option. func WithSearchService(val searchsvc.SearchProviderService) Option { return func(o *Options) { diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 8a4728aed41..3012bbd7298 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -146,6 +146,7 @@ func NewService(opts ...Option) (Graph, error) { keycloakClient: options.KeycloakClient, historyClient: options.EventHistoryClient, traceProvider: options.TraceProvider, + valueService: options.ValueService, } if err := setIdentityBackends(options, &svc); err != nil { @@ -215,12 +216,9 @@ func NewService(opts ...Option) (Graph, error) { r.Route("/drives", func(r chi.Router) { r.Get("/", svc.GetDrives) }) - r.Route("/language", func(r chi.Router) { - r.Get("/", svc.GetOwnLanguage) - r.Post("/{language}", svc.SetOwnLanguage) - }) r.Get("/drive/root/children", svc.GetRootDriveChildren) r.Post("/changePassword", svc.ChangeOwnPassword) + r.Patch("/", svc.PatchMe) }) r.Route("/users", func(r chi.Router) { r.With(requireAdmin).Get("/", svc.GetUsers) @@ -231,7 +229,6 @@ func NewService(opts ...Option) (Graph, error) { r.Post("/exportPersonalData", svc.ExportPersonalData) r.With(requireAdmin).Delete("/", svc.DeleteUser) r.With(requireAdmin).Patch("/", svc.PatchUser) - r.With(requireAdmin).Patch("/language/{language}", svc.SetUserLanguage) if svc.roleService != nil { r.With(requireAdmin).Route("/appRoleAssignments", func(r chi.Router) { r.Get("/", svc.ListAppRoleAssignments) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 097e4fa654a..c42b97f6201 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "net/http" "net/url" "reflect" @@ -22,10 +23,12 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" - settingssvc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + ocissettingssvc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" "golang.org/x/exp/slices" ) @@ -85,6 +88,16 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { } } + preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService) + if err != nil { + logger.Error().Err(err).Msg("could not get user language") + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, me) + return + } + + me.PreferredLanguage = &preferedLanguage + render.Status(r, http.StatusOK) render.JSON(w, r, me) } @@ -319,10 +332,10 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { // to all new users for now, as create Account request does not have any role field if _, err = g.roleService.AssignRoleToUser(r.Context(), &settings.AssignRoleToUserRequest{ AccountUuid: *u.Id, - RoleId: settingssvc.BundleUUIDRoleUser, + RoleId: ocissettingssvc.BundleUUIDRoleUser, }); err != nil { // log as error, admin eventually needs to do something - logger.Error().Err(err).Str("id", *u.Id).Str("role", settingssvc.BundleUUIDRoleUser).Msg("could not create user: role assignment failed") + logger.Error().Err(err).Str("id", *u.Id).Str("role", ocissettingssvc.BundleUUIDRoleUser).Msg("could not create user: role assignment failed") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "role assignment failed") return } @@ -475,10 +488,37 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { } } + preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService) + if err != nil { + logger.Error().Err(err).Msg("could not get user language") + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, user) + return + } + + user.PreferredLanguage = &preferedLanguage + render.Status(r, http.StatusOK) render.JSON(w, r, user) } +// getUserLanguage returns the language of the user in the context. +func getUserLanguage(ctx context.Context, valueService settingssvc.ValueService) (string, string, error) { + gvr, err := valueService.GetValueByUniqueIdentifiers(ctx, &settingssvc.GetValueByUniqueIdentifiersRequest{ + AccountUuid: revactx.ContextMustGetUser(ctx).GetId().GetOpaqueId(), + SettingId: defaults.SettingUUIDProfileLanguage, + }) + if err != nil { + return "", "", err + } + + langVal := gvr.GetValue().GetValue().GetListValue().GetValues() + if len(langVal) > 0 && langVal[0] != nil { + return langVal[0].GetStringValue(), gvr.GetValue().GetValue().GetId(), nil + } + return "", "", errors.New("no language value found") +} + // DeleteUser implements the Service interface. func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) @@ -599,11 +639,23 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { render.NoContent(w, r) } +// PatchMe implements the Service Interface. Updates the specified attributes of the +func (g Graph) PatchMe(w http.ResponseWriter, r *http.Request) { + logger := g.logger.SubloggerWithRequestID(r.Context()) + logger.Debug().Msg("calling patch me") + userID := revactx.ContextMustGetUser(r.Context()).GetId().GetOpaqueId() + if userID == "" { + logger.Debug().Msg("could not update user: missing user id") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") + return + } + g.patchUser(w, r, userID) +} + // PatchUser implements the Service Interface. Updates the specified attributes of an // ExistingUser func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Debug().Msg("calling patch user") nameOrID := chi.URLParam(r, "userID") nameOrID, err := url.PathUnescape(nameOrID) if err != nil { @@ -611,6 +663,12 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") return } + g.patchUser(w, r, nameOrID) +} + +func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string) { + logger := g.logger.SubloggerWithRequestID(r.Context()) + logger.Debug().Msg("calling patch user") sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") @@ -657,6 +715,42 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { } } + preferredLanguage, ok := changes.GetPreferredLanguageOk() + if ok { + _, vID, err := getUserLanguage(r.Context(), g.valueService) + if err != nil { + logger.Error().Err(err).Msg("could not get user language") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get user language") + return + } + _, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{ + Value: &settingsmsg.Value{ + Id: vID, + BundleId: defaults.BundleUUIDProfile, + SettingId: defaults.SettingUUIDProfileLanguage, + AccountUuid: nameOrID, + Resource: &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_USER, + }, + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: *preferredLanguage, + }, + }, + }}, + }, + }, + }) + if err != nil { + logger.Error().Err(err).Msg("could not update user: error saving language setting") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error saving language setting") + return + } + + } + var features []events.UserFeature if mail, ok := changes.GetMailOk(); ok { if !isValidEmail(*mail) { @@ -715,6 +809,7 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { errorcode.RenderError(w, r, err) return } + u.PreferredLanguage = preferredLanguage e := events.UserFeatureChanged{ UserID: nameOrID, From 33f3f0d10b112be27839c881999e24eb88ed8b31 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 13 Nov 2023 13:23:39 +0100 Subject: [PATCH 05/16] add changelog Signed-off-by: Christian Richter --- changelog/unreleased/preferred-language.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/unreleased/preferred-language.md diff --git a/changelog/unreleased/preferred-language.md b/changelog/unreleased/preferred-language.md new file mode 100644 index 00000000000..5bc03f56029 --- /dev/null +++ b/changelog/unreleased/preferred-language.md @@ -0,0 +1,7 @@ +Enhancement: Add preferred language to user settings + +We have added the preferred language to the libre-graph api & added endpoints for that to ocis. + +https://github.com/owncloud/ocis/pull/7720 +https://github.com/owncloud/ocis/issues/5455 +https://github.com/owncloud/libre-graph-api/pull/130 From 6f327b6881e9a3beec80be5f284e9c467dafbd83 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 13 Nov 2023 15:53:43 +0100 Subject: [PATCH 06/16] bump libre-graph-api-go Signed-off-by: Christian Richter --- go.mod | 2 +- go.sum | 4 +- .../owncloud/libre-graph-api-go/README.md | 1 + .../libre-graph-api-go/api_me_user.go | 114 ++++++++++++++++++ .../owncloud/libre-graph-api-go/model_user.go | 37 ++++++ vendor/modules.txt | 2 +- 6 files changed, 156 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 9fd29de3c4b..72afa1f60d2 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/onsi/gomega v1.29.0 github.com/open-policy-agent/opa v0.51.0 github.com/orcaman/concurrent-map v1.0.0 - github.com/owncloud/libre-graph-api-go v1.0.5-0.20231107135330-011e9d4c45e3 + github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.9 github.com/prometheus/client_golang v1.17.0 diff --git a/go.sum b/go.sum index 791546b027e..9663d29f787 100644 --- a/go.sum +++ b/go.sum @@ -1774,8 +1774,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20231107135330-011e9d4c45e3 h1:eUE3kNgr8PwcXeUKFkuEuz1+4hfCCmq+rKYQzk0OxtY= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20231107135330-011e9d4c45e3/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb h1:KFnmkGvHY+6k6IZ9I1w5Ia24VbALYms+Y6W7LrsUbsE= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/vendor/github.com/owncloud/libre-graph-api-go/README.md b/vendor/github.com/owncloud/libre-graph-api-go/README.md index 9e4810624b4..00f539bd35f 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/README.md +++ b/vendor/github.com/owncloud/libre-graph-api-go/README.md @@ -134,6 +134,7 @@ Class | Method | HTTP request | Description *MeDriveRootChildrenApi* | [**HomeGetChildren**](docs/MeDriveRootChildrenApi.md#homegetchildren) | **Get** /v1.0/me/drive/root/children | Get children from drive *MeDrivesApi* | [**ListMyDrives**](docs/MeDrivesApi.md#listmydrives) | **Get** /v1.0/me/drives | Get all drives where the current user is a regular member of *MeUserApi* | [**GetOwnUser**](docs/MeUserApi.md#getownuser) | **Get** /v1.0/me | Get current user +*MeUserApi* | [**UpdateOwnUser**](docs/MeUserApi.md#updateownuser) | **Patch** /v1.0/me | Update the current user *RoleManagementApi* | [**GetPermissionRoleDefinition**](docs/RoleManagementApi.md#getpermissionroledefinition) | **Get** /v1beta1/roleManagement/permissions/roleDefinitions/{role-id} | Get unifiedRoleDefinition *RoleManagementApi* | [**ListPermissionRoleDefinitions**](docs/RoleManagementApi.md#listpermissionroledefinitions) | **Get** /v1beta1/roleManagement/permissions/roleDefinitions | List roleDefinitions *TagsApi* | [**AssignTags**](docs/TagsApi.md#assigntags) | **Put** /v1.0/extensions/org.libregraph/tags | Assign tags to a resource diff --git a/vendor/github.com/owncloud/libre-graph-api-go/api_me_user.go b/vendor/github.com/owncloud/libre-graph-api-go/api_me_user.go index 3d9820dfaf9..5613c7fc76b 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/api_me_user.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/api_me_user.go @@ -135,3 +135,117 @@ func (a *MeUserApiService) GetOwnUserExecute(r ApiGetOwnUserRequest) (*User, *ht return localVarReturnValue, localVarHTTPResponse, nil } + +type ApiUpdateOwnUserRequest struct { + ctx context.Context + ApiService *MeUserApiService + user *User +} + +// New user values +func (r ApiUpdateOwnUserRequest) User(user User) ApiUpdateOwnUserRequest { + r.user = &user + return r +} + +func (r ApiUpdateOwnUserRequest) Execute() (*User, *http.Response, error) { + return r.ApiService.UpdateOwnUserExecute(r) +} + +/* +UpdateOwnUser Update the current user + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return ApiUpdateOwnUserRequest +*/ +func (a *MeUserApiService) UpdateOwnUser(ctx context.Context) ApiUpdateOwnUserRequest { + return ApiUpdateOwnUserRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// @return User +func (a *MeUserApiService) UpdateOwnUserExecute(r ApiUpdateOwnUserRequest) (*User, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPatch + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *User + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeUserApiService.UpdateOwnUser") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1.0/me" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = r.user + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + var v OdataError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_user.go b/vendor/github.com/owncloud/libre-graph-api-go/model_user.go index b3170f2ea33..4a6262a73fe 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/model_user.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_user.go @@ -45,6 +45,8 @@ type User struct { GivenName *string `json:"givenName,omitempty"` // The user`s type. This can be either \"Member\" for regular user, or \"Guest\" for guest users. UserType *string `json:"userType,omitempty"` + // Represents the users language setting, ISO-639-1 Code + PreferredLanguage *string `json:"preferredLanguage,omitempty"` } // NewUser instantiates a new User object @@ -512,6 +514,38 @@ func (o *User) SetUserType(v string) { o.UserType = &v } +// GetPreferredLanguage returns the PreferredLanguage field value if set, zero value otherwise. +func (o *User) GetPreferredLanguage() string { + if o == nil || IsNil(o.PreferredLanguage) { + var ret string + return ret + } + return *o.PreferredLanguage +} + +// GetPreferredLanguageOk returns a tuple with the PreferredLanguage field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *User) GetPreferredLanguageOk() (*string, bool) { + if o == nil || IsNil(o.PreferredLanguage) { + return nil, false + } + return o.PreferredLanguage, true +} + +// HasPreferredLanguage returns a boolean if a field has been set. +func (o *User) HasPreferredLanguage() bool { + if o != nil && !IsNil(o.PreferredLanguage) { + return true + } + + return false +} + +// SetPreferredLanguage gets a reference to the given string and assigns it to the PreferredLanguage field. +func (o *User) SetPreferredLanguage(v string) { + o.PreferredLanguage = &v +} + func (o User) MarshalJSON() ([]byte, error) { toSerialize, err := o.ToMap() if err != nil { @@ -564,6 +598,9 @@ func (o User) ToMap() (map[string]interface{}, error) { if !IsNil(o.UserType) { toSerialize["userType"] = o.UserType } + if !IsNil(o.PreferredLanguage) { + toSerialize["preferredLanguage"] = o.PreferredLanguage + } return toSerialize, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 9775a1059cb..b7246cc7305 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1555,7 +1555,7 @@ github.com/opentracing/opentracing-go/log # github.com/orcaman/concurrent-map v1.0.0 ## explicit github.com/orcaman/concurrent-map -# github.com/owncloud/libre-graph-api-go v1.0.5-0.20231107135330-011e9d4c45e3 +# github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb ## explicit; go 1.18 github.com/owncloud/libre-graph-api-go # github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c From aea129b6585ab9f89b69ad612dbb75c5c15431d0 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 13 Nov 2023 15:58:52 +0100 Subject: [PATCH 07/16] remove obsolete fields from ldap Signed-off-by: Christian Richter --- services/graph/pkg/identity/ldap.go | 32 ++++++++++++----------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index d8fcfed4b6d..e3283605a66 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -72,15 +72,14 @@ type LDAP struct { } type userAttributeMap struct { - displayName string - id string - mail string - userName string - givenName string - surname string - accountEnabled string - userType string - preferredLanguage string + displayName string + id string + mail string + userName string + givenName string + surname string + accountEnabled string + userType string } type ldapAttributeValues map[string][]string @@ -301,12 +300,11 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph. mr := ldap.ModifyRequest{DN: e.DN} properties := map[string]string{ - i.userAttributeMap.displayName: user.GetDisplayName(), - i.userAttributeMap.mail: user.GetMail(), - i.userAttributeMap.surname: user.GetSurname(), - i.userAttributeMap.givenName: user.GetGivenName(), - i.userAttributeMap.userType: user.GetUserType(), - i.userAttributeMap.preferredLanguage: user.GetPreferredLanguage(), + i.userAttributeMap.displayName: user.GetDisplayName(), + i.userAttributeMap.mail: user.GetMail(), + i.userAttributeMap.surname: user.GetSurname(), + i.userAttributeMap.givenName: user.GetGivenName(), + i.userAttributeMap.userType: user.GetUserType(), } for attribute, value := range properties { @@ -396,7 +394,6 @@ func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) { i.userAttributeMap.givenName, i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, - i.userAttributeMap.preferredLanguage, } filter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass) @@ -524,7 +521,6 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) { i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, - i.userAttributeMap.preferredLanguage, } return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter) } @@ -604,7 +600,6 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib i.userAttributeMap.givenName, i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, - i.userAttributeMap.preferredLanguage, }, nil, ) @@ -861,7 +856,6 @@ func (i *LDAP) getUserAttrTypes() []string { "userPassword", i.userAttributeMap.accountEnabled, i.userAttributeMap.userType, - i.userAttributeMap.preferredLanguage, } } From 3e203fe751c060d64a795b382a9203f4f50ecec4 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 14 Nov 2023 14:56:28 +0100 Subject: [PATCH 08/16] add mocks for valueservice Signed-off-by: Christian Richter --- services/graph/mocks/value_service.go | 164 ++++++++++++++++++++ services/graph/pkg/service/v0/users_test.go | 18 +++ 2 files changed, 182 insertions(+) create mode 100644 services/graph/mocks/value_service.go diff --git a/services/graph/mocks/value_service.go b/services/graph/mocks/value_service.go new file mode 100644 index 00000000000..1872da1de14 --- /dev/null +++ b/services/graph/mocks/value_service.go @@ -0,0 +1,164 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + context "context" + + client "go-micro.dev/v4/client" + + mock "github.com/stretchr/testify/mock" + + v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" +) + +// ValueService is an autogenerated mock type for the ValueService type +type ValueService struct { + mock.Mock +} + +// GetValue provides a mock function with given fields: ctx, in, opts +func (_m *ValueService) GetValue(ctx context.Context, in *v0.GetValueRequest, opts ...client.CallOption) (*v0.GetValueResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v0.GetValueResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v0.GetValueRequest, ...client.CallOption) (*v0.GetValueResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v0.GetValueRequest, ...client.CallOption) *v0.GetValueResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0.GetValueResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v0.GetValueRequest, ...client.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetValueByUniqueIdentifiers provides a mock function with given fields: ctx, in, opts +func (_m *ValueService) GetValueByUniqueIdentifiers(ctx context.Context, in *v0.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*v0.GetValueResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v0.GetValueResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v0.GetValueByUniqueIdentifiersRequest, ...client.CallOption) (*v0.GetValueResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v0.GetValueByUniqueIdentifiersRequest, ...client.CallOption) *v0.GetValueResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0.GetValueResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v0.GetValueByUniqueIdentifiersRequest, ...client.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListValues provides a mock function with given fields: ctx, in, opts +func (_m *ValueService) ListValues(ctx context.Context, in *v0.ListValuesRequest, opts ...client.CallOption) (*v0.ListValuesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v0.ListValuesResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v0.ListValuesRequest, ...client.CallOption) (*v0.ListValuesResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v0.ListValuesRequest, ...client.CallOption) *v0.ListValuesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0.ListValuesResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v0.ListValuesRequest, ...client.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SaveValue provides a mock function with given fields: ctx, in, opts +func (_m *ValueService) SaveValue(ctx context.Context, in *v0.SaveValueRequest, opts ...client.CallOption) (*v0.SaveValueResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v0.SaveValueResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v0.SaveValueRequest, ...client.CallOption) (*v0.SaveValueResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v0.SaveValueRequest, ...client.CallOption) *v0.SaveValueResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0.SaveValueResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v0.SaveValueRequest, ...client.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewValueService creates a new instance of ValueService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewValueService(t interface { + mock.TestingT + Cleanup(func()) +}) *ValueService { + mock := &ValueService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go index 8e67a5152d2..87074aa30c5 100644 --- a/services/graph/pkg/service/v0/users_test.go +++ b/services/graph/pkg/service/v0/users_test.go @@ -47,6 +47,7 @@ var _ = Describe("Users", func() { gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher roleService *mocks.RoleService + valueService *mocks.ValueService identityBackend *identitymocks.Backend rr *httptest.ResponseRecorder @@ -73,6 +74,7 @@ var _ = Describe("Users", func() { identityBackend = &identitymocks.Backend{} roleService = &mocks.RoleService{} + valueService = &mocks.ValueService{} rr = httptest.NewRecorder() ctx = context.Background() @@ -90,6 +92,7 @@ var _ = Describe("Users", func() { service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithRoleService(roleService), + service.WithValueService(valueService), ) }) @@ -102,6 +105,14 @@ var _ = Describe("Users", func() { }) It("gets the information", func() { + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + : &settingsmsg.ListOptionValue{}, + } + }, + }, nil) + r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) svc.GetMe(rr, r) @@ -117,6 +128,7 @@ var _ = Describe("Users", func() { }, } identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=memberOf", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) @@ -145,6 +157,7 @@ var _ = Describe("Users", func() { }, } roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=appRoleAssignments", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) @@ -413,6 +426,8 @@ var _ = Describe("Users", func() { user.SetId("user1") identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) + r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", *user.Id) @@ -455,6 +470,7 @@ var _ = Describe("Users", func() { }, }, }, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=drive", nil) rctx := chi.NewRouteContext() @@ -490,6 +506,7 @@ var _ = Describe("Users", func() { }, }, }, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=drives", nil) rctx := chi.NewRouteContext() @@ -521,6 +538,7 @@ var _ = Describe("Users", func() { }, } roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users/user1?$expand=appRoleAssignments", nil) rctx := chi.NewRouteContext() From e2e1d1da44f2649d50ff582425fd69e014b7da00 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 14 Nov 2023 15:06:04 +0100 Subject: [PATCH 09/16] fix(graph users test): provide proto list value example on how to set language Co-authored-by: Florian Schade Signed-of-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 2 +- services/graph/pkg/service/v0/users_test.go | 160 ++++++++++++++++---- 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index c42b97f6201..4ed06de33e0 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -639,7 +639,7 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { render.NoContent(w, r) } -// PatchMe implements the Service Interface. Updates the specified attributes of the +// PatchMe implements the Service Interface. Updates the specified attributes of the current user func (g Graph) PatchMe(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Debug().Msg("calling patch me") diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go index 87074aa30c5..44f271e3ffc 100644 --- a/services/graph/pkg/service/v0/users_test.go +++ b/services/graph/pkg/service/v0/users_test.go @@ -13,14 +13,18 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/stretchr/testify/mock" + "go-micro.dev/v4/client" + "google.golang.org/grpc" + + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/owncloud/ocis/v2/ocis-pkg/shared" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -29,9 +33,6 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/stretchr/testify/mock" - "go-micro.dev/v4/client" - "google.golang.org/grpc" ) type userList struct { @@ -105,13 +106,24 @@ var _ = Describe("Users", func() { }) It("gets the information", func() { - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{ - Value: &settingsmsg.ValueWithIdentifier{ - Value: &settingsmsg.Value{ - : &settingsmsg.ListOptionValue{}, - } - }, - }, nil) + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) @@ -128,8 +140,24 @@ var _ = Describe("Users", func() { }, } identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=memberOf", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) svc.GetMe(rr, r) @@ -157,8 +185,24 @@ var _ = Describe("Users", func() { }, } roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=appRoleAssignments", nil) r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) svc.GetMe(rr, r) @@ -426,8 +470,24 @@ var _ = Describe("Users", func() { user.SetId("user1") identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", *user.Id) @@ -470,8 +530,24 @@ var _ = Describe("Users", func() { }, }, }, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=drive", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", *user.Id) @@ -506,8 +582,24 @@ var _ = Describe("Users", func() { }, }, }, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=drives", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", *user.Id) @@ -538,8 +630,24 @@ var _ = Describe("Users", func() { }, } roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil) - valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil) - + valueService.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything, mock.Anything). + Return(&settings.GetValueResponse{ + Value: &settingsmsg.ValueWithIdentifier{ + Value: &settingsmsg.Value{ + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{ + Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: "it", + }, + }, + }, + }, + }, + }, + }, + }, nil) r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users/user1?$expand=appRoleAssignments", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", user.GetId()) From c5d3b74cd498ab8a6e4d88c456182afb73146f23 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 14 Nov 2023 15:45:27 +0100 Subject: [PATCH 10/16] filter out unallowed fields in changes Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 40 +++++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 4ed06de33e0..b03af6c91e0 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -649,7 +649,25 @@ func (g Graph) PatchMe(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") return } - g.patchUser(w, r, userID) + changes := libregraph.NewUser() + err := StrictJSONUnmarshal(r.Body, changes) + if err != nil { + logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update user: invalid request body") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, + fmt.Sprintf("invalid request body: %s", err.Error())) + return + } + if _, ok := changes.GetDisplayNameOk(); ok { + logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change own displayname") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change own displayname") + return + } + if _, ok := changes.GetMailOk(); ok { + logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change own mail") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change own mail") + return + } + g.patchUser(w, r, userID, changes) } // PatchUser implements the Service Interface. Updates the specified attributes of an @@ -663,10 +681,18 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") return } - g.patchUser(w, r, nameOrID) + changes := libregraph.NewUser() + err = StrictJSONUnmarshal(r.Body, changes) + if err != nil { + logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update user: invalid request body") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, + fmt.Sprintf("invalid request body: %s", err.Error())) + return + } + g.patchUser(w, r, nameOrID, changes) } -func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string) { +func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string, changes *libregraph.User) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Debug().Msg("calling patch user") @@ -691,14 +717,6 @@ func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") return } - changes := libregraph.NewUser() - err = StrictJSONUnmarshal(r.Body, changes) - if err != nil { - logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update user: invalid request body") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, - fmt.Sprintf("invalid request body: %s", err.Error())) - return - } if reflect.ValueOf(*changes).IsZero() { logger.Debug().Interface("body", r.Body).Msg("ignoring empty request body") From 407238efa5dde002cc8c209c52e0eb9d8e95307a Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 14 Nov 2023 17:23:45 +0100 Subject: [PATCH 11/16] incorporate requested changes Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index b03af6c91e0..5f9f6a4e0bc 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -91,8 +91,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService) if err != nil { logger.Error().Err(err).Msg("could not get user language") - render.Status(r, http.StatusInternalServerError) - render.JSON(w, r, me) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get user language") return } From 1e34e43c100802586236082423d1426534731da3 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 15 Nov 2023 07:13:11 +0100 Subject: [PATCH 12/16] Add language to PostUser, allow admin to read user values Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 51 ++++++++++++++++++--- services/settings/pkg/service/v0/service.go | 7 +-- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 5f9f6a4e0bc..93f41050da8 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/google/uuid" "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "net/http" "net/url" @@ -88,7 +89,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { } } - preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService) + preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService, me.GetId()) if err != nil { logger.Error().Err(err).Msg("could not get user language") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get user language") @@ -318,6 +319,8 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { u.SetUserType("Member") } + userLang := u.GetPreferredLanguage() + logger.Debug().Interface("user", u).Msg("calling create user on backend") if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil { logger.Error().Err(err).Msg("could not create user: backend error") @@ -340,6 +343,35 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { } } + if userLang != "" { + langUUID, err := uuid.NewUUID() + if err != nil { + logger.Error().Err(err).Msg("could not create user: error generating uuid") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error generating uuid") + return + } + _, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{ + Value: &settingsmsg.Value{ + Id: langUUID.String(), + BundleId: defaults.BundleUUIDProfile, + SettingId: defaults.SettingUUIDProfileLanguage, + AccountUuid: u.GetId(), + Resource: &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_USER, + }, + Value: &settingsmsg.Value_ListValue{ + ListValue: &settingsmsg.ListValue{Values: []*settingsmsg.ListOptionValue{ + { + Option: &settingsmsg.ListOptionValue_StringValue{ + StringValue: userLang, + }, + }, + }}, + }, + }, + }) + } + e := events.UserCreated{UserID: *u.Id} if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { e.Executant = currentUser.GetId() @@ -487,7 +519,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { } } - preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService) + preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService, user.GetId()) if err != nil { logger.Error().Err(err).Msg("could not get user language") render.Status(r, http.StatusInternalServerError) @@ -502,9 +534,9 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { } // getUserLanguage returns the language of the user in the context. -func getUserLanguage(ctx context.Context, valueService settingssvc.ValueService) (string, string, error) { +func getUserLanguage(ctx context.Context, valueService settingssvc.ValueService, userID string) (string, string, error) { gvr, err := valueService.GetValueByUniqueIdentifiers(ctx, &settingssvc.GetValueByUniqueIdentifiersRequest{ - AccountUuid: revactx.ContextMustGetUser(ctx).GetId().GetOpaqueId(), + AccountUuid: userID, SettingId: defaults.SettingUUIDProfileLanguage, }) if err != nil { @@ -734,11 +766,16 @@ func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string preferredLanguage, ok := changes.GetPreferredLanguageOk() if ok { - _, vID, err := getUserLanguage(r.Context(), g.valueService) + _, vID, err := getUserLanguage(r.Context(), g.valueService, oldUserValues.GetId()) if err != nil { logger.Error().Err(err).Msg("could not get user language") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get user language") - return + tvID, err := uuid.NewUUID() + if err != nil { + logger.Error().Err(err).Msg("could not create user: error generating uuid") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error generating uuid") + return + } + vID = tvID.String() } _, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{ Value: &settingsmsg.Value{ diff --git a/services/settings/pkg/service/v0/service.go b/services/settings/pkg/service/v0/service.go index 48897b1a09e..36db0f46429 100644 --- a/services/settings/pkg/service/v0/service.go +++ b/services/settings/pkg/service/v0/service.go @@ -274,8 +274,8 @@ func (g Service) RemoveSettingFromBundle(ctx context.Context, req *settingssvc.R // SaveValue implements the ValueServiceHandler interface func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueRequest, res *settingssvc.SaveValueResponse) error { req.Value.AccountUuid = getValidatedAccountUUID(ctx, req.Value.AccountUuid) - - if !g.isCurrentUser(ctx, req.Value.AccountUuid) { + ctxUser, _ := metadata.Get(ctx, middleware.AccountID) + if !g.isCurrentUser(ctx, req.Value.AccountUuid) && ctxUser != g.config.AdminUserID { return merrors.Forbidden(g.id, "can't save value for another user") } @@ -316,7 +316,8 @@ func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, // GetValueByUniqueIdentifiers implements the ValueService interface func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, res *settingssvc.GetValueResponse) error { req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) - if !g.isCurrentUser(ctx, req.AccountUuid) { + ctxUser, _ := metadata.Get(ctx, middleware.AccountID) + if !g.isCurrentUser(ctx, req.AccountUuid) && ctxUser != g.config.AdminUserID { return merrors.Forbidden(g.id, "can't get value of another user") } if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil { From 5e02658873ad6a495f2e35f84ac018eb325e6fd4 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 15 Nov 2023 09:30:32 +0100 Subject: [PATCH 13/16] Fix wrong variable Signed-off-by: Christian Richter Co-authored-by: Julian Koberg Co-authored-by: Ralf Haferkamp --- services/graph/pkg/service/v0/users.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 93f41050da8..f756396a7b4 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -344,15 +344,8 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { } if userLang != "" { - langUUID, err := uuid.NewUUID() - if err != nil { - logger.Error().Err(err).Msg("could not create user: error generating uuid") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error generating uuid") - return - } _, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{ Value: &settingsmsg.Value{ - Id: langUUID.String(), BundleId: defaults.BundleUUIDProfile, SettingId: defaults.SettingUUIDProfileLanguage, AccountUuid: u.GetId(), @@ -782,7 +775,7 @@ func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string Id: vID, BundleId: defaults.BundleUUIDProfile, SettingId: defaults.SettingUUIDProfileLanguage, - AccountUuid: nameOrID, + AccountUuid: oldUserValues.GetId(), Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_USER, }, From 414cdd0b518481478505c716a50b3b84801fe6af Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 15 Nov 2023 10:49:02 +0100 Subject: [PATCH 14/16] add admin check Signed-off-by: Christian Richter --- services/settings/pkg/service/v0/service.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/services/settings/pkg/service/v0/service.go b/services/settings/pkg/service/v0/service.go index 36db0f46429..38ecbe2348c 100644 --- a/services/settings/pkg/service/v0/service.go +++ b/services/settings/pkg/service/v0/service.go @@ -275,7 +275,7 @@ func (g Service) RemoveSettingFromBundle(ctx context.Context, req *settingssvc.R func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueRequest, res *settingssvc.SaveValueResponse) error { req.Value.AccountUuid = getValidatedAccountUUID(ctx, req.Value.AccountUuid) ctxUser, _ := metadata.Get(ctx, middleware.AccountID) - if !g.isCurrentUser(ctx, req.Value.AccountUuid) && ctxUser != g.config.AdminUserID { + if !g.isCurrentUser(ctx, req.Value.AccountUuid) && !g.isAdmin(ctxUser) { return merrors.Forbidden(g.id, "can't save value for another user") } @@ -296,6 +296,20 @@ func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueReques return nil } +func (g Service) isAdmin(userId string) bool { + assignedRoles, err := g.manager.ListRoleAssignments(userId) + if err != nil { + return false + } + for _, role := range assignedRoles { + if role.RoleId == defaults.BundleUUIDRoleAdmin { + return true + } + } + return false + +} + // GetValue implements the ValueServiceHandler interface func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, res *settingssvc.GetValueResponse) error { if validationError := validateGetValue(req); validationError != nil { @@ -317,7 +331,7 @@ func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, res *settingssvc.GetValueResponse) error { req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) ctxUser, _ := metadata.Get(ctx, middleware.AccountID) - if !g.isCurrentUser(ctx, req.AccountUuid) && ctxUser != g.config.AdminUserID { + if !g.isCurrentUser(ctx, req.AccountUuid) && !g.isAdmin(ctxUser) { return merrors.Forbidden(g.id, "can't get value of another user") } if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil { From 15699f02b8ce9670b93a824b0c5ecd81758aa0d8 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 15 Nov 2023 12:02:49 +0100 Subject: [PATCH 15/16] Incorporate requested changes Co-authored-by: Julian Koberg Co-authored-by: Ralf Haferkamp Co-authored-by: Michael Barz Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 30 +++++---------------- services/settings/pkg/service/v0/service.go | 20 ++------------ 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index f756396a7b4..0bf5c704262 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -319,8 +319,6 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { u.SetUserType("Member") } - userLang := u.GetPreferredLanguage() - logger.Debug().Interface("user", u).Msg("calling create user on backend") if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil { logger.Error().Err(err).Msg("could not create user: backend error") @@ -343,28 +341,6 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { } } - if userLang != "" { - _, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{ - Value: &settingsmsg.Value{ - BundleId: defaults.BundleUUIDProfile, - SettingId: defaults.SettingUUIDProfileLanguage, - AccountUuid: u.GetId(), - Resource: &settingsmsg.Resource{ - Type: settingsmsg.Resource_TYPE_USER, - }, - Value: &settingsmsg.Value_ListValue{ - ListValue: &settingsmsg.ListValue{Values: []*settingsmsg.ListOptionValue{ - { - Option: &settingsmsg.ListOptionValue_StringValue{ - StringValue: userLang, - }, - }, - }}, - }, - }, - }) - } - e := events.UserCreated{UserID: *u.Id} if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { e.Executant = currentUser.GetId() @@ -713,6 +689,12 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { fmt.Sprintf("invalid request body: %s", err.Error())) return } + if _, ok := changes.GetPreferredLanguageOk(); ok { + logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change other users language") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change other users language") + return + } + g.patchUser(w, r, nameOrID, changes) } diff --git a/services/settings/pkg/service/v0/service.go b/services/settings/pkg/service/v0/service.go index 38ecbe2348c..9513be9d6e8 100644 --- a/services/settings/pkg/service/v0/service.go +++ b/services/settings/pkg/service/v0/service.go @@ -274,8 +274,7 @@ func (g Service) RemoveSettingFromBundle(ctx context.Context, req *settingssvc.R // SaveValue implements the ValueServiceHandler interface func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueRequest, res *settingssvc.SaveValueResponse) error { req.Value.AccountUuid = getValidatedAccountUUID(ctx, req.Value.AccountUuid) - ctxUser, _ := metadata.Get(ctx, middleware.AccountID) - if !g.isCurrentUser(ctx, req.Value.AccountUuid) && !g.isAdmin(ctxUser) { + if !g.isCurrentUser(ctx, req.Value.AccountUuid) { return merrors.Forbidden(g.id, "can't save value for another user") } @@ -296,20 +295,6 @@ func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueReques return nil } -func (g Service) isAdmin(userId string) bool { - assignedRoles, err := g.manager.ListRoleAssignments(userId) - if err != nil { - return false - } - for _, role := range assignedRoles { - if role.RoleId == defaults.BundleUUIDRoleAdmin { - return true - } - } - return false - -} - // GetValue implements the ValueServiceHandler interface func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, res *settingssvc.GetValueResponse) error { if validationError := validateGetValue(req); validationError != nil { @@ -330,8 +315,7 @@ func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, // GetValueByUniqueIdentifiers implements the ValueService interface func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, res *settingssvc.GetValueResponse) error { req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) - ctxUser, _ := metadata.Get(ctx, middleware.AccountID) - if !g.isCurrentUser(ctx, req.AccountUuid) && !g.isAdmin(ctxUser) { + if !g.isCurrentUser(ctx, req.AccountUuid) { return merrors.Forbidden(g.id, "can't get value of another user") } if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil { From fde6a4b67a7bc5ec20d3fea65e5f5c49e8df4647 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 15 Nov 2023 13:36:35 +0100 Subject: [PATCH 16/16] [Experiment] Remove language object from GetUser Co-authored-by: Florian Schade Signed-off-by: Christian Richter --- services/graph/pkg/service/v0/users.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 0bf5c704262..0a2ee154629 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -488,16 +488,6 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { } } - preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService, user.GetId()) - if err != nil { - logger.Error().Err(err).Msg("could not get user language") - render.Status(r, http.StatusInternalServerError) - render.JSON(w, r, user) - return - } - - user.PreferredLanguage = &preferedLanguage - render.Status(r, http.StatusOK) render.JSON(w, r, user) }