diff --git a/api/rest.go b/api/rest.go index 1667caf..4a37901 100644 --- a/api/rest.go +++ b/api/rest.go @@ -17,6 +17,7 @@ import ( "time" "github.com/ClusterCockpit/cc-backend/auth" + "github.com/ClusterCockpit/cc-backend/config" "github.com/ClusterCockpit/cc-backend/graph" "github.com/ClusterCockpit/cc-backend/graph/model" "github.com/ClusterCockpit/cc-backend/log" @@ -29,6 +30,7 @@ import ( type RestApi struct { JobRepository *repository.JobRepository Resolver *graph.Resolver + Authentication *auth.Authentication MachineStateDir string OngoingArchivings sync.WaitGroup } @@ -48,6 +50,12 @@ func (api *RestApi) MountRoutes(r *mux.Router) { r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) + if api.Authentication != nil { + r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodPost) + r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) + r.HandleFunc("/configuration/", api.updateConfiguration).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) @@ -465,6 +473,61 @@ func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) { }) } +func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { + username := r.FormValue("username") + me := auth.GetUser(r.Context()) + if !me.HasRole(auth.RoleAdmin) { + if username != me.Username { + http.Error(rw, "only admins are allowed to sign JWTs not for themselves", http.StatusForbidden) + return + } + } + + user, err := api.Authentication.FetchUser(username) + if err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + + jwt, err := api.Authentication.ProvideJWT(user) + if err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(jwt)) +} + +func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { + me := auth.GetUser(r.Context()) + if !me.HasRole(auth.RoleAdmin) { + http.Error(rw, "only admins are allowed to create new users", http.StatusForbidden) + return + } + + username, password, role := r.FormValue("username"), r.FormValue("password"), r.FormValue("role") + if len(password) == 0 && role != auth.RoleApi { + http.Error(rw, "only API users are allowed to have a blank password (login will be impossible)", http.StatusBadRequest) + return + } + + if err := api.Authentication.AddUser(username + ":" + role + ":" + password); err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + + rw.WriteHeader(http.StatusOK) +} + +func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { + key, value := r.FormValue("key"), r.FormValue("value") + if err := config.UpdateConfig(key, value, r.Context()); err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } +} + func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) { if api.MachineStateDir == "" { http.Error(rw, "not enabled", http.StatusNotFound) diff --git a/server.go b/server.go index 0ccbea5..2077b57 100644 --- a/server.go +++ b/server.go @@ -296,8 +296,9 @@ func main() { // Initialize sub-modules... - authentication := &auth.Authentication{} + var authentication *auth.Authentication if !programConfig.DisableAuthentication { + authentication = &auth.Authentication{} if d, err := time.ParseDuration(programConfig.SessionMaxAge); err != nil { authentication.SessionMaxAge = d } @@ -398,6 +399,7 @@ func main() { JobRepository: jobRepo, Resolver: resolver, MachineStateDir: programConfig.MachineStateDir, + Authentication: authentication, } handleGetLogin := func(rw http.ResponseWriter, r *http.Request) {