mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-22 20:41:40 +02:00
Refactor auth module
Separate parts Add user repository Add user schema
This commit is contained in:
@@ -17,7 +17,9 @@ type LdapConfig struct {
|
||||
UserFilter string `json:"user_filter"`
|
||||
SyncInterval string `json:"sync_interval"` // Parsed using time.ParseDuration.
|
||||
SyncDelOldUsers bool `json:"sync_del_old_users"`
|
||||
SyncUserOnLogin bool `json:"syncUserOnLogin"`
|
||||
|
||||
// Should an non-existent user be added to the DB if user exists in ldap directory
|
||||
SyncUserOnLogin bool `json:"syncUserOnLogin"`
|
||||
}
|
||||
|
||||
type JWTAuthConfig struct {
|
||||
@@ -30,10 +32,13 @@ type JWTAuthConfig struct {
|
||||
|
||||
// Deny login for users not in database (but defined in JWT).
|
||||
// Ignore user roles defined in JWTs ('roles' claim), get them from db.
|
||||
ForceJWTValidationViaDatabase bool `json:"forceJWTValidationViaDatabase"`
|
||||
ValidateUser bool `json:"validateUser"`
|
||||
|
||||
// Specifies which issuer should be accepted when validating external JWTs ('iss' claim)
|
||||
TrustedExternalIssuer string `json:"trustedExternalIssuer"`
|
||||
TrustedIssuer string `json:"trustedIssuer"`
|
||||
|
||||
// Should an non-existent user be added to the DB based on the information in the token
|
||||
SyncUserOnLogin bool `json:"syncUserOnLogin"`
|
||||
}
|
||||
|
||||
type IntRange struct {
|
||||
|
200
pkg/schema/user.go
Normal file
200
pkg/schema/user.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright (C) 2023 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 schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Role int
|
||||
|
||||
const (
|
||||
RoleAnonymous Role = iota
|
||||
RoleApi
|
||||
RoleUser
|
||||
RoleManager
|
||||
RoleSupport
|
||||
RoleAdmin
|
||||
RoleError
|
||||
)
|
||||
|
||||
type AuthSource int
|
||||
|
||||
const (
|
||||
AuthViaLocalPassword AuthSource = iota
|
||||
AuthViaLDAP
|
||||
AuthViaToken
|
||||
)
|
||||
|
||||
type AuthType int
|
||||
|
||||
const (
|
||||
AuthToken AuthType = iota
|
||||
AuthSession
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Roles []string `json:"roles"`
|
||||
AuthType AuthType `json:"authType"`
|
||||
AuthSource AuthSource `json:"authSource"`
|
||||
Email string `json:"email"`
|
||||
Projects []string `json:"projects"`
|
||||
}
|
||||
|
||||
func (u *User) HasProject(project string) bool {
|
||||
for _, p := range u.Projects {
|
||||
if p == project {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetRoleString(roleInt Role) string {
|
||||
return [6]string{"anonymous", "api", "user", "manager", "support", "admin"}[roleInt]
|
||||
}
|
||||
|
||||
func getRoleEnum(roleStr string) Role {
|
||||
switch strings.ToLower(roleStr) {
|
||||
case "admin":
|
||||
return RoleAdmin
|
||||
case "support":
|
||||
return RoleSupport
|
||||
case "manager":
|
||||
return RoleManager
|
||||
case "user":
|
||||
return RoleUser
|
||||
case "api":
|
||||
return RoleApi
|
||||
case "anonymous":
|
||||
return RoleAnonymous
|
||||
default:
|
||||
return RoleError
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidRole(role string) bool {
|
||||
return getRoleEnum(role) != RoleError
|
||||
}
|
||||
|
||||
func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
|
||||
if IsValidRole(role) {
|
||||
for _, r := range u.Roles {
|
||||
if r == role {
|
||||
return true, true
|
||||
}
|
||||
}
|
||||
return false, true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (u *User) HasRole(role Role) bool {
|
||||
for _, r := range u.Roles {
|
||||
if r == GetRoleString(role) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Role-Arrays are short: performance not impacted by nested loop
|
||||
func (u *User) HasAnyRole(queryroles []Role) bool {
|
||||
for _, ur := range u.Roles {
|
||||
for _, qr := range queryroles {
|
||||
if ur == GetRoleString(qr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Role-Arrays are short: performance not impacted by nested loop
|
||||
func (u *User) HasAllRoles(queryroles []Role) bool {
|
||||
target := len(queryroles)
|
||||
matches := 0
|
||||
for _, ur := range u.Roles {
|
||||
for _, qr := range queryroles {
|
||||
if ur == GetRoleString(qr) {
|
||||
matches += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches == target {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Role-Arrays are short: performance not impacted by nested loop
|
||||
func (u *User) HasNotRoles(queryroles []Role) bool {
|
||||
matches := 0
|
||||
for _, ur := range u.Roles {
|
||||
for _, qr := range queryroles {
|
||||
if ur == GetRoleString(qr) {
|
||||
matches += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches == 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Called by API endpoint '/roles/' from frontend: Only required for admin config -> Check Admin Role
|
||||
func GetValidRoles(user *User) ([]string, error) {
|
||||
var vals []string
|
||||
if user.HasRole(RoleAdmin) {
|
||||
for i := RoleApi; i < RoleError; i++ {
|
||||
vals = append(vals, GetRoleString(i))
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
return vals, fmt.Errorf("%s: only admins are allowed to fetch a list of roles", user.Username)
|
||||
}
|
||||
|
||||
// Called by routerConfig web.page setup in backend: Only requires known user
|
||||
func GetValidRolesMap(user *User) (map[string]Role, error) {
|
||||
named := make(map[string]Role)
|
||||
if user.HasNotRoles([]Role{RoleAnonymous}) {
|
||||
for i := RoleApi; i < RoleError; i++ {
|
||||
named[GetRoleString(i)] = i
|
||||
}
|
||||
return named, nil
|
||||
}
|
||||
return named, fmt.Errorf("only known users are allowed to fetch a list of roles")
|
||||
}
|
||||
|
||||
// Find highest role
|
||||
func (u *User) GetAuthLevel() Role {
|
||||
if u.HasRole(RoleAdmin) {
|
||||
return RoleAdmin
|
||||
} else if u.HasRole(RoleSupport) {
|
||||
return RoleSupport
|
||||
} else if u.HasRole(RoleManager) {
|
||||
return RoleManager
|
||||
} else if u.HasRole(RoleUser) {
|
||||
return RoleUser
|
||||
} else if u.HasRole(RoleApi) {
|
||||
return RoleApi
|
||||
} else if u.HasRole(RoleAnonymous) {
|
||||
return RoleAnonymous
|
||||
} else {
|
||||
return RoleError
|
||||
}
|
||||
}
|
129
pkg/schema/user_test.go
Normal file
129
pkg/schema/user_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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 schema
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHasValidRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
exists, _ := u.HasValidRole("user")
|
||||
|
||||
if !exists {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasValidRole("user"): EXISTS = %v, expected 'true'.`, exists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotValidRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
exists, _ := u.HasValidRole("manager")
|
||||
|
||||
if exists {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasValidRole("manager"): EXISTS = %v, expected 'false'.`, exists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasInvalidRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
_, valid := u.HasValidRole("invalid")
|
||||
|
||||
if valid {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasValidRole("invalid"): VALID = %v, expected 'false'.`, valid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotInvalidRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
_, valid := u.HasValidRole("user")
|
||||
|
||||
if !valid {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasValidRole("user"): VALID = %v, expected 'true'.`, valid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
exists := u.HasRole(RoleUser)
|
||||
|
||||
if !exists {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasRole(RoleUser): EXISTS = %v, expected 'true'.`, exists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user"}}
|
||||
|
||||
exists := u.HasRole(RoleManager)
|
||||
|
||||
if exists {
|
||||
t.Fatalf(`User{Roles: ["user"]} -> HasRole(RoleManager): EXISTS = %v, expected 'false'.`, exists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasAnyRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager"}}
|
||||
|
||||
result := u.HasAnyRole([]Role{RoleManager, RoleSupport, RoleAdmin})
|
||||
|
||||
if !result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager"]} -> HasAnyRole([]Role{RoleManager, RoleSupport, RoleAdmin}): RESULT = %v, expected 'true'.`, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotAnyRole(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager"}}
|
||||
|
||||
result := u.HasAnyRole([]Role{RoleSupport, RoleAdmin})
|
||||
|
||||
if result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager"]} -> HasAllRoles([]Role{RoleSupport, RoleAdmin}): RESULT = %v, expected 'false'.`, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasAllRoles(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager", "support"}}
|
||||
|
||||
result := u.HasAllRoles([]Role{RoleUser, RoleManager, RoleSupport})
|
||||
|
||||
if !result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager", "support"]} -> HasAllRoles([]Role{RoleUser, RoleManager, RoleSupport}): RESULT = %v, expected 'true'.`, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotAllRoles(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager"}}
|
||||
|
||||
result := u.HasAllRoles([]Role{RoleUser, RoleManager, RoleSupport})
|
||||
|
||||
if result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager"]} -> HasAllRoles([]Role{RoleUser, RoleManager, RoleSupport}): RESULT = %v, expected 'false'.`, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNotRoles(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager"}}
|
||||
|
||||
result := u.HasNotRoles([]Role{RoleSupport, RoleAdmin})
|
||||
|
||||
if !result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager"]} -> HasNotRoles([]Role{RoleSupport, RoleAdmin}): RESULT = %v, expected 'true'.`, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasAllNotRoles(t *testing.T) {
|
||||
u := User{Username: "testuser", Roles: []string{"user", "manager"}}
|
||||
|
||||
result := u.HasNotRoles([]Role{RoleUser, RoleManager})
|
||||
|
||||
if result {
|
||||
t.Fatalf(`User{Roles: ["user", "manager"]} -> HasNotRoles([]Role{RoleUser, RoleManager}): RESULT = %v, expected 'false'.`, result)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user