From 6393035e5554d95f388104f33f2e1fb8f6d1fdb9 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Thu, 3 Aug 2023 17:47:09 +0200 Subject: [PATCH 1/4] first iteraton of implementing ip-secured enpoint --- internal/api/rest.go | 79 ++++++++++++++++++++++++++++++++++++++++++++ pkg/schema/config.go | 3 ++ 2 files changed, 82 insertions(+) diff --git a/internal/api/rest.go b/internal/api/rest.go index c199bc2..c1f6fd1 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -20,6 +20,7 @@ import ( "time" "github.com/ClusterCockpit/cc-backend/internal/auth" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/graph" "github.com/ClusterCockpit/cc-backend/internal/graph/model" "github.com/ClusterCockpit/cc-backend/internal/importer" @@ -75,6 +76,8 @@ func (api *RestApi) MountRoutes(r *mux.Router) { r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete) + r.HandleFunc("/secured/addProject/{id}/{project}", api.secureUpdateUser).Methods(http.MethodPost) + r.HandleFunc("/secured/addRole/{id}/{role}", api.secureUpdateUser).Methods(http.MethodPost) if api.Authentication != nil { r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) @@ -103,6 +106,11 @@ type DeleteJobApiResponse struct { Message string `json:"msg"` } +// UpdateUserApiResponse model +type UpdateUserApiResponse struct { + Message string `json:"msg"` +} + // StopJobApiRequest model type StopJobApiRequest struct { // Stop Time of job as epoch @@ -1043,6 +1051,77 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { } } +func (api *RestApi) secureUpdateUser(rw http.ResponseWriter, r *http.Request) { + if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { + handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw) + return + } + + // If nothing declared in config: deny all request to this endpint + if config.Keys.ApiAllowedAddrs == nil || len(config.Keys.ApiAllowedAddrs) == 0 { + handleError(fmt.Errorf("denied by default policy!"), http.StatusForbidden, rw) + return + } + + // IP CHECK HERE (WIP) + // Probably better as private routine + IPAddress := r.Header.Get("X-Real-Ip") + if IPAddress == "" { + IPAddress = r.Header.Get("X-Forwarded-For") + } + if IPAddress == "" { + IPAddress = r.RemoteAddr + } + + // Also This + ipOk := false + for _, a := range config.Keys.ApiAllowedAddrs { + if a == IPAddress { + ipOk = true + } + } + + if IPAddress == "" || ipOk == false { + handleError(fmt.Errorf("unknown ip: %v", IPAddress), http.StatusForbidden, rw) + return + } + // IP CHECK END + + // Get Values + id := mux.Vars(r)["id"] + newproj := mux.Vars(r)["project"] + newrole := mux.Vars(r)["role"] + + // TODO: Handle anything but roles... + if newrole != "" { + if err := api.Authentication.AddRole(r.Context(), id, newrole); err != nil { + handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + json.NewEncoder(rw).Encode(UpdateUserApiResponse{ + Message: fmt.Sprintf("Successfully added role %s to %s", newrole, id), + }) + + } else if newproj != "" { + if err := api.Authentication.AddProject(r.Context(), id, newproj); err != nil { + handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + json.NewEncoder(rw).Encode(UpdateUserApiResponse{ + Message: fmt.Sprintf("Successfully added project %s to %s", newproj, id), + }) + + } else { + handleError(errors.New("Not Add [role|project]?"), http.StatusBadRequest, rw) + } +} + func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "text/plain") key, value := r.FormValue("key"), r.FormValue("value") diff --git a/pkg/schema/config.go b/pkg/schema/config.go index 9a88ea2..30515dd 100644 --- a/pkg/schema/config.go +++ b/pkg/schema/config.go @@ -69,6 +69,9 @@ type ProgramConfig struct { // Address where the http (or https) server will listen on (for example: 'localhost:80'). Addr string `json:"addr"` + // Addresses from which the /secured/* API endpoints can be reached + ApiAllowedAddrs []string `json:"apiAllowedAddrs"` + // Drop root permissions once .env was read and the port was taken. User string `json:"user"` Group string `json:"group"` From 90bdfcfbb62d88dd5eba5ef06fa70e1a8c38ecf2 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Mon, 14 Aug 2023 14:33:05 +0200 Subject: [PATCH 2/4] Add secured subrouter for REST API Rename IP filter option Add array helper in util --- internal/api/rest.go | 201 ++++++++++++++++++++++++----------------- internal/util/array.go | 14 +++ pkg/schema/config.go | 4 +- 3 files changed, 135 insertions(+), 84 deletions(-) create mode 100644 internal/util/array.go diff --git a/internal/api/rest.go b/internal/api/rest.go index c1f6fd1..1e758a2 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -26,6 +26,7 @@ import ( "github.com/ClusterCockpit/cc-backend/internal/importer" "github.com/ClusterCockpit/cc-backend/internal/metricdata" "github.com/ClusterCockpit/cc-backend/internal/repository" + "github.com/ClusterCockpit/cc-backend/internal/util" "github.com/ClusterCockpit/cc-backend/pkg/archive" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -76,23 +77,65 @@ func (api *RestApi) MountRoutes(r *mux.Router) { r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete) - r.HandleFunc("/secured/addProject/{id}/{project}", api.secureUpdateUser).Methods(http.MethodPost) - r.HandleFunc("/secured/addRole/{id}/{role}", api.secureUpdateUser).Methods(http.MethodPost) - - if api.Authentication != nil { - r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) - r.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) - r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) - r.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) - r.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) - r.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) - r.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) - } + // r.HandleFunc("/secured/addProject/{id}/{project}", api.secureUpdateUser).Methods(http.MethodPost) + // r.HandleFunc("/secured/addRole/{id}/{role}", api.secureUpdateUser).Methods(http.MethodPost) if api.MachineStateDir != "" { r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet) r.HandleFunc("/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost) } + + if api.Authentication != nil { + rw := r.MatcherFunc( + func(rq *http.Request, rm *mux.RouteMatch) bool { + user := auth.GetUser(rq.Context()) + return user.AuthType == auth.AuthSession + }).Subrouter() + rw.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) + rw.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) + rw.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) + rw.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) + rw.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) + rw.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) + rw.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) + + rs := r.PathPrefix("/secured").MatcherFunc( + func(rq *http.Request, rm *mux.RouteMatch) bool { + user := auth.GetUser(rq.Context()) + // this only applies for token based authorization + if user.AuthType != auth.AuthToken { + return false + } + + // If nothing declared in config: deny all request to this endpoint + if config.Keys.ApiAllowedIPs == nil || len(config.Keys.ApiAllowedIPs) == 0 { + return false + } + + // extract IP address + IPAddress := rq.Header.Get("X-Real-Ip") + if IPAddress == "" { + IPAddress = rq.Header.Get("X-Forwarded-For") + } + if IPAddress == "" { + IPAddress = rq.RemoteAddr + } + + // check if IP is allowed + if !util.Contains(config.Keys.ApiAllowedIPs, IPAddress) { + return false + } + + return true + }).Subrouter() + rs.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) + rs.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) + rs.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) + rs.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) + rs.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) + rs.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) + rs.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) + } } // StartJobApiResponse model @@ -1051,76 +1094,70 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { } } -func (api *RestApi) secureUpdateUser(rw http.ResponseWriter, r *http.Request) { - if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { - handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw) - return - } - - // If nothing declared in config: deny all request to this endpint - if config.Keys.ApiAllowedAddrs == nil || len(config.Keys.ApiAllowedAddrs) == 0 { - handleError(fmt.Errorf("denied by default policy!"), http.StatusForbidden, rw) - return - } - - // IP CHECK HERE (WIP) - // Probably better as private routine - IPAddress := r.Header.Get("X-Real-Ip") - if IPAddress == "" { - IPAddress = r.Header.Get("X-Forwarded-For") - } - if IPAddress == "" { - IPAddress = r.RemoteAddr - } - - // Also This - ipOk := false - for _, a := range config.Keys.ApiAllowedAddrs { - if a == IPAddress { - ipOk = true - } - } - - if IPAddress == "" || ipOk == false { - handleError(fmt.Errorf("unknown ip: %v", IPAddress), http.StatusForbidden, rw) - return - } - // IP CHECK END - - // Get Values - id := mux.Vars(r)["id"] - newproj := mux.Vars(r)["project"] - newrole := mux.Vars(r)["role"] - - // TODO: Handle anything but roles... - if newrole != "" { - if err := api.Authentication.AddRole(r.Context(), id, newrole); err != nil { - handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.WriteHeader(http.StatusOK) - json.NewEncoder(rw).Encode(UpdateUserApiResponse{ - Message: fmt.Sprintf("Successfully added role %s to %s", newrole, id), - }) - - } else if newproj != "" { - if err := api.Authentication.AddProject(r.Context(), id, newproj); err != nil { - handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.WriteHeader(http.StatusOK) - json.NewEncoder(rw).Encode(UpdateUserApiResponse{ - Message: fmt.Sprintf("Successfully added project %s to %s", newproj, id), - }) - - } else { - handleError(errors.New("Not Add [role|project]?"), http.StatusBadRequest, rw) - } -} +// func (api *RestApi) secureUpdateUser(rw http.ResponseWriter, r *http.Request) { +// if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { +// handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw) +// return +// } +// +// // IP CHECK HERE (WIP) +// // Probably better as private routine +// IPAddress := r.Header.Get("X-Real-Ip") +// if IPAddress == "" { +// IPAddress = r.Header.Get("X-Forwarded-For") +// } +// if IPAddress == "" { +// IPAddress = r.RemoteAddr +// } +// +// // Also This +// ipOk := false +// for _, a := range config.Keys.ApiAllowedAddrs { +// if a == IPAddress { +// ipOk = true +// } +// } +// +// if IPAddress == "" || ipOk == false { +// handleError(fmt.Errorf("unknown ip: %v", IPAddress), http.StatusForbidden, rw) +// return +// } +// // IP CHECK END +// +// // Get Values +// id := mux.Vars(r)["id"] +// newproj := mux.Vars(r)["project"] +// newrole := mux.Vars(r)["role"] +// +// // TODO: Handle anything but roles... +// if newrole != "" { +// if err := api.Authentication.AddRole(r.Context(), id, newrole); err != nil { +// handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) +// return +// } +// +// rw.Header().Add("Content-Type", "application/json") +// rw.WriteHeader(http.StatusOK) +// json.NewEncoder(rw).Encode(UpdateUserApiResponse{ +// Message: fmt.Sprintf("Successfully added role %s to %s", newrole, id), +// }) +// +// } else if newproj != "" { +// if err := api.Authentication.AddProject(r.Context(), id, newproj); err != nil { +// handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) +// return +// } +// +// rw.Header().Add("Content-Type", "application/json") +// rw.WriteHeader(http.StatusOK) +// json.NewEncoder(rw).Encode(UpdateUserApiResponse{ +// Message: fmt.Sprintf("Successfully added project %s to %s", newproj, id), +// }) +// +// } else { +// handleError(errors.New("Not Add [role|project]?"), http.StatusBadRequest, rw) +// } +// } func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "text/plain") diff --git a/internal/util/array.go b/internal/util/array.go new file mode 100644 index 0000000..bc7ed04 --- /dev/null +++ b/internal/util/array.go @@ -0,0 +1,14 @@ +// Copyright (C) 2023 NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +package util + +func Contains[T comparable](items []T, item T) bool { + for _, v := range items { + if v == item { + return true + } + } + return false +} diff --git a/pkg/schema/config.go b/pkg/schema/config.go index b59feb3..95cc641 100644 --- a/pkg/schema/config.go +++ b/pkg/schema/config.go @@ -70,8 +70,8 @@ type ProgramConfig struct { // Address where the http (or https) server will listen on (for example: 'localhost:80'). Addr string `json:"addr"` - // Addresses from which the /secured/* API endpoints can be reached - ApiAllowedAddrs []string `json:"apiAllowedAddrs"` + // Addresses from which the /api/secured/* API endpoints can be reached + ApiAllowedIPs []string `json:"apiAllowedIPs"` // Drop root permissions once .env was read and the port was taken. User string `json:"user"` From 202521cbfd3d13e7935e52e04c6482bd5b5f7c3c Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Mon, 14 Aug 2023 18:38:30 +0200 Subject: [PATCH 3/4] Restructure routing and security check --- internal/api/rest.go | 129 +++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 53 deletions(-) diff --git a/internal/api/rest.go b/internal/api/rest.go index 1e758a2..501cf3b 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg. +// Copyright (C) 2023 NHR@FAU, University Erlangen-Nuremberg. // All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -86,55 +86,13 @@ func (api *RestApi) MountRoutes(r *mux.Router) { } if api.Authentication != nil { - rw := r.MatcherFunc( - func(rq *http.Request, rm *mux.RouteMatch) bool { - user := auth.GetUser(rq.Context()) - return user.AuthType == auth.AuthSession - }).Subrouter() - rw.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) - rw.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) - rw.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) - rw.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) - rw.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) - rw.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) - rw.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) - - rs := r.PathPrefix("/secured").MatcherFunc( - func(rq *http.Request, rm *mux.RouteMatch) bool { - user := auth.GetUser(rq.Context()) - // this only applies for token based authorization - if user.AuthType != auth.AuthToken { - return false - } - - // If nothing declared in config: deny all request to this endpoint - if config.Keys.ApiAllowedIPs == nil || len(config.Keys.ApiAllowedIPs) == 0 { - return false - } - - // extract IP address - IPAddress := rq.Header.Get("X-Real-Ip") - if IPAddress == "" { - IPAddress = rq.Header.Get("X-Forwarded-For") - } - if IPAddress == "" { - IPAddress = rq.RemoteAddr - } - - // check if IP is allowed - if !util.Contains(config.Keys.ApiAllowedIPs, IPAddress) { - return false - } - - return true - }).Subrouter() - rs.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) - rs.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) - rs.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) - rs.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) - rs.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) - rs.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) - rs.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) + r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) + r.HandleFunc("/roles/", api.getRoles).Methods(http.MethodGet) + r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) + r.HandleFunc("/users/", api.getUsers).Methods(http.MethodGet) + r.HandleFunc("/users/", api.deleteUser).Methods(http.MethodDelete) + r.HandleFunc("/user/{id}", api.updateUser).Methods(http.MethodPost) + r.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) } } @@ -223,6 +181,36 @@ func decode(r io.Reader, val interface{}) error { return dec.Decode(val) } +func securedCheck(r *http.Request) error { + user := auth.GetUser(r.Context()) + if user == nil { + return fmt.Errorf("no user in context") + } + + if user.AuthType == auth.AuthToken { + // If nothing declared in config: deny all request to this endpoint + if config.Keys.ApiAllowedIPs == nil || len(config.Keys.ApiAllowedIPs) == 0 { + return fmt.Errorf("missing configuration key ApiAllowedIPs") + } + + // extract IP address + IPAddress := r.Header.Get("X-Real-Ip") + if IPAddress == "" { + IPAddress = r.Header.Get("X-Forwarded-For") + } + if IPAddress == "" { + IPAddress = r.RemoteAddr + } + + // check if IP is allowed + if !util.Contains(config.Keys.ApiAllowedIPs, IPAddress) { + return fmt.Errorf("unknown ip: %v", IPAddress) + } + } + + return nil +} + // getJobs godoc // @summary Lists all jobs // @tags query @@ -943,6 +931,11 @@ func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + rw.Header().Set("Content-Type", "text/plain") username := r.FormValue("username") me := auth.GetUser(r.Context()) @@ -971,6 +964,11 @@ func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + rw.Header().Set("Content-Type", "text/plain") me := auth.GetUser(r.Context()) if !me.HasRole(auth.RoleAdmin) { @@ -978,17 +976,22 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { return } - username, password, role, name, email, project := r.FormValue("username"), r.FormValue("password"), r.FormValue("role"), r.FormValue("name"), r.FormValue("email"), r.FormValue("project") + username, password, role, name, email, project := r.FormValue("username"), + r.FormValue("password"), r.FormValue("role"), r.FormValue("name"), + r.FormValue("email"), r.FormValue("project") + if len(password) == 0 && role != auth.GetRoleString(auth.RoleApi) { http.Error(rw, "Only API users are allowed to have a blank password (login will be impossible)", http.StatusBadRequest) return } if len(project) != 0 && role != auth.GetRoleString(auth.RoleManager) { - http.Error(rw, "only managers require a project (can be changed later)", http.StatusBadRequest) + http.Error(rw, "only managers require a project (can be changed later)", + http.StatusBadRequest) return } else if len(project) == 0 && role == auth.GetRoleString(auth.RoleManager) { - http.Error(rw, "managers require a project to manage (can be changed later)", http.StatusBadRequest) + http.Error(rw, "managers require a project to manage (can be changed later)", + http.StatusBadRequest) return } @@ -1007,6 +1010,11 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) { http.Error(rw, "Only admins are allowed to delete a user", http.StatusForbidden) return @@ -1022,6 +1030,11 @@ func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) { http.Error(rw, "Only admins are allowed to fetch a list of users", http.StatusForbidden) return @@ -1037,6 +1050,11 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + user := auth.GetUser(r.Context()) if !user.HasRole(auth.RoleAdmin) { http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden) @@ -1053,6 +1071,11 @@ func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { } func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + } + if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) { http.Error(rw, "Only admins are allowed to update a user", http.StatusForbidden) return From 4f6d1fec68f53a9dee3f1727f2abfb1b95dfd738 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Wed, 16 Aug 2023 09:19:41 +0200 Subject: [PATCH 4/4] Fix errors in ldap auth --- internal/auth/auth.go | 13 +++++++------ internal/auth/ldap.go | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 8149bc1..500ef1a 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -158,12 +158,13 @@ func Init(db *sqlx.DB, } if config, ok := configs["ldap"]; ok { - auth.LdapAuth = &LdapAuthenticator{} - if err := auth.LdapAuth.Init(auth, config); err != nil { - log.Error("Error while initializing authentication -> ldapAuth init failed") - return nil, err + ldapAuth := &LdapAuthenticator{} + if err := ldapAuth.Init(auth, config); err != nil { + log.Warn("Error while initializing authentication -> ldapAuth init failed") + } else { + auth.LdapAuth = ldapAuth + auth.authenticators = append(auth.authenticators, auth.LdapAuth) } - auth.authenticators = append(auth.authenticators, auth.LdapAuth) } jwtSessionAuth := &JWTSessionAuthenticator{} @@ -174,7 +175,7 @@ func Init(db *sqlx.DB, } jwtCookieSessionAuth := &JWTCookieSessionAuthenticator{} - if err := jwtSessionAuth.Init(auth, configs["jwt"]); err != nil { + if err := jwtCookieSessionAuth.Init(auth, configs["jwt"]); err != nil { log.Warn("Error while initializing authentication -> jwtCookieSessionAuth init failed") } else { auth.authenticators = append(auth.authenticators, jwtCookieSessionAuth) diff --git a/internal/auth/ldap.go b/internal/auth/ldap.go index 17b5c0c..9feebc1 100644 --- a/internal/auth/ldap.go +++ b/internal/auth/ldap.go @@ -59,6 +59,8 @@ func (la *LdapAuthenticator) Init( log.Print("sync done") } }() + } else { + return fmt.Errorf("missing LDAP configuration") } return nil @@ -73,7 +75,7 @@ func (la *LdapAuthenticator) CanLogin( if user != nil && user.AuthSource == AuthViaLDAP { return true } else { - if la.config.SyncUserOnLogin { + if la.config != nil && la.config.SyncUserOnLogin { l, err := la.getLdapConnection(true) if err != nil { log.Error("LDAP connection error")