Refactor auth module

Separate parts
Add user repository
Add user schema
This commit is contained in:
2023-08-17 10:29:00 +02:00
parent 80aed87415
commit 87ce4f63d4
22 changed files with 637 additions and 600 deletions

View File

@@ -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
View 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
View 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)
}
}