mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-11-10 08:57:25 +01:00
b2aed2f16b
- Addresses issues #40 #45 #82 - Reworked Navigation Header for all roles - 'Manager' role added, can be assigned a project-id in config by admins - BREAKING! -> Added 'project' column in SQLite3 table 'user' - Manager-Assigned project will be added to all graphql filters: Only show Jobs and Users of given project - 'My Jobs' Tab for all Roles - Switched from Bool "isAdmin" to integer authLevels - Removed critical data frontend logging - Reworked repo.query.SecurityCheck()
245 lines
6.3 KiB
Go
245 lines
6.3 KiB
Go
// Copyright (C) 2022 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 auth
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
|
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
|
sq "github.com/Masterminds/squirrel"
|
|
"github.com/jmoiron/sqlx"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func (auth *Authentication) GetUser(username string) (*User, error) {
|
|
|
|
user := &User{Username: username}
|
|
var hashedPassword, name, rawRoles, email, project sql.NullString
|
|
if err := sq.Select("password", "ldap", "name", "roles", "email", "project").From("user").
|
|
Where("user.username = ?", username).RunWith(auth.db).
|
|
QueryRow().Scan(&hashedPassword, &user.AuthSource, &name, &rawRoles, &email, &project); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user.Password = hashedPassword.String
|
|
user.Name = name.String
|
|
user.Email = email.String
|
|
user.Project = project.String
|
|
if rawRoles.Valid {
|
|
if err := json.Unmarshal([]byte(rawRoles.String), &user.Roles); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (auth *Authentication) AddUser(user *User) error {
|
|
|
|
rolesJson, _ := json.Marshal(user.Roles)
|
|
|
|
cols := []string{"username", "roles"}
|
|
vals := []interface{}{user.Username, string(rolesJson)}
|
|
if user.Name != "" {
|
|
cols = append(cols, "name")
|
|
vals = append(vals, user.Name)
|
|
}
|
|
if user.Email != "" {
|
|
cols = append(cols, "email")
|
|
vals = append(vals, user.Email)
|
|
}
|
|
if user.Project != "" {
|
|
cols = append(cols, "project")
|
|
vals = append(vals, user.Project)
|
|
}
|
|
if user.Password != "" {
|
|
password, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cols = append(cols, "password")
|
|
vals = append(vals, string(password))
|
|
}
|
|
|
|
if _, err := sq.Insert("user").Columns(cols...).Values(vals...).RunWith(auth.db).Exec(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("new user %#v created (roles: %s, auth-source: %d, project: %s)", user.Username, rolesJson, user.AuthSource, user.Project)
|
|
return nil
|
|
}
|
|
|
|
func (auth *Authentication) DelUser(username string) error {
|
|
|
|
_, err := auth.db.Exec(`DELETE FROM user WHERE user.username = ?`, username)
|
|
return err
|
|
}
|
|
|
|
func (auth *Authentication) ListUsers(specialsOnly bool) ([]*User, error) {
|
|
|
|
q := sq.Select("username", "name", "email", "roles", "project").From("user")
|
|
if specialsOnly {
|
|
q = q.Where("(roles != '[\"user\"]' AND roles != '[]')")
|
|
}
|
|
|
|
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, project sql.NullString
|
|
if err := rows.Scan(&user.Username, &name, &email, &rawroles, &project); 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
|
|
user.Project = project.String
|
|
users = append(users, user)
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func (auth *Authentication) AddRole(
|
|
ctx context.Context,
|
|
username string,
|
|
role string) error {
|
|
|
|
user, err := auth.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !IsValidRole(role) {
|
|
return fmt.Errorf("invalid user role: %#v", role)
|
|
}
|
|
|
|
if user.HasRole(role) {
|
|
return fmt.Errorf("user %#v already has role %#v", username, role)
|
|
}
|
|
|
|
roles, _ := json.Marshal(append(user.Roles, role))
|
|
if _, err := sq.Update("user").Set("roles", roles).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (auth *Authentication) RemoveRole(ctx context.Context, username string, role string) error {
|
|
user, err := auth.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !IsValidRole(role) {
|
|
return fmt.Errorf("invalid user role: %#v", role)
|
|
}
|
|
|
|
if (role == RoleManager && len(user.Project) != 0) {
|
|
return fmt.Errorf("Cannot remove role 'manager' while user %#v still has an assigned project!", username)
|
|
}
|
|
|
|
var exists bool
|
|
var newroles []string
|
|
for _, r := range user.Roles {
|
|
if r != role {
|
|
newroles = append(newroles, r) // Append all roles not matching requested delete role
|
|
} else {
|
|
exists = true
|
|
}
|
|
}
|
|
|
|
if (exists == true) {
|
|
var mroles, _ = json.Marshal(newroles)
|
|
if _, err := sq.Update("user").Set("roles", mroles).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
} else {
|
|
return fmt.Errorf("user %#v already does not have role %#v", username, role)
|
|
}
|
|
}
|
|
|
|
func (auth *Authentication) AddProject(
|
|
ctx context.Context,
|
|
username string,
|
|
project string) error {
|
|
|
|
user, err := auth.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !user.HasRole(RoleManager) {
|
|
return fmt.Errorf("user '%#v' is not a manager!", username)
|
|
}
|
|
|
|
if user.HasProject(project) {
|
|
return fmt.Errorf("user '%#v' already manages project '%#v'", username, project)
|
|
}
|
|
|
|
if _, err := sq.Update("user").Set("project", project).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (auth *Authentication) RemoveProject(ctx context.Context, username string, project string) error {
|
|
user, err := auth.GetUser(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !user.HasRole(RoleManager) {
|
|
return fmt.Errorf("user '%#v' is not a manager!", username)
|
|
}
|
|
|
|
if !user.HasProject(project) {
|
|
return fmt.Errorf("user '%#v': Cannot remove project '%#v' - Does not match!", username, project)
|
|
}
|
|
|
|
if _, err := sq.Update("user").Set("project", "").Where("user.username = ?", username).Where("user.project = ?", project).RunWith(auth.db).Exec(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func FetchUser(ctx context.Context, db *sqlx.DB, username string) (*model.User, error) {
|
|
me := GetUser(ctx)
|
|
if me != nil && me.Username != username && me.HasNotRoles([]string{RoleAdmin, RoleSupport, RoleManager}) {
|
|
return nil, errors.New("forbidden")
|
|
}
|
|
|
|
user := &model.User{Username: username}
|
|
var name, email sql.NullString
|
|
if err := sq.Select("name", "email").From("user").Where("user.username = ?", username).
|
|
RunWith(db).QueryRow().Scan(&name, &email); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
user.Name = name.String
|
|
user.Email = email.String
|
|
// user.Project = project.String
|
|
return user, nil
|
|
}
|