diff --git a/accounts/pkg/service/v0/accounts.go b/accounts/pkg/service/v0/accounts.go index c26d86110..090246c9b 100644 --- a/accounts/pkg/service/v0/accounts.go +++ b/accounts/pkg/service/v0/accounts.go @@ -16,8 +16,10 @@ import ( "github.com/golang/protobuf/ptypes/empty" fieldmask_utils "github.com/mennanov/fieldmask-utils" merrors "github.com/micro/go-micro/v2/errors" + "github.com/micro/go-micro/v2/metadata" "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/accounts/pkg/storage" + "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/roles" settings "github.com/owncloud/ocis/settings/pkg/proto/v0" settings_svc "github.com/owncloud/ocis/settings/pkg/service/v0" @@ -75,6 +77,22 @@ func (s Service) hasAccountManagementPermissions(ctx context.Context) bool { return s.RoleManager.FindPermissionByID(ctx, roleIDs, AccountManagementPermissionID) != nil } +func (s Service) hasSelfManagementPermissions(ctx context.Context) bool { + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(ctx) + if !ok { + /** + * FIXME: with this we are skipping permission checks on all requests that are coming in without roleIDs in the + * metadata context. This is a huge security impairment, as that's the case not only for grpc requests but also + * for unauthenticated http requests and http requests coming in without hitting the ocis-proxy first. + */ + return true + } + + // check if permission is present in roles of the authenticated account + return s.RoleManager.FindPermissionByID(ctx, roleIDs, SelfManagementPermissionID) != nil +} + // serviceUserToIndex temporarily adds a service user to the index, which is supposed to be removed before the lock on the handler function is released func (s Service) serviceUserToIndex() (teardownServiceUser func()) { if s.Config.ServiceUser.Username != "" && s.Config.ServiceUser.UUID != "" { @@ -105,9 +123,12 @@ func (s Service) getInMemoryServiceUser() proto.Account { // ListAccounts implements the AccountsServiceHandler interface // the query contains account properties func (s Service) ListAccounts(ctx context.Context, in *proto.ListAccountsRequest, out *proto.ListAccountsResponse) (err error) { - if !s.hasAccountManagementPermissions(ctx) { + hasSelf := s.hasSelfManagementPermissions(ctx) + hasManagement := s.hasAccountManagementPermissions(ctx) + if !hasSelf && !hasManagement { return merrors.Forbidden(s.id, "no permission for ListAccounts") } + onlySelf := hasSelf && !hasManagement accLock.Lock() defer accLock.Unlock() @@ -146,6 +167,15 @@ func (s Service) ListAccounts(ctx context.Context, in *proto.ListAccountsRequest return nil } + if onlySelf { + // limit list to own account id + if aid, ok := metadata.Get(ctx, middleware.AccountID); ok { + in.Query = "id eq '" + aid + "'" + } else { + return merrors.InternalServerError(s.id, "account id not in context") + } + } + if in.Query == "" { err = s.repo.LoadAccounts(ctx, &out.Accounts) if err != nil { @@ -202,9 +232,12 @@ func (s Service) findAccountsByQuery(ctx context.Context, query string) ([]strin // GetAccount implements the AccountsServiceHandler interface func (s Service) GetAccount(ctx context.Context, in *proto.GetAccountRequest, out *proto.Account) (err error) { - if !s.hasAccountManagementPermissions(ctx) { + hasSelf := s.hasSelfManagementPermissions(ctx) + hasManagement := s.hasAccountManagementPermissions(ctx) + if !hasSelf && !hasManagement { return merrors.Forbidden(s.id, "no permission for GetAccount") } + onlySelf := hasSelf && !hasManagement accLock.Lock() defer accLock.Unlock() @@ -213,6 +246,17 @@ func (s Service) GetAccount(ctx context.Context, in *proto.GetAccountRequest, ou return merrors.InternalServerError(s.id, "could not clean up account id: %v", err.Error()) } + if onlySelf { + // limit get to own account id + if aid, ok := metadata.Get(ctx, middleware.AccountID); ok { + if id != aid { + return merrors.Forbidden(s.id, "no permission for GetAccount of another user") + } + } else { + return merrors.InternalServerError(s.id, "account id not in context") + } + } + if err = s.repo.LoadAccount(ctx, id, out); err != nil { if storage.IsNotFoundErr(err) { return merrors.NotFound(s.id, "account not found: %v", err.Error()) diff --git a/accounts/pkg/service/v0/permissions.go b/accounts/pkg/service/v0/permissions.go index 30730659f..22c5f3b4f 100644 --- a/accounts/pkg/service/v0/permissions.go +++ b/accounts/pkg/service/v0/permissions.go @@ -11,13 +11,17 @@ import ( const ( // AccountManagementPermissionID is the hardcoded setting UUID for the account management permission - AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73" + AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73" // AccountManagementPermissionName is the hardcoded setting name for the account management permission AccountManagementPermissionName string = "account-management" // GroupManagementPermissionID is the hardcoded setting UUID for the group management permission - GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245" + GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245" // GroupManagementPermissionName is the hardcoded setting name for the group management permission - GroupManagementPermissionName string = "group-management" + GroupManagementPermissionName string = "group-management" + // SelfManagementPermissionID is the hardcoded setting UUID for the self management permission + SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e" + // SelfManagementPermissionName is the hardcoded setting name for the self management permission + SelfManagementPermissionName string = "self-management" ) // RegisterPermissions registers permissions for account management and group management with the settings service. @@ -78,5 +82,24 @@ func generateAccountManagementPermissionsRequests() []settings.AddSettingToBundl }, }, }, + { + BundleId: ssvc.BundleUUIDRoleUser, + Setting: &settings.Setting{ + Id: SelfManagementPermissionID, + Name: SelfManagementPermissionName, + DisplayName: "Self Management", + Description: "This permission gives access to self management.", + Resource: &settings.Resource{ + Type: settings.Resource_TYPE_USER, + Id: "me", + }, + Value: &settings.Setting_PermissionValue{ + PermissionValue: &settings.Permission{ + Operation: settings.Permission_OPERATION_READWRITE, + Constraint: settings.Permission_CONSTRAINT_OWN, + }, + }, + }, + }, } } diff --git a/glauth/go.sum b/glauth/go.sum index 4fb54e306..dade35176 100644 --- a/glauth/go.sum +++ b/glauth/go.sum @@ -55,6 +55,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzS github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CiscoM31/godata v0.0.0-20201003040028-eadcd34e7f06 h1:FKxVU/j9Dd8Je0YkVkm8Fxpz9zIeN21SEkcbzA6NWgY= +github.com/CiscoM31/godata v0.0.0-20201003040028-eadcd34e7f06/go.mod h1:tjaihnMBH6p5DVnGBksDQQHpErbrLvb9ek6cEWuyc7E= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc= github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0= diff --git a/ocis-pkg/middleware/account.go b/ocis-pkg/middleware/account.go index af04acbce..0a1e90775 100644 --- a/ocis-pkg/middleware/account.go +++ b/ocis-pkg/middleware/account.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/cs3org/reva/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/user" "github.com/micro/go-micro/v2/metadata" "github.com/owncloud/ocis/ocis-pkg/account" ) @@ -49,18 +50,21 @@ func ExtractAccountUUID(opts ...account.Option) func(http.Handler) http.Handler return } - user, err := tokenManager.DismantleToken(r.Context(), token) + u, err := tokenManager.DismantleToken(r.Context(), token) if err != nil { opt.Logger.Error().Err(err) return } + // store user in context for request + ctx := user.ContextSetUser(r.Context(), u) + // Important: user.Id.OpaqueId is the AccountUUID. Set this way in the account uuid middleware in ocis-proxy. // https://github.com/owncloud/ocis-proxy/blob/ea254d6036592cf9469d757d1295e0c4309d1e63/pkg/middleware/account_uuid.go#L109 - ctx := context.WithValue(r.Context(), UUIDKey, user.Id.OpaqueId) + ctx = context.WithValue(ctx, UUIDKey, u.Id.OpaqueId) // TODO: implement token manager in cs3org/reva that uses generic metadata instead of access token from header. - ctx = metadata.Set(ctx, AccountID, user.Id.OpaqueId) - ctx = metadata.Set(ctx, RoleIDs, string(user.Opaque.Map["roles"].Value)) + ctx = metadata.Set(ctx, AccountID, u.Id.OpaqueId) + ctx = metadata.Set(ctx, RoleIDs, string(u.Opaque.Map["roles"].Value)) next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/ocs/pkg/middleware/accesstoken.go b/ocs/pkg/middleware/accesstoken.go deleted file mode 100644 index 9565f7c8a..000000000 --- a/ocs/pkg/middleware/accesstoken.go +++ /dev/null @@ -1,40 +0,0 @@ -package middleware - -import ( - "net/http" - - "github.com/cs3org/reva/pkg/token/manager/jwt" - "github.com/cs3org/reva/pkg/user" -) - -// AccessToken middleware is used to set the user from an x-access-token to the context -func AccessToken(opts ...Option) func(next http.Handler) http.Handler { - opt := newOptions(opts...) - - return func(next http.Handler) http.Handler { - // TODO: handle error - tokenManager, err := jwt.New(map[string]interface{}{ - "secret": opt.TokenManagerConfig.JWTSecret, - "expires": int64(60), - }) - if err != nil { - opt.Logger.Fatal().Err(err).Msgf("Could not initialize token-manager") - } - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("x-access-token") - if token != "" { - u, err := tokenManager.DismantleToken(r.Context(), token) - if err != nil { - opt.Logger.Error().Err(err).Msg("could not dismantle token") - w.WriteHeader(http.StatusInternalServerError) - return - } - // store user in context for request - r = r.WithContext(user.ContextSetUser(r.Context(), u)) - } - - next.ServeHTTP(w, r) - }) - } -} diff --git a/ocs/pkg/middleware/options.go b/ocs/pkg/middleware/options.go index ccc003ad5..36853d84f 100644 --- a/ocs/pkg/middleware/options.go +++ b/ocs/pkg/middleware/options.go @@ -1,8 +1,8 @@ package middleware import ( - "github.com/owncloud/ocis/ocs/pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/roles" ) // Option defines a single option function. @@ -12,8 +12,8 @@ type Option func(o *Options) type Options struct { // Logger to use for logging, must be set Logger log.Logger - // TokenManagerConfig for communicating with the reva token manager - TokenManagerConfig config.TokenManager + // RoleManager for looking up permissions + RoleManager *roles.Manager } // newOptions initializes the available default options. @@ -34,9 +34,9 @@ func Logger(l log.Logger) Option { } } -// TokenManagerConfig provides a function to set the token manger config option. -func TokenManagerConfig(cfg config.TokenManager) Option { +// RoleManager provides a function to set the RoleManager option. +func RoleManager(val *roles.Manager) Option { return func(o *Options) { - o.TokenManagerConfig = cfg + o.RoleManager = val } } diff --git a/ocs/pkg/middleware/requireadmin.go b/ocs/pkg/middleware/requireadmin.go new file mode 100644 index 000000000..625ae721f --- /dev/null +++ b/ocs/pkg/middleware/requireadmin.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/render" + accounts "github.com/owncloud/ocis/accounts/pkg/service/v0" + "github.com/owncloud/ocis/ocis-pkg/roles" + "github.com/owncloud/ocis/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/ocs/pkg/service/v0/response" +) + +// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions +func RequireAdmin(opts ...Option) func(next http.Handler) http.Handler { + opt := newOptions(opts...) + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) + if !ok { + render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + return + } + + // check if permission is present in roles of the authenticated account + if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.AccountManagementPermissionID) != nil { + next.ServeHTTP(w, r) + return + } + + render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + + }) + } +} diff --git a/ocs/pkg/middleware/requireselforadmin.go b/ocs/pkg/middleware/requireselforadmin.go new file mode 100644 index 000000000..972dfbaa7 --- /dev/null +++ b/ocs/pkg/middleware/requireselforadmin.go @@ -0,0 +1,57 @@ +package middleware + +import ( + "net/http" + + "github.com/cs3org/reva/pkg/user" + "github.com/go-chi/chi" + "github.com/go-chi/render" + accounts "github.com/owncloud/ocis/accounts/pkg/service/v0" + "github.com/owncloud/ocis/ocis-pkg/roles" + "github.com/owncloud/ocis/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/ocs/pkg/service/v0/response" +) + +// RequireSelfOrAdmin middleware is used to require the requesting user to be an admin or the requested user himself +func RequireSelfOrAdmin(opts ...Option) func(next http.Handler) http.Handler { + opt := newOptions(opts...) + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + u, ok := user.ContextGetUser(r.Context()) + if !ok { + render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + return + } + if u.Id == nil || u.Id.OpaqueId == "" { + render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id")) + return + } + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) + if !ok { + render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + return + } + + // check if account management permission is present in roles of the authenticated account + if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.AccountManagementPermissionID) != nil { + next.ServeHTTP(w, r) + return + } + + // check if self management permission is present in roles of the authenticated account + if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.SelfManagementPermissionID) != nil { + userid := chi.URLParam(r, "userid") + if userid == "" || userid == u.Id.OpaqueId || userid == u.Username { + next.ServeHTTP(w, r) + return + } + } + + render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + + }) + } +} diff --git a/ocs/pkg/service/v0/option.go b/ocs/pkg/service/v0/option.go index de898ebd2..4de1623f2 100644 --- a/ocs/pkg/service/v0/option.go +++ b/ocs/pkg/service/v0/option.go @@ -3,8 +3,10 @@ package svc import ( "net/http" - "github.com/owncloud/ocis/ocs/pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/roles" + "github.com/owncloud/ocis/ocs/pkg/config" + settings "github.com/owncloud/ocis/settings/pkg/proto/v0" ) // Option defines a single option function. @@ -12,9 +14,11 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + RoleService settings.RoleService + RoleManager *roles.Manager } // newOptions initializes the available default options. @@ -48,3 +52,17 @@ func Middleware(val ...func(http.Handler) http.Handler) Option { o.Middleware = val } } + +// RoleService provides a function to set the RoleService option. +func RoleService(val settings.RoleService) Option { + return func(o *Options) { + o.RoleService = val + } +} + +// RoleManager provides a function to set the RoleManager option. +func RoleManager(val *roles.Manager) Option { + return func(o *Options) { + o.RoleManager = val + } +} diff --git a/ocs/pkg/service/v0/service.go b/ocs/pkg/service/v0/service.go index c1beb845c..9880be20a 100644 --- a/ocs/pkg/service/v0/service.go +++ b/ocs/pkg/service/v0/service.go @@ -2,18 +2,24 @@ package svc import ( "net/http" + "time" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/render" "github.com/micro/go-micro/v2/client/grpc" + mclient "github.com/micro/go-micro/v2/client" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" + "github.com/owncloud/ocis/ocis-pkg/account" + "github.com/owncloud/ocis/ocis-pkg/log" + opkgm "github.com/owncloud/ocis/ocis-pkg/middleware" + "github.com/owncloud/ocis/ocis-pkg/roles" "github.com/owncloud/ocis/ocs/pkg/config" ocsm "github.com/owncloud/ocis/ocs/pkg/middleware" "github.com/owncloud/ocis/ocs/pkg/service/v0/data" "github.com/owncloud/ocis/ocs/pkg/service/v0/response" - "github.com/owncloud/ocis/ocis-pkg/log" + settings "github.com/owncloud/ocis/settings/pkg/proto/v0" ) var defaultClient = grpc.NewClient() @@ -31,19 +37,45 @@ func NewService(opts ...Option) Service { m := chi.NewMux() m.Use(options.Middleware...) - svc := Ocs{ - config: options.Config, - mux: m, - logger: options.Logger, + roleService := options.RoleService + if roleService == nil { + // https://github.com/owncloud/ocis-proxy/issues/38 + // TODO this won't work with a registry other than mdns. Look into Micro's client initialization. + roleService = settings.NewRoleService("com.owncloud.api.settings", mclient.DefaultClient) + } + roleManager := options.RoleManager + if roleManager == nil { + m := roles.NewManager( + roles.CacheSize(1024), + roles.CacheTTL(time.Hour*24*7), + roles.Logger(options.Logger), + roles.RoleService(roleService), + ) + roleManager = &m } + svc := Ocs{ + config: options.Config, + mux: m, + RoleManager: roleManager, + logger: options.Logger, + } + + requireAdmin := ocsm.RequireAdmin( + ocsm.RoleManager(roleManager), + ) + + requireSelfOrAdmin := ocsm.RequireSelfOrAdmin( + ocsm.RoleManager(roleManager), + ocsm.Logger(options.Logger), + ) m.Route(options.Config.HTTP.Root, func(r chi.Router) { r.NotFound(svc.NotFound) r.Use(middleware.StripSlashes) - r.Use(ocsm.AccessToken( - ocsm.Logger(options.Logger), - ocsm.TokenManagerConfig(options.Config.TokenManager), - )) + r.Use(opkgm.ExtractAccountUUID( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret)), + ) r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter r.Route("/v{version:(1|2)}.php", func(r chi.Router) { r.Use(response.VersionCtx) // stores version in context @@ -51,28 +83,31 @@ func NewService(opts ...Option) Service { r.Route("/apps/notifications/api/v1", func(r chi.Router) {}) r.Route("/cloud", func(r chi.Router) { r.Route("/capabilities", func(r chi.Router) {}) + // TODO /apps r.Route("/user", func(r chi.Router) { - r.Get("/", svc.GetUser) + r.With(requireSelfOrAdmin).Get("/", svc.GetSelf) r.Get("/signing-key", svc.GetSigningKey) }) r.Route("/users", func(r chi.Router) { - r.Get("/", svc.ListUsers) - r.Post("/", svc.AddUser) - r.Get("/{userid}", svc.GetUser) - r.Put("/{userid}", svc.EditUser) - r.Delete("/{userid}", svc.DeleteUser) + r.With(requireAdmin).Get("/", svc.ListUsers) + r.With(requireAdmin).Post("/", svc.AddUser) + r.Route("/{userid}", func(r chi.Router) { + r.With(requireSelfOrAdmin).Get("/", svc.GetUser) + r.With(requireSelfOrAdmin).Put("/", svc.EditUser) + r.With(requireAdmin).Delete("/", svc.DeleteUser) + }) r.Route("/{userid}/groups", func(r chi.Router) { - r.Get("/", svc.ListUserGroups) - r.Post("/", svc.AddToGroup) - r.Delete("/", svc.RemoveFromGroup) + r.With(requireSelfOrAdmin).Get("/", svc.ListUserGroups) + r.With(requireAdmin).Post("/", svc.AddToGroup) + r.With(requireAdmin).Delete("/", svc.RemoveFromGroup) }) }) r.Route("/groups", func(r chi.Router) { - r.Get("/", svc.ListGroups) - r.Post("/", svc.AddGroup) - r.Delete("/{groupid}", svc.DeleteGroup) - r.Get("/{groupid}", svc.GetGroupMembers) + r.With(requireAdmin).Get("/", svc.ListGroups) + r.With(requireAdmin).Post("/", svc.AddGroup) + r.With(requireAdmin).Delete("/{groupid}", svc.DeleteGroup) + r.With(requireSelfOrAdmin).Get("/{groupid}", svc.GetGroupMembers) }) }) r.Route("/config", func(r chi.Router) { @@ -86,9 +121,11 @@ func NewService(opts ...Option) Service { // Ocs defines implements the business logic for Service. type Ocs struct { - config *config.Config - logger log.Logger - mux *chi.Mux + config *config.Config + logger log.Logger + RoleService settings.RoleService + RoleManager *roles.Manager + mux *chi.Mux } // ServeHTTP implements the Service interface. diff --git a/ocs/pkg/service/v0/users.go b/ocs/pkg/service/v0/users.go index 4e257c81b..3b3b29127 100644 --- a/ocs/pkg/service/v0/users.go +++ b/ocs/pkg/service/v0/users.go @@ -22,22 +22,58 @@ import ( storepb "github.com/owncloud/ocis/store/pkg/proto/v0" ) -// GetUser returns the currently logged in user +// GetSelf returns the currently logged in user +func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) { + var account *accounts.Account + var err error + u, ok := user.ContextGetUser(r.Context()) + if !ok || u.Id == nil || u.Id.OpaqueId == "" { + render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id")) + return + } + + account, err = o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{ + Id: u.Id.OpaqueId, + }) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + // if the user was authenticated why wes he not found?!? log error? + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found")) + } else { + render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(merr).Interface("user", u).Msg("could not get account for user") + return + } + + // remove password from log if it is set + if account.PasswordProfile != nil { + account.PasswordProfile.Password = "" + } + o.logger.Debug().Interface("account", account).Msg("got user") + + d := &data.User{ + UserID: account.PreferredName, + DisplayName: account.DisplayName, + LegacyDisplayName: account.DisplayName, + Email: account.Mail, + UIDNumber: account.UidNumber, + GIDNumber: account.GidNumber, + // TODO hide enabled flag or it might get rendered as false + } + render.Render(w, r, response.DataRender(d)) +} + +// GetUser returns the user with the given userid func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { - // TODO this endpoint needs authentication using the roles and permissions userid := chi.URLParam(r, "userid") var account *accounts.Account var err error if userid == "" { - u, ok := user.ContextGetUser(r.Context()) - if !ok || u.Id == nil || u.Id.OpaqueId == "" { - render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) - return - } - account, err = o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{ - Id: u.Id.OpaqueId, - }) + render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) } else { account, err = o.fetchAccountByUsername(r.Context(), userid) } @@ -48,7 +84,7 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { } else { render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) } - o.logger.Error().Err(err).Str("userid", userid).Msg("could not get user") + o.logger.Error().Err(merr).Str("userid", userid).Msg("could not get account for user") return } @@ -73,8 +109,7 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { Email: account.Mail, UIDNumber: account.UidNumber, GIDNumber: account.GidNumber, - Enabled: enabled, - // FIXME onlyfor users/{userid} endpoint (not /user) + Enabled: enabled, // TODO include in response only when admin? // TODO query storage registry for free space? of home storage, maybe... Quota: &data.Quota{ Free: 2840756224000, @@ -89,7 +124,6 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { // AddUser creates a new user account func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { - // TODO this endpoint needs authentication using the roles and permissions userid := r.PostFormValue("userid") password := r.PostFormValue("password") displayname := r.PostFormValue("displayname") @@ -186,7 +220,6 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { // EditUser creates a new user account func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { - // TODO this endpoint needs authentication userid := chi.URLParam(r, "userid") account, err := o.fetchAccountByUsername(r.Context(), userid) if err != nil {