mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-09-07 01:03:00 +02:00
Add support for multiple projects per manager
- Handled like roles in admin view - !! NEW COLUMN CHANGED TO "projects"
This commit is contained in:
@@ -50,11 +50,11 @@ import (
|
||||
// @name X-Auth-Token
|
||||
|
||||
type RestApi struct {
|
||||
JobRepository *repository.JobRepository
|
||||
Resolver *graph.Resolver
|
||||
Authentication *auth.Authentication
|
||||
MachineStateDir string
|
||||
RepositoryMutex sync.Mutex
|
||||
JobRepository *repository.JobRepository
|
||||
Resolver *graph.Resolver
|
||||
Authentication *auth.Authentication
|
||||
MachineStateDir string
|
||||
RepositoryMutex sync.Mutex
|
||||
}
|
||||
|
||||
func (api *RestApi) MountRoutes(r *mux.Router) {
|
||||
@@ -832,7 +832,7 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
|
||||
if len(project) != 0 && role != auth.RoleManager {
|
||||
http.Error(rw, "only managers require a project (can be changed later)", http.StatusBadRequest)
|
||||
return
|
||||
} else if (len(project) == 0 && role == auth.RoleManager) {
|
||||
} else if len(project) == 0 && role == auth.RoleManager {
|
||||
http.Error(rw, "managers require a project to manage (can be changed later)", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -842,7 +842,7 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
|
||||
Name: name,
|
||||
Password: password,
|
||||
Email: email,
|
||||
Project: project,
|
||||
Projects: []string{project},
|
||||
Roles: []string{role}}); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
@@ -883,7 +883,7 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
user := auth.GetUser(r.Context())
|
||||
if (!user.HasRole(auth.RoleAdmin)) {
|
||||
if !user.HasRole(auth.RoleAdmin) {
|
||||
http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -923,17 +923,17 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
rw.Write([]byte("Remove Role Success"))
|
||||
} else if newproj != "" {
|
||||
if err := api.Authentication.AddProject(r.Context(), mux.Vars(r)["id"], newproj); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
rw.Write([]byte("Set Project Success"))
|
||||
if err := api.Authentication.AddProject(r.Context(), mux.Vars(r)["id"], newproj); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
rw.Write([]byte("Add Project Success"))
|
||||
} else if delproj != "" {
|
||||
if err := api.Authentication.RemoveProject(r.Context(), mux.Vars(r)["id"], delproj); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
rw.Write([]byte("Reset Project Success"))
|
||||
if err := api.Authentication.RemoveProject(r.Context(), mux.Vars(r)["id"], delproj); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
rw.Write([]byte("Remove Project Success"))
|
||||
} else {
|
||||
http.Error(rw, "Not Add or Del [role|project]?", http.StatusInternalServerError)
|
||||
}
|
||||
|
@@ -9,10 +9,10 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||
"github.com/gorilla/sessions"
|
||||
@@ -42,7 +42,7 @@ type User struct {
|
||||
Roles []string `json:"roles"`
|
||||
AuthSource int8 `json:"via"`
|
||||
Email string `json:"email"`
|
||||
Project string `json:"project"`
|
||||
Projects []string `json:"projects"`
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (u *User) HasAnyRole(queryroles []string) bool {
|
||||
|
||||
// Role-Arrays are short: performance not impacted by nested loop
|
||||
func (u *User) HasAllRoles(queryroles []string) bool {
|
||||
target := len(queryroles)
|
||||
target := len(queryroles)
|
||||
matches := 0
|
||||
for _, ur := range u.Roles {
|
||||
for _, qr := range queryroles {
|
||||
@@ -81,7 +81,7 @@ func (u *User) HasAllRoles(queryroles []string) bool {
|
||||
}
|
||||
|
||||
if matches == target {
|
||||
return true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func (u *User) HasNotRoles(queryroles []string) bool {
|
||||
}
|
||||
|
||||
if matches == 0 {
|
||||
return true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -108,15 +108,15 @@ func (u *User) HasNotRoles(queryroles []string) bool {
|
||||
|
||||
// Find highest role, returns integer
|
||||
func (u *User) GetAuthLevel() int {
|
||||
if (u.HasRole(RoleAdmin)) {
|
||||
if u.HasRole(RoleAdmin) {
|
||||
return 5
|
||||
} else if (u.HasRole(RoleSupport)) {
|
||||
} else if u.HasRole(RoleSupport) {
|
||||
return 4
|
||||
} else if (u.HasRole(RoleManager)) {
|
||||
} else if u.HasRole(RoleManager) {
|
||||
return 3
|
||||
} else if (u.HasRole(RoleUser)) {
|
||||
} else if u.HasRole(RoleUser) {
|
||||
return 2
|
||||
} else if (u.HasRole(RoleApi)) {
|
||||
} else if u.HasRole(RoleApi) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
@@ -124,11 +124,12 @@ func (u *User) GetAuthLevel() int {
|
||||
}
|
||||
|
||||
func (u *User) HasProject(project string) bool {
|
||||
if (u.Project != "" && u.Project == project) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
for _, p := range u.Projects {
|
||||
if p == project {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidRole(role string) bool {
|
||||
@@ -142,7 +143,7 @@ func IsValidRole(role string) bool {
|
||||
|
||||
func GetValidRoles(user *User) ([5]string, error) {
|
||||
var vals [5]string
|
||||
if (!user.HasRole(RoleAdmin)) {
|
||||
if !user.HasRole(RoleAdmin) {
|
||||
return vals, fmt.Errorf("%s: only admins are allowed to fetch a list of roles", user.Username)
|
||||
} else {
|
||||
return validRoles, nil
|
||||
@@ -192,7 +193,7 @@ func Init(db *sqlx.DB,
|
||||
name varchar(255) DEFAULT NULL,
|
||||
roles varchar(255) NOT NULL DEFAULT "[]",
|
||||
email varchar(255) DEFAULT NULL,
|
||||
project varchar(255) DEFAULT NULL);`)
|
||||
projects varchar(255) NOT NULL DEFAULT "[]");`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -250,11 +251,11 @@ func (auth *Authentication) AuthViaSession(
|
||||
}
|
||||
|
||||
username, _ := session.Values["username"].(string)
|
||||
project, _ := session.Values["project"].(string)
|
||||
projects, _ := session.Values["projects"].([]string)
|
||||
roles, _ := session.Values["roles"].([]string)
|
||||
return &User{
|
||||
Username: username,
|
||||
Project: project,
|
||||
Projects: projects,
|
||||
Roles: roles,
|
||||
AuthSource: -1,
|
||||
}, nil
|
||||
@@ -299,7 +300,7 @@ func (auth *Authentication) Login(
|
||||
session.Options.MaxAge = int(auth.SessionMaxAge.Seconds())
|
||||
}
|
||||
session.Values["username"] = user.Username
|
||||
session.Values["project"] = user.Project
|
||||
session.Values["projects"] = user.Projects
|
||||
session.Values["roles"] = user.Roles
|
||||
if err := auth.sessionStore.Save(r, rw, session); err != nil {
|
||||
log.Errorf("session save failed: %s", err.Error())
|
||||
@@ -307,7 +308,7 @@ func (auth *Authentication) Login(
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("login successfull: user: %#v (roles: %v, project: %v)", user.Username, user.Roles, user.Project)
|
||||
log.Infof("login successfull: user: %#v (roles: %v, projects: %v)", user.Username, user.Roles, user.Projects)
|
||||
ctx := context.WithValue(r.Context(), ContextUserKey, user)
|
||||
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
|
||||
return
|
||||
|
@@ -21,22 +21,26 @@ import (
|
||||
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").
|
||||
var hashedPassword, name, rawRoles, email, rawProjects sql.NullString
|
||||
if err := sq.Select("password", "ldap", "name", "roles", "email", "projects").From("user").
|
||||
Where("user.username = ?", username).RunWith(auth.db).
|
||||
QueryRow().Scan(&hashedPassword, &user.AuthSource, &name, &rawRoles, &email, &project); err != nil {
|
||||
QueryRow().Scan(&hashedPassword, &user.AuthSource, &name, &rawRoles, &email, &rawProjects); 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
|
||||
}
|
||||
}
|
||||
if rawProjects.Valid {
|
||||
if err := json.Unmarshal([]byte(rawProjects.String), &user.Projects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -44,9 +48,11 @@ func (auth *Authentication) GetUser(username string) (*User, error) {
|
||||
func (auth *Authentication) AddUser(user *User) error {
|
||||
|
||||
rolesJson, _ := json.Marshal(user.Roles)
|
||||
projectsJson, _ := json.Marshal(user.Projects)
|
||||
|
||||
cols := []string{"username", "roles", "projects"}
|
||||
vals := []interface{}{user.Username, string(rolesJson), string(projectsJson)}
|
||||
|
||||
cols := []string{"username", "roles"}
|
||||
vals := []interface{}{user.Username, string(rolesJson)}
|
||||
if user.Name != "" {
|
||||
cols = append(cols, "name")
|
||||
vals = append(vals, user.Name)
|
||||
@@ -55,10 +61,6 @@ func (auth *Authentication) AddUser(user *User) error {
|
||||
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 {
|
||||
@@ -72,7 +74,7 @@ func (auth *Authentication) AddUser(user *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("new user %#v created (roles: %s, auth-source: %d, project: %s)", user.Username, rolesJson, user.AuthSource, user.Project)
|
||||
log.Infof("new user %#v created (roles: %s, auth-source: %d, projects: %s)", user.Username, rolesJson, user.AuthSource, projectsJson)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -84,7 +86,7 @@ func (auth *Authentication) DelUser(username string) error {
|
||||
|
||||
func (auth *Authentication) ListUsers(specialsOnly bool) ([]*User, error) {
|
||||
|
||||
q := sq.Select("username", "name", "email", "roles", "project").From("user")
|
||||
q := sq.Select("username", "name", "email", "roles", "projects").From("user")
|
||||
if specialsOnly {
|
||||
q = q.Where("(roles != '[\"user\"]' AND roles != '[]')")
|
||||
}
|
||||
@@ -98,9 +100,10 @@ func (auth *Authentication) ListUsers(specialsOnly bool) ([]*User, error) {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
rawroles := ""
|
||||
rawprojects := ""
|
||||
user := &User{}
|
||||
var name, email, project sql.NullString
|
||||
if err := rows.Scan(&user.Username, &name, &email, &rawroles, &project); err != nil {
|
||||
var name, email sql.NullString
|
||||
if err := rows.Scan(&user.Username, &name, &email, &rawroles, &rawprojects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -108,9 +111,12 @@ func (auth *Authentication) ListUsers(specialsOnly bool) ([]*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(rawprojects), &user.Projects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Name = name.String
|
||||
user.Email = email.String
|
||||
user.Project = project.String
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, nil
|
||||
@@ -151,21 +157,21 @@ func (auth *Authentication) RemoveRole(ctx context.Context, username string, rol
|
||||
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)
|
||||
if role == RoleManager && len(user.Projects) != 0 {
|
||||
return fmt.Errorf("Cannot remove role 'manager' while user %s still has an assigned project(s) : %v", username, user.Projects)
|
||||
}
|
||||
|
||||
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
|
||||
newroles = append(newroles, r) // Append all roles not matching requested to be deleted role
|
||||
} else {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
|
||||
if (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
|
||||
@@ -187,16 +193,18 @@ func (auth *Authentication) AddProject(
|
||||
}
|
||||
|
||||
if !user.HasRole(RoleManager) {
|
||||
return fmt.Errorf("user '%#v' is not a manager!", username)
|
||||
return fmt.Errorf("user '%s' is not a manager!", username)
|
||||
}
|
||||
|
||||
if user.HasProject(project) {
|
||||
return fmt.Errorf("user '%#v' already manages project '%#v'", username, project)
|
||||
return fmt.Errorf("user '%s' already manages project '%s'", username, project)
|
||||
}
|
||||
|
||||
if _, err := sq.Update("user").Set("project", project).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
||||
projects, _ := json.Marshal(append(user.Projects, project))
|
||||
if _, err := sq.Update("user").Set("projects", projects).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -214,10 +222,30 @@ func (auth *Authentication) RemoveProject(ctx context.Context, username string,
|
||||
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
|
||||
var exists bool
|
||||
var newprojects []string
|
||||
for _, p := range user.Projects {
|
||||
if p != project {
|
||||
newprojects = append(newprojects, p) // Append all projects not matching requested to be deleted project
|
||||
} else {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
|
||||
if exists == true {
|
||||
var result interface{}
|
||||
if len(newprojects) == 0 {
|
||||
result = "[]"
|
||||
} else {
|
||||
result, _ = json.Marshal(newprojects)
|
||||
}
|
||||
if _, err := sq.Update("user").Set("projects", result).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("user %s already does not manage project %s", username, project)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FetchUser(ctx context.Context, db *sqlx.DB, username string) (*model.User, error) {
|
||||
@@ -239,6 +267,5 @@ func FetchUser(ctx context.Context, db *sqlx.DB, username string) (*model.User,
|
||||
|
||||
user.Name = name.String
|
||||
user.Email = email.String
|
||||
// user.Project = project.String
|
||||
return user, nil
|
||||
}
|
||||
|
@@ -254,6 +254,7 @@ type ComplexityRoot struct {
|
||||
User struct {
|
||||
Email func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Project func(childComplexity int) int
|
||||
Username func(childComplexity int) int
|
||||
}
|
||||
}
|
||||
@@ -1289,6 +1290,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.User.Name(childComplexity), true
|
||||
|
||||
case "User.project":
|
||||
if e.complexity.User.Project == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.User.Project(childComplexity), true
|
||||
|
||||
case "User.username":
|
||||
if e.complexity.User.Username == nil {
|
||||
break
|
||||
@@ -1533,6 +1541,7 @@ type Count {
|
||||
type User {
|
||||
username: String!
|
||||
name: String!
|
||||
project: String
|
||||
email: String!
|
||||
}
|
||||
|
||||
@@ -1569,14 +1578,15 @@ type IntRangeOutput { from: Int!, to: Int! }
|
||||
type TimeRangeOutput { from: Time!, to: Time! }
|
||||
|
||||
input JobFilter {
|
||||
tags: [ID!]
|
||||
jobId: StringInput
|
||||
arrayJobId: Int
|
||||
user: StringInput
|
||||
project: StringInput
|
||||
cluster: StringInput
|
||||
partition: StringInput
|
||||
duration: IntRange
|
||||
tags: [ID!]
|
||||
jobId: StringInput
|
||||
arrayJobId: Int
|
||||
user: StringInput
|
||||
project: StringInput
|
||||
multiProject: [String]
|
||||
cluster: StringInput
|
||||
partition: StringInput
|
||||
duration: IntRange
|
||||
|
||||
minRunningFor: Int
|
||||
|
||||
@@ -3832,6 +3842,8 @@ func (ec *executionContext) fieldContext_Job_userData(ctx context.Context, field
|
||||
return ec.fieldContext_User_username(ctx, field)
|
||||
case "name":
|
||||
return ec.fieldContext_User_name(ctx, field)
|
||||
case "project":
|
||||
return ec.fieldContext_User_project(ctx, field)
|
||||
case "email":
|
||||
return ec.fieldContext_User_email(ctx, field)
|
||||
}
|
||||
@@ -5940,6 +5952,8 @@ func (ec *executionContext) fieldContext_Query_user(ctx context.Context, field g
|
||||
return ec.fieldContext_User_username(ctx, field)
|
||||
case "name":
|
||||
return ec.fieldContext_User_name(ctx, field)
|
||||
case "project":
|
||||
return ec.fieldContext_User_project(ctx, field)
|
||||
case "email":
|
||||
return ec.fieldContext_User_email(ctx, field)
|
||||
}
|
||||
@@ -8439,6 +8453,47 @@ func (ec *executionContext) fieldContext_User_name(ctx context.Context, field gr
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _User_project(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_User_project(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Project, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
fc.Result = res
|
||||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_User_project(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "User",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _User_email(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_User_email(ctx, field)
|
||||
if err != nil {
|
||||
@@ -10335,7 +10390,7 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"}
|
||||
fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "multiProject", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
@@ -10382,6 +10437,14 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "multiProject":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("multiProject"))
|
||||
it.MultiProject, err = ec.unmarshalOString2ᚕᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "cluster":
|
||||
var err error
|
||||
|
||||
@@ -12309,6 +12372,10 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "project":
|
||||
|
||||
out.Values[i] = ec._User_project(ctx, field, obj)
|
||||
|
||||
case "email":
|
||||
|
||||
out.Values[i] = ec._User_email(ctx, field, obj)
|
||||
@@ -14524,6 +14591,38 @@ func (ec *executionContext) marshalOString2ᚕstringᚄ(ctx context.Context, sel
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOString2ᚕᚖstring(ctx context.Context, v interface{}) ([]*string, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var vSlice []interface{}
|
||||
if v != nil {
|
||||
vSlice = graphql.CoerceList(v)
|
||||
}
|
||||
var err error
|
||||
res := make([]*string, len(vSlice))
|
||||
for i := range vSlice {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
|
||||
res[i], err = ec.unmarshalOString2ᚖstring(ctx, vSlice[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOString2ᚕᚖstring(ctx context.Context, sel ast.SelectionSet, v []*string) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ret := make(graphql.Array, len(v))
|
||||
for i := range v {
|
||||
ret[i] = ec.marshalOString2ᚖstring(ctx, sel, v[i])
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
|
@@ -42,6 +42,7 @@ type JobFilter struct {
|
||||
ArrayJobID *int `json:"arrayJobId"`
|
||||
User *StringInput `json:"user"`
|
||||
Project *StringInput `json:"project"`
|
||||
MultiProject []*string `json:"multiProject"`
|
||||
Cluster *StringInput `json:"cluster"`
|
||||
Partition *StringInput `json:"partition"`
|
||||
Duration *schema.IntRange `json:"duration"`
|
||||
@@ -113,9 +114,10 @@ type TimeRangeOutput struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Project *string `json:"project"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type Aggregate string
|
||||
|
@@ -152,7 +152,7 @@ func (r *queryResolver) Job(ctx context.Context, id string) (*schema.Job, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user := auth.GetUser(ctx); user != nil && job.User != user.Username && user.HasNotRoles([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}){
|
||||
if user := auth.GetUser(ctx); user != nil && job.User != user.Username && user.HasNotRoles([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) {
|
||||
return nil, errors.New("you are not allowed to see this job")
|
||||
}
|
||||
|
||||
|
@@ -103,9 +103,9 @@ func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (queryOut sq.Sel
|
||||
user := auth.GetUser(ctx)
|
||||
if user == nil || user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleApi}) {
|
||||
return query, nil
|
||||
} else if (user.HasRole(auth.RoleManager)) { // Manager (Might be doublefiltered by frontend: should not matter)
|
||||
return query.Where("job.project = ?", user.Project), nil
|
||||
} else if (user.HasRole(auth.RoleUser)) { // User
|
||||
} else if user.HasRole(auth.RoleManager) { // Manager (Might be doublefiltered by frontend: should not matter)
|
||||
return query.Where(sq.Or{sq.Eq{"job.project": user.Projects}}), nil // Only Jobs from manages projects
|
||||
} else if user.HasRole(auth.RoleUser) { // User
|
||||
return query.Where("job.user = ?", user.Username), nil
|
||||
} else { // Unauthorized
|
||||
var qnil sq.SelectBuilder
|
||||
@@ -130,6 +130,13 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
|
||||
if filter.Project != nil {
|
||||
query = buildStringCondition("job.project", filter.Project, query)
|
||||
}
|
||||
if filter.MultiProject != nil {
|
||||
queryProjs := make([]string, len(filter.MultiProject))
|
||||
for i, val := range filter.MultiProject {
|
||||
queryProjs[i] = *val
|
||||
}
|
||||
query = query.Where(sq.Or{sq.Eq{"job.project": queryProjs}})
|
||||
}
|
||||
if filter.Cluster != nil {
|
||||
query = buildStringCondition("job.cluster", filter.Cluster, query)
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
||||
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -58,7 +60,7 @@ func (r *JobRepository) CreateTag(tagType string, tagName string) (tagId int64,
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (r *JobRepository) CountTags(user *string, project *string) (tags []schema.Tag, counts map[string]int, err error) {
|
||||
func (r *JobRepository) CountTags(user *string, projects *[]string) (tags []schema.Tag, counts map[string]int, err error) {
|
||||
tags = make([]schema.Tag, 0, 100)
|
||||
xrows, err := r.DB.Queryx("SELECT * FROM tag")
|
||||
if err != nil {
|
||||
@@ -78,10 +80,11 @@ func (r *JobRepository) CountTags(user *string, project *string) (tags []schema.
|
||||
LeftJoin("jobtag jt ON t.id = jt.tag_id").
|
||||
GroupBy("t.tag_name")
|
||||
|
||||
if (user != nil && project == nil) { // USER: Only count own jobs
|
||||
if user != nil && len(*projects) == 0 { // USER: Only count own jobs
|
||||
q = q.Where("jt.job_id IN (SELECT id FROM job WHERE job.user = ?)", *user)
|
||||
} else if (user != nil && project != nil) { // MANAGER: Count own jobs plus project's jobs
|
||||
q = q.Where("jt.job_id IN (SELECT id FROM job WHERE job.user = ? OR job.project = ?)", *user, *project)
|
||||
} else if user != nil && len(*projects) != 0 { // MANAGER: Count own jobs plus project's jobs
|
||||
// Build ("project1", "project2", ...) list of variable length directly in SQL string
|
||||
q = q.Where("jt.job_id IN (SELECT id FROM job WHERE job.user = ? OR job.project IN (\""+strings.Join(*projects, "\",\"")+"\"))", *user)
|
||||
} // else: ADMIN || SUPPORT: Count all jobs
|
||||
|
||||
rows, err := q.RunWith(r.stmtCache).Query()
|
||||
|
@@ -107,7 +107,6 @@ func setupUserRoute(i InfoType, r *http.Request) InfoType {
|
||||
if user, _ := auth.FetchUser(r.Context(), jobRepo.DB, username); user != nil {
|
||||
i["name"] = user.Name
|
||||
i["email"] = user.Email
|
||||
// i["project"] = user.Project
|
||||
}
|
||||
return i
|
||||
}
|
||||
@@ -144,19 +143,19 @@ func setupAnalysisRoute(i InfoType, r *http.Request) InfoType {
|
||||
|
||||
func setupTaglistRoute(i InfoType, r *http.Request) InfoType {
|
||||
var username *string = nil
|
||||
var project *string = nil
|
||||
var projects *[]string
|
||||
|
||||
jobRepo := repository.GetJobRepository()
|
||||
user := auth.GetUser(r.Context())
|
||||
user := auth.GetUser(r.Context())
|
||||
|
||||
if (user != nil && user.HasNotRoles([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager})) {
|
||||
if user != nil && user.HasNotRoles([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) {
|
||||
username = &user.Username
|
||||
} else if (user != nil && user.HasRole(auth.RoleManager)) {
|
||||
} else if user != nil && user.HasRole(auth.RoleManager) {
|
||||
username = &user.Username
|
||||
project = &user.Project
|
||||
projects = &user.Projects
|
||||
} // ADMINS && SUPPORT w/o additional conditions
|
||||
|
||||
tags, counts, err := jobRepo.CountTags(username, project)
|
||||
tags, counts, err := jobRepo.CountTags(username, projects)
|
||||
tagMap := make(map[string][]map[string]interface{})
|
||||
if err != nil {
|
||||
log.Errorf("GetTags failed: %s", err.Error())
|
||||
@@ -189,6 +188,9 @@ func buildFilterPresets(query url.Values) map[string]interface{} {
|
||||
filterPresets["project"] = query.Get("project")
|
||||
filterPresets["projectMatch"] = "eq"
|
||||
}
|
||||
if len(query["multiProject"]) != 0 {
|
||||
filterPresets["multiProject"] = query["multiProject"]
|
||||
}
|
||||
if query.Get("user") != "" {
|
||||
filterPresets["user"] = query.Get("user")
|
||||
filterPresets["userMatch"] = "eq"
|
||||
@@ -279,17 +281,18 @@ func SetupRoutes(router *mux.Router, version string, hash string, buildTime stri
|
||||
title = strings.Replace(route.Title, "<ID>", id.(string), 1)
|
||||
}
|
||||
|
||||
username, project, authLevel := "", "", 0
|
||||
username, authLevel := "", 0
|
||||
var projects []string
|
||||
|
||||
if user := auth.GetUser(r.Context()); user != nil {
|
||||
username = user.Username
|
||||
project = user.Project
|
||||
username = user.Username
|
||||
projects = user.Projects
|
||||
authLevel = user.GetAuthLevel()
|
||||
}
|
||||
|
||||
page := web.Page{
|
||||
Title: title,
|
||||
User: web.User{Username: username, Project: project, AuthLevel: authLevel},
|
||||
User: web.User{Username: username, Projects: projects, AuthLevel: authLevel},
|
||||
Build: web.Build{Version: version, Hash: hash, Buildtime: buildTime},
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
|
Reference in New Issue
Block a user