Some new REST endpoints; cleanup

This commit is contained in:
Lou Knauer 2022-03-03 14:54:37 +01:00
parent 5f12b49145
commit 046d4d5187
5 changed files with 122 additions and 31 deletions

View File

@ -51,8 +51,10 @@ func (api *RestApi) MountRoutes(r *mux.Router) {
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
if api.Authentication != nil { if api.Authentication != nil {
r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodPost) r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet)
r.HandleFunc("/users/", api.createUser).Methods(http.MethodPost, http.MethodPut) 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("/configuration/", api.updateConfiguration).Methods(http.MethodPost) r.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost)
} }
@ -474,6 +476,7 @@ func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) {
} }
func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain")
username := r.FormValue("username") username := r.FormValue("username")
me := auth.GetUser(r.Context()) me := auth.GetUser(r.Context())
if !me.HasRole(auth.RoleAdmin) { if !me.HasRole(auth.RoleAdmin) {
@ -500,19 +503,35 @@ func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) {
} }
func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain")
me := auth.GetUser(r.Context()) me := auth.GetUser(r.Context())
if !me.HasRole(auth.RoleAdmin) { if !me.HasRole(auth.RoleAdmin) {
http.Error(rw, "only admins are allowed to create new users", http.StatusForbidden) http.Error(rw, "only admins are allowed to create new users", http.StatusForbidden)
return return
} }
username, password, role := r.FormValue("username"), r.FormValue("password"), r.FormValue("role") username, password, role, name, email := r.FormValue("username"), r.FormValue("password"), r.FormValue("role"), r.FormValue("name"), r.FormValue("email")
if len(password) == 0 && role != auth.RoleApi { 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) http.Error(rw, "only API users are allowed to have a blank password (login will be impossible)", http.StatusBadRequest)
return return
} }
if err := api.Authentication.AddUser(username + ":" + role + ":" + password); err != nil { if err := api.Authentication.CreateUser(username, name, password, email, []string{role}); err != nil {
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
return
}
rw.Write([]byte(fmt.Sprintf("User %#v successfully created!\n", username)))
}
func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) {
if user := auth.GetUser(r.Context()); !user.HasRole(auth.RoleAdmin) {
http.Error(rw, "only admins are allowed to delete a user", http.StatusForbidden)
return
}
username := r.FormValue("username")
if err := api.Authentication.DelUser(username); err != nil {
http.Error(rw, err.Error(), http.StatusUnprocessableEntity) http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
return return
} }
@ -520,12 +539,33 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
} }
func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
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
}
users, err := api.Authentication.FetchUsers(r.URL.Query().Get("via-ldap") == "true")
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(rw).Encode(users)
}
func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { 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") key, value := r.FormValue("key"), r.FormValue("value")
fmt.Printf("KEY: %#v\nVALUE: %#v\n", key, value)
if err := config.UpdateConfig(key, value, r.Context()); err != nil { if err := config.UpdateConfig(key, value, r.Context()); err != nil {
http.Error(rw, err.Error(), http.StatusUnprocessableEntity) http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
return return
} }
rw.Write([]byte("success"))
} }
func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) {

View File

@ -26,12 +26,12 @@ import (
// If Name and Email is needed as well, use auth.FetchUser(), which does a database // If Name and Email is needed as well, use auth.FetchUser(), which does a database
// query for all fields. // query for all fields.
type User struct { type User struct {
Username string Username string `json:"username"`
Password string Password string `json:"-"`
Name string Name string `json:"name"`
Roles []string Roles []string `json:"roles"`
ViaLdap bool ViaLdap bool `json:"via-ldap"`
Email string Email string `json:"email"`
} }
const ( const (
@ -130,32 +130,46 @@ func (auth *Authentication) AddUser(arg string) error {
return errors.New("invalid argument format") return errors.New("invalid argument format")
} }
password := "" roles := strings.Split(parts[1], ",")
if len(parts[2]) > 0 { return auth.CreateUser(parts[0], "", parts[2], "", roles)
bytes, err := bcrypt.GenerateFromPassword([]byte(parts[2]), bcrypt.DefaultCost) }
func (auth *Authentication) CreateUser(username, name, password, email string, roles []string) error {
for _, role := range roles {
if role != RoleAdmin && role != RoleApi && role != RoleUser {
return fmt.Errorf("invalid user role: %#v", role)
}
}
if username == "" {
return errors.New("username should not be empty")
}
if password != "" {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return err return err
} }
password = string(bytes) password = string(bytes)
} }
roles := []string{} rolesJson, _ := json.Marshal(roles)
for _, role := range strings.Split(parts[1], ",") { cols := []string{"username", "password", "roles"}
if len(role) == 0 { vals := []interface{}{username, password, string(rolesJson)}
continue if name != "" {
} else if role == RoleAdmin || role == RoleApi || role == RoleUser { cols = append(cols, "name")
roles = append(roles, role) vals = append(vals, name)
} else {
return fmt.Errorf("invalid user role: %#v", role)
} }
if email != "" {
cols = append(cols, "email")
vals = append(vals, email)
} }
rolesJson, _ := json.Marshal(roles) if _, err := sq.Insert("user").Columns(cols...).Values(vals...).RunWith(auth.db).Exec(); err != nil {
_, err := sq.Insert("user").Columns("username", "password", "roles").Values(parts[0], password, string(rolesJson)).RunWith(auth.db).Exec()
if err != nil {
return err return err
} }
log.Infof("new user %#v added (roles: %s)", parts[0], roles)
log.Infof("new user %#v created (roles: %s)", username, roles)
return nil return nil
} }
@ -164,6 +178,40 @@ func (auth *Authentication) DelUser(username string) error {
return err return err
} }
func (auth *Authentication) FetchUsers(viaLdap bool) ([]*User, error) {
q := sq.Select("username", "name", "email", "roles").From("user")
if !viaLdap {
q = q.Where("ldap = 0")
} else {
q = q.Where("ldap = 1")
}
rows, err := q.RunWith(auth.db).Query()
if err != nil {
return nil, err
}
users := make([]*User, 0)
defer rows.Close()
for rows.Next() {
rawroles := ""
user := &User{}
var name, email sql.NullString
if err := rows.Scan(&user.Username, &name, &email, &rawroles); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(rawroles), &user.Roles); err != nil {
return nil, err
}
user.Name = name.String
user.Email = email.String
users = append(users, user)
}
return users, nil
}
func (auth *Authentication) FetchUser(username string) (*User, error) { func (auth *Authentication) FetchUser(username string) (*User, error) {
user := &User{Username: username} user := &User{Username: username}
var hashedPassword, name, rawRoles, email sql.NullString var hashedPassword, name, rawRoles, email sql.NullString

View File

@ -132,6 +132,7 @@ func GetUIConfig(r *http.Request) (map[string]interface{}, error) {
} }
size := 0 size := 0
defer rows.Close()
for rows.Next() { for rows.Next() {
var key, rawval string var key, rawval string
if err := rows.Scan(&key, &rawval); err != nil { if err := rows.Scan(&key, &rawval); err != nil {
@ -173,12 +174,16 @@ func UpdateConfig(key, value string, ctx context.Context) error {
return nil return nil
} }
cache.Del(user.Username) if _, ok := uiDefaults[key]; !ok {
return errors.New("this configuration key does not exist")
}
if _, err := db.Exec(`REPLACE INTO configuration (username, confkey, value) VALUES (?, ?, ?)`, if _, err := db.Exec(`REPLACE INTO configuration (username, confkey, value) VALUES (?, ?, ?)`,
user.Username, key, value); err != nil { user.Username, key, value); err != nil {
return err return err
} }
cache.Del(user.Username)
return nil return nil
} }

View File

@ -60,6 +60,7 @@ func (r *JobRepository) QueryJobs(
for rows.Next() { for rows.Next() {
job, err := scanJob(rows) job, err := scanJob(rows)
if err != nil { if err != nil {
rows.Close()
return nil, err return nil, err
} }
jobs = append(jobs, job) jobs = append(jobs, job)

View File

@ -1,8 +1,6 @@
package repository package repository
import ( import (
"fmt"
"github.com/ClusterCockpit/cc-backend/metricdata" "github.com/ClusterCockpit/cc-backend/metricdata"
"github.com/ClusterCockpit/cc-backend/schema" "github.com/ClusterCockpit/cc-backend/schema"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
@ -88,9 +86,8 @@ func (r *JobRepository) CountTags(user *string) (tags []schema.Tag, counts map[s
for rows.Next() { for rows.Next() {
var tagName string var tagName string
var count int var count int
err = rows.Scan(&tagName, &count) if err := rows.Scan(&tagName, &count); err != nil {
if err != nil { return nil, nil, err
fmt.Println(err)
} }
counts[tagName] = count counts[tagName] = count
} }