mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 05:19:05 +01:00
Some new REST endpoints; cleanup
This commit is contained in:
parent
5f12b49145
commit
046d4d5187
46
api/rest.go
46
api/rest.go
@ -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) {
|
||||||
|
92
auth/auth.go
92
auth/auth.go
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user