mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 05:19:05 +01:00
Rework initial commit
- moved frontend configuration api to new subrouter for compatibility
This commit is contained in:
parent
552da005dc
commit
61eebc9fbd
@ -371,6 +371,8 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
secured := r.PathPrefix("/").Subrouter()
|
secured := r.PathPrefix("/").Subrouter()
|
||||||
|
securedapi := r.PathPrefix("/api").Subrouter()
|
||||||
|
userapi := r.PathPrefix("/userapi").Subrouter()
|
||||||
|
|
||||||
if !config.Keys.DisableAuthentication {
|
if !config.Keys.DisableAuthentication {
|
||||||
r.Handle("/login", authentication.Login(
|
r.Handle("/login", authentication.Login(
|
||||||
@ -437,6 +439,42 @@ func main() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
securedapi.Use(func(next http.Handler) http.Handler {
|
||||||
|
return authentication.AuthApi(
|
||||||
|
// On success;
|
||||||
|
next,
|
||||||
|
|
||||||
|
// On failure:
|
||||||
|
func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
|
web.RenderTemplate(rw, "login.tmpl", &web.Page{
|
||||||
|
Title: "Authentication failed - ClusterCockpit",
|
||||||
|
MsgType: "alert-danger",
|
||||||
|
Message: err.Error(),
|
||||||
|
Build: buildInfo,
|
||||||
|
Infos: info,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
userapi.Use(func(next http.Handler) http.Handler {
|
||||||
|
return authentication.AuthUserApi(
|
||||||
|
// On success;
|
||||||
|
next,
|
||||||
|
|
||||||
|
// On failure:
|
||||||
|
func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
|
web.RenderTemplate(rw, "login.tmpl", &web.Page{
|
||||||
|
Title: "Authentication failed - ClusterCockpit",
|
||||||
|
MsgType: "alert-danger",
|
||||||
|
Message: err.Error(),
|
||||||
|
Build: buildInfo,
|
||||||
|
Infos: info,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagDev {
|
if flagDev {
|
||||||
@ -453,7 +491,9 @@ func main() {
|
|||||||
|
|
||||||
// Mount all /monitoring/... and /api/... routes.
|
// Mount all /monitoring/... and /api/... routes.
|
||||||
routerConfig.SetupRoutes(secured, buildInfo)
|
routerConfig.SetupRoutes(secured, buildInfo)
|
||||||
api.MountRoutes(secured)
|
api.MountConfigApiRoutes(secured)
|
||||||
|
api.MountApiRoutes(securedapi)
|
||||||
|
api.MountUserApiRoutes(userapi)
|
||||||
|
|
||||||
if config.Keys.EmbedStaticFiles {
|
if config.Keys.EmbedStaticFiles {
|
||||||
if i, err := os.Stat("./var/img"); err == nil {
|
if i, err := os.Stat("./var/img"); err == nil {
|
||||||
|
@ -197,7 +197,7 @@ func TestRestApi(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
restapi.MountRoutes(r)
|
restapi.MountApiRoutes(r)
|
||||||
|
|
||||||
const startJobBody string = `{
|
const startJobBody string = `{
|
||||||
"jobId": 123,
|
"jobId": 123,
|
||||||
|
@ -59,8 +59,7 @@ type RestApi struct {
|
|||||||
RepositoryMutex sync.Mutex
|
RepositoryMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *RestApi) MountRoutes(r *mux.Router) {
|
func (api *RestApi) MountApiRoutes(r *mux.Router) {
|
||||||
r = r.PathPrefix("/api").Subrouter()
|
|
||||||
r.StrictSlash(true)
|
r.StrictSlash(true)
|
||||||
|
|
||||||
r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
|
r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
|
||||||
@ -84,6 +83,24 @@ func (api *RestApi) MountRoutes(r *mux.Router) {
|
|||||||
r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet)
|
r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost)
|
r.HandleFunc("/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *RestApi) MountUserApiRoutes(r *mux.Router) {
|
||||||
|
r.StrictSlash(true)
|
||||||
|
|
||||||
|
r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost)
|
||||||
|
r.HandleFunc("/jobs/{id}", api.getCompleteJobById).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
if api.Authentication != nil {
|
||||||
|
r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *RestApi) MountConfigApiRoutes(r *mux.Router) {
|
||||||
|
r = r.PathPrefix("/config").Subrouter()
|
||||||
|
r.StrictSlash(true)
|
||||||
|
|
||||||
if api.Authentication != nil {
|
if api.Authentication != nil {
|
||||||
r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet)
|
r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet)
|
||||||
@ -311,13 +328,6 @@ func (api *RestApi) getClusters(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/ [get]
|
// @router /jobs/ [get]
|
||||||
func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
withMetadata := false
|
withMetadata := false
|
||||||
filter := &model.JobFilter{}
|
filter := &model.JobFilter{}
|
||||||
page := &model.PageRequest{ItemsPerPage: 25, Page: 1}
|
page := &model.PageRequest{ItemsPerPage: 25, Page: 1}
|
||||||
@ -434,7 +444,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getJobById godoc
|
// getCompleteJobById godoc
|
||||||
// @summary Get job meta and optional all metric data
|
// @summary Get job meta and optional all metric data
|
||||||
// @tags Job query
|
// @tags Job query
|
||||||
// @description Job to get is specified by database ID
|
// @description Job to get is specified by database ID
|
||||||
@ -452,14 +462,6 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/{id} [get]
|
// @router /jobs/{id} [get]
|
||||||
func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v",
|
|
||||||
schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch job from db
|
// Fetch job from db
|
||||||
id, ok := mux.Vars(r)["id"]
|
id, ok := mux.Vars(r)["id"]
|
||||||
var job *schema.Job
|
var job *schema.Job
|
||||||
@ -471,7 +473,7 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err = api.JobRepository.FindById(id)
|
job, err = api.JobRepository.FindById(r.Context(), id) // Get Job from Repo by ID
|
||||||
} else {
|
} else {
|
||||||
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
@ -546,14 +548,6 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/{id} [post]
|
// @router /jobs/{id} [post]
|
||||||
func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v",
|
|
||||||
schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch job from db
|
// Fetch job from db
|
||||||
id, ok := mux.Vars(r)["id"]
|
id, ok := mux.Vars(r)["id"]
|
||||||
var job *schema.Job
|
var job *schema.Job
|
||||||
@ -565,7 +559,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err = api.JobRepository.FindById(id)
|
job, err = api.JobRepository.FindById(r.Context(), id)
|
||||||
} else {
|
} else {
|
||||||
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
@ -651,19 +645,13 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/edit_meta/{id} [post]
|
// @router /jobs/edit_meta/{id} [post]
|
||||||
func (api *RestApi) editMeta(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) editMeta(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
iid, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err := api.JobRepository.FindById(iid)
|
job, err := api.JobRepository.FindById(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusNotFound)
|
http.Error(rw, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@ -702,20 +690,13 @@ func (api *RestApi) editMeta(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/tag_job/{id} [post]
|
// @router /jobs/tag_job/{id} [post]
|
||||||
func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
iid, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err := api.JobRepository.FindById(iid)
|
job, err := api.JobRepository.FindById(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusNotFound)
|
http.Error(rw, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@ -769,13 +750,6 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/start_job/ [post]
|
// @router /jobs/start_job/ [post]
|
||||||
func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := schema.JobMeta{BaseJob: schema.JobDefaults}
|
req := schema.JobMeta{BaseJob: schema.JobDefaults}
|
||||||
if err := decode(r.Body, &req); err != nil {
|
if err := decode(r.Body, &req); err != nil {
|
||||||
handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw)
|
handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw)
|
||||||
@ -852,13 +826,6 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/stop_job/{id} [post]
|
// @router /jobs/stop_job/{id} [post]
|
||||||
func (api *RestApi) stopJobById(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) stopJobById(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse request body: Only StopTime and State
|
// Parse request body: Only StopTime and State
|
||||||
req := StopJobApiRequest{}
|
req := StopJobApiRequest{}
|
||||||
if err := decode(r.Body, &req); err != nil {
|
if err := decode(r.Body, &req); err != nil {
|
||||||
@ -877,7 +844,7 @@ func (api *RestApi) stopJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err = api.JobRepository.FindById(id)
|
job, err = api.JobRepository.FindById(r.Context(), id)
|
||||||
} else {
|
} else {
|
||||||
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
handleError(errors.New("the parameter 'id' is required"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
@ -907,13 +874,6 @@ func (api *RestApi) stopJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/stop_job/ [post]
|
// @router /jobs/stop_job/ [post]
|
||||||
func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse request body
|
// Parse request body
|
||||||
req := StopJobApiRequest{}
|
req := StopJobApiRequest{}
|
||||||
if err := decode(r.Body, &req); err != nil {
|
if err := decode(r.Body, &req); err != nil {
|
||||||
@ -955,11 +915,6 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/delete_job/{id} [delete]
|
// @router /jobs/delete_job/{id} [delete]
|
||||||
func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil && !user.HasRole(schema.RoleApi) {
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch job (that will be stopped) from db
|
// Fetch job (that will be stopped) from db
|
||||||
id, ok := mux.Vars(r)["id"]
|
id, ok := mux.Vars(r)["id"]
|
||||||
var err error
|
var err error
|
||||||
@ -1003,12 +958,6 @@ func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/delete_job/ [delete]
|
// @router /jobs/delete_job/ [delete]
|
||||||
func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil &&
|
|
||||||
!user.HasRole(schema.RoleApi) {
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse request body
|
// Parse request body
|
||||||
req := DeleteJobApiRequest{}
|
req := DeleteJobApiRequest{}
|
||||||
if err := decode(r.Body, &req); err != nil {
|
if err := decode(r.Body, &req); err != nil {
|
||||||
@ -1060,11 +1009,6 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request)
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/delete_job_before/{ts} [delete]
|
// @router /jobs/delete_job_before/{ts} [delete]
|
||||||
func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
|
||||||
if user := repository.GetUserFromContext(r.Context()); user != nil && !user.HasRole(schema.RoleApi) {
|
|
||||||
handleError(fmt.Errorf("missing role: %v", schema.GetRoleString(schema.RoleApi)), http.StatusForbidden, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var cnt int
|
var cnt int
|
||||||
// Fetch job (that will be stopped) from db
|
// Fetch job (that will be stopped) from db
|
||||||
id, ok := mux.Vars(r)["ts"]
|
id, ok := mux.Vars(r)["ts"]
|
||||||
|
@ -244,6 +244,76 @@ func (auth *Authentication) Auth(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (auth *Authentication) AuthApi(
|
||||||
|
onsuccess http.Handler,
|
||||||
|
onfailure func(rw http.ResponseWriter, r *http.Request, authErr error),
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
user, err := auth.JwtAuth.AuthViaJWT(rw, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("authentication failed: %s", err.Error())
|
||||||
|
http.Error(rw, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
switch {
|
||||||
|
case len(user.Roles) == 1:
|
||||||
|
if user.HasRole(schema.RoleApi) {
|
||||||
|
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
|
||||||
|
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case len(user.Roles) >= 2:
|
||||||
|
if user.HasAllRoles([]schema.Role{schema.RoleAdmin, schema.RoleApi}) {
|
||||||
|
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
|
||||||
|
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Debug("authentication failed")
|
||||||
|
onfailure(rw, r, errors.New("unauthorized (missing role)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug("authentication failed")
|
||||||
|
onfailure(rw, r, errors.New("unauthorized (no auth)"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Authentication) AuthUserApi(
|
||||||
|
onsuccess http.Handler,
|
||||||
|
onfailure func(rw http.ResponseWriter, r *http.Request, authErr error),
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
user, err := auth.JwtAuth.AuthViaJWT(rw, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("authentication failed: %s", err.Error())
|
||||||
|
http.Error(rw, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
switch {
|
||||||
|
case len(user.Roles) == 1:
|
||||||
|
if user.HasRole(schema.RoleApi) {
|
||||||
|
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
|
||||||
|
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case len(user.Roles) >= 2:
|
||||||
|
if user.HasRole(schema.RoleApi) && user.HasAnyRole([]schema.Role{schema.RoleUser, schema.RoleManager, schema.RoleAdmin}) {
|
||||||
|
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
|
||||||
|
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Debug("authentication failed")
|
||||||
|
onfailure(rw, r, errors.New("unauthorized (missing role)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug("authentication failed")
|
||||||
|
onfailure(rw, r, errors.New("unauthorized (no auth)"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (auth *Authentication) Logout(onsuccess http.Handler) http.Handler {
|
func (auth *Authentication) Logout(onsuccess http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
session, err := auth.sessionStore.Get(r, "session")
|
session, err := auth.sessionStore.Get(r, "session")
|
||||||
|
@ -172,7 +172,7 @@ func (r *queryResolver) Job(ctx context.Context, id string) (*schema.Job, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err := r.Repo.FindById(numericId)
|
job, err := r.Repo.FindById(ctx, numericId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while finding job by id")
|
log.Warn("Error while finding job by id")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -283,12 +283,43 @@ func (r *JobRepository) FindAll(
|
|||||||
// The job is queried using the database id.
|
// The job is queried using the database id.
|
||||||
// It returns a pointer to a schema.Job data structure and an error variable.
|
// It returns a pointer to a schema.Job data structure and an error variable.
|
||||||
// To check if no job was found test err == sql.ErrNoRows
|
// To check if no job was found test err == sql.ErrNoRows
|
||||||
func (r *JobRepository) FindById(jobId int64) (*schema.Job, error) {
|
func (r *JobRepository) FindById(ctx context.Context, jobId int64) (*schema.Job, error) {
|
||||||
|
q := sq.Select(jobColumns...).
|
||||||
|
From("job").Where("job.id = ?", jobId)
|
||||||
|
|
||||||
|
q, qerr := SecurityCheck(ctx, q)
|
||||||
|
if qerr != nil {
|
||||||
|
return nil, qerr
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanJob(q.RunWith(r.stmtCache).QueryRow())
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByIdDirect executes a SQL query to find a specific batch job.
|
||||||
|
// The job is queried using the database id.
|
||||||
|
// It returns a pointer to a schema.Job data structure and an error variable.
|
||||||
|
// To check if no job was found test err == sql.ErrNoRows
|
||||||
|
func (r *JobRepository) FindByIdDirect(jobId int64) (*schema.Job, error) {
|
||||||
q := sq.Select(jobColumns...).
|
q := sq.Select(jobColumns...).
|
||||||
From("job").Where("job.id = ?", jobId)
|
From("job").Where("job.id = ?", jobId)
|
||||||
return scanJob(q.RunWith(r.stmtCache).QueryRow())
|
return scanJob(q.RunWith(r.stmtCache).QueryRow())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsJobOwner executes a SQL query to find a specific batch job.
|
||||||
|
// The job is queried using the slurm id,a username and the cluster.
|
||||||
|
// It returns a bool.
|
||||||
|
// If job was found, user is owner: test err != sql.ErrNoRows
|
||||||
|
func (r *JobRepository) IsJobOwner(jobId int64, user string, cluster string) bool {
|
||||||
|
q := sq.Select("id").
|
||||||
|
From("job").
|
||||||
|
Where("job.job_id = ?", jobId).
|
||||||
|
Where("job.user = ?", user).
|
||||||
|
Where("job.cluster = ?", cluster)
|
||||||
|
|
||||||
|
_, err := scanJob(q.RunWith(r.stmtCache).QueryRow())
|
||||||
|
return err != sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
func (r *JobRepository) FindConcurrentJobs(
|
func (r *JobRepository) FindConcurrentJobs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
job *schema.Job,
|
job *schema.Job,
|
||||||
|
@ -30,7 +30,7 @@ func TestFind(t *testing.T) {
|
|||||||
func TestFindById(t *testing.T) {
|
func TestFindById(t *testing.T) {
|
||||||
r := setup(t)
|
r := setup(t)
|
||||||
|
|
||||||
job, err := r.FindById(5)
|
job, err := r.FindById(getContext(t), 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -97,23 +97,25 @@ func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilde
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
var qnil sq.SelectBuilder
|
var qnil sq.SelectBuilder
|
||||||
return qnil, fmt.Errorf("user context is nil")
|
return qnil, fmt.Errorf("user context is nil")
|
||||||
} else if user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport, schema.RoleApi}) { // Admin & Co. : All jobs
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(user.Roles) == 1 && user.HasRole(schema.RoleApi): // API-User : All jobs
|
||||||
return query, nil
|
return query, nil
|
||||||
} else if user.HasRole(schema.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs
|
case user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}): // Admin & Support : All jobs
|
||||||
|
return query, nil
|
||||||
|
case user.HasRole(schema.RoleManager): // Manager : Add filter for managed projects' jobs only + personal jobs
|
||||||
if len(user.Projects) != 0 {
|
if len(user.Projects) != 0 {
|
||||||
return query.Where(sq.Or{sq.Eq{"job.project": user.Projects}, sq.Eq{"job.user": user.Username}}), nil
|
return query.Where(sq.Or{sq.Eq{"job.project": user.Projects}, sq.Eq{"job.user": user.Username}}), nil
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Manager-User '%s' has no defined projects to lookup! Query only personal jobs ...", user.Username)
|
log.Debugf("Manager-User '%s' has no defined projects to lookup! Query only personal jobs ...", user.Username)
|
||||||
return query.Where("job.user = ?", user.Username), nil
|
return query.Where("job.user = ?", user.Username), nil
|
||||||
}
|
}
|
||||||
} else if user.HasRole(schema.RoleUser) { // User : Only personal jobs
|
case user.HasRole(schema.RoleUser): // User : Only personal jobs
|
||||||
return query.Where("job.user = ?", user.Username), nil
|
return query.Where("job.user = ?", user.Username), nil
|
||||||
} else {
|
default: // No known Role, return error
|
||||||
// Shortterm compatibility: Return User-Query if no roles:
|
var qnil sq.SelectBuilder
|
||||||
return query.Where("job.user = ?", user.Username), nil
|
return qnil, fmt.Errorf("user has no or unknown roles")
|
||||||
// // On the longterm: Return Error instead of fallback:
|
|
||||||
// var qnil sq.SelectBuilder
|
|
||||||
// return qnil, fmt.Errorf("user '%s' with unknown roles [%#v]", user.Username, user.Roles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func BenchmarkDB_FindJobById(b *testing.B) {
|
|||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
_, err := db.FindById(jobId)
|
_, err := db.FindById(getContext(b), jobId)
|
||||||
noErr(b, err)
|
noErr(b, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ func (r *JobRepository) AddTag(job int64, tag int64) ([]*schema.Tag, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := r.FindById(job)
|
j, err := r.FindByIdDirect(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while finding job by id")
|
log.Warn("Error while finding job by id")
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -48,7 +48,7 @@ func (r *JobRepository) RemoveTag(job, tag int64) ([]*schema.Tag, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := r.FindById(job)
|
j, err := r.FindByIdDirect(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while finding job by id")
|
log.Warn("Error while finding job by id")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
let roles = [];
|
let roles = [];
|
||||||
|
|
||||||
function getUserList() {
|
function getUserList() {
|
||||||
fetch("/api/users/?via-ldap=false¬-just-user=true")
|
fetch("/config/users/?via-ldap=false¬-just-user=true")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((usersRaw) => {
|
.then((usersRaw) => {
|
||||||
users = usersRaw;
|
users = usersRaw;
|
||||||
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getValidRoles() {
|
function getValidRoles() {
|
||||||
fetch("/api/roles/")
|
fetch("/config/roles/")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((rolesRaw) => {
|
.then((rolesRaw) => {
|
||||||
roles = rolesRaw;
|
roles = rolesRaw;
|
||||||
|
@ -283,7 +283,7 @@
|
|||||||
<form
|
<form
|
||||||
id="line-width-form"
|
id="line-width-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/configuration/"
|
action="/config/configuration/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
on:submit|preventDefault={() =>
|
on:submit|preventDefault={() =>
|
||||||
handleSettingSubmit("#line-width-form", "lw")}
|
handleSettingSubmit("#line-width-form", "lw")}
|
||||||
@ -329,7 +329,7 @@
|
|||||||
<form
|
<form
|
||||||
id="plots-per-row-form"
|
id="plots-per-row-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/configuration/"
|
action="/config/configuration/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
on:submit|preventDefault={() =>
|
on:submit|preventDefault={() =>
|
||||||
handleSettingSubmit("#plots-per-row-form", "ppr")}
|
handleSettingSubmit("#plots-per-row-form", "ppr")}
|
||||||
@ -375,7 +375,7 @@
|
|||||||
<form
|
<form
|
||||||
id="backgrounds-form"
|
id="backgrounds-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/configuration/"
|
action="/config/configuration/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
on:submit|preventDefault={() =>
|
on:submit|preventDefault={() =>
|
||||||
handleSettingSubmit("#backgrounds-form", "bg")}
|
handleSettingSubmit("#backgrounds-form", "bg")}
|
||||||
@ -429,7 +429,7 @@
|
|||||||
<form
|
<form
|
||||||
id="paging-form"
|
id="paging-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/configuration/"
|
action="/config/configuration/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
on:submit|preventDefault={() =>
|
on:submit|preventDefault={() =>
|
||||||
handleSettingSubmit("#paging-form", "pag")}
|
handleSettingSubmit("#paging-form", "pag")}
|
||||||
@ -485,7 +485,7 @@
|
|||||||
<form
|
<form
|
||||||
id="colorscheme-form"
|
id="colorscheme-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/configuration/"
|
action="/config/configuration/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
>
|
>
|
||||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<form
|
<form
|
||||||
id="create-user-form"
|
id="create-user-form"
|
||||||
method="post"
|
method="post"
|
||||||
action="/api/users/"
|
action="/config/users/"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
on:submit|preventDefault={handleUserSubmit}
|
on:submit|preventDefault={handleUserSubmit}
|
||||||
>
|
>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
formData.append("add-project", project);
|
formData.append("add-project", project);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/user/${username}`, {
|
const res = await fetch(`/config/user/${username}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@ -54,7 +54,7 @@
|
|||||||
formData.append("remove-project", project);
|
formData.append("remove-project", project);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/user/${username}`, {
|
const res = await fetch(`/config/user/${username}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
formData.append("add-role", role);
|
formData.append("add-role", role);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/user/${username}`, {
|
const res = await fetch(`/config/user/${username}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@
|
|||||||
formData.append("remove-role", role);
|
formData.append("remove-role", role);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/user/${username}`, {
|
const res = await fetch(`/config/user/${username}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
if (confirm("Are you sure?")) {
|
if (confirm("Are you sure?")) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("username", username);
|
formData.append("username", username);
|
||||||
fetch("/api/users/", { method: "DELETE", body: formData }).then((res) => {
|
fetch("/config/users/", { method: "DELETE", body: formData }).then((res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
reloadUserList();
|
reloadUserList();
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
let jwt = "";
|
let jwt = "";
|
||||||
|
|
||||||
function getUserJwt(username) {
|
function getUserJwt(username) {
|
||||||
fetch(`/api/jwt/?username=${username}`)
|
fetch(`/config/jwt/?username=${username}`)
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
jwt = text;
|
jwt = text;
|
||||||
|
Loading…
Reference in New Issue
Block a user