347 lines
9.0 KiB
Go
347 lines
9.0 KiB
Go
/*
|
|
* Amsterdam Web Communities System
|
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*/
|
|
// The database package contains database management and storage logic.
|
|
package database
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/labstack/gommon/log"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
/* securityScope defines nested "scopes" within the security level system. Each scope is numerically nested
|
|
* inside the previous one and outside the next one. Each scope has a "low band" of values (for ordinary users)
|
|
* and a "high band" of values (for users with administrative privilege).
|
|
*/
|
|
type securityScope struct {
|
|
lowbandLow uint16
|
|
lowbandHigh uint16
|
|
highbandLow uint16
|
|
highbandHigh uint16
|
|
}
|
|
|
|
// scopelist defines the boundaries of the known scopes. There are 16 in all, most are unused.
|
|
var scopelist = []securityScope{
|
|
{0, 1999, 63000, 64999}, // global scope
|
|
{2000, 3999, 61000, 62999},
|
|
{4000, 5999, 59000, 60999},
|
|
{6000, 7999, 57000, 58999}, // community scope
|
|
{8000, 9999, 55000, 56999},
|
|
{10000, 11999, 53000, 54999},
|
|
{12000, 13999, 51000, 52999}, // conference scope
|
|
{14000, 15999, 49000, 50999},
|
|
{16000, 17999, 47000, 48999},
|
|
{18000, 19999, 45000, 46999},
|
|
{20000, 21999, 43000, 44999},
|
|
{22000, 23999, 41000, 42999},
|
|
{24000, 25999, 39000, 40999},
|
|
{26000, 27999, 37000, 38999},
|
|
{28000, 29999, 35000, 36999},
|
|
{30000, 31999, 33000, 34999},
|
|
}
|
|
|
|
// CfgScope is a configured scope.
|
|
type CfgScope struct {
|
|
Name string `yaml:"name"`
|
|
Index int `yaml:"index"`
|
|
bounds *securityScope
|
|
}
|
|
|
|
// CfgRole is a configured role.
|
|
type CfgRole struct {
|
|
Internal string `yaml:"name"`
|
|
Display string `yaml:"display"`
|
|
Scope string `yaml:"scope"`
|
|
Value string `yaml:"value"`
|
|
level uint16
|
|
}
|
|
|
|
// CfgDefault is a configured default value.
|
|
type CfgDefault struct {
|
|
Name string `yaml:"name"`
|
|
Role string `yaml:"role"`
|
|
roleptr *CfgRole
|
|
}
|
|
|
|
// CfgRoleList is a configured role list.
|
|
type CfgRoleList struct {
|
|
Name string `yaml:"name"`
|
|
DDefault string `yaml:"default"`
|
|
DRoles []string `yaml:"roles"`
|
|
defptr *CfgRole
|
|
roleptrs []*CfgRole
|
|
}
|
|
|
|
// CfgPermission is a configured permission.
|
|
type CfgPermission struct {
|
|
Name string `yaml:"name"`
|
|
Role string `yaml:"role"`
|
|
level uint16
|
|
}
|
|
|
|
// CfgSecurityDefs is the master structure for security definitions.
|
|
type CfgSecurityDefs struct {
|
|
Scopes []CfgScope `yaml:"scopes"`
|
|
Roles []CfgRole `yaml:"roles"`
|
|
Defaults []CfgDefault `yaml:"defaults"`
|
|
Lists []CfgRoleList `yaml:"lists"`
|
|
Permissions []CfgPermission `yaml:"permissions"`
|
|
scopeMap map[string]*CfgScope
|
|
roleMap map[string]*CfgRole
|
|
roleMapReverse map[uint16]*CfgRole
|
|
defaultsMap map[string]*CfgDefault
|
|
listsMap map[string]*CfgRoleList
|
|
permsMap map[string]*CfgPermission
|
|
}
|
|
|
|
//go:embed securitydefs.yaml
|
|
var initSecurityData []byte
|
|
|
|
// securityRoot contains the root-level security information.
|
|
var securityRoot CfgSecurityDefs
|
|
|
|
// parseLevelValue is a helper which parses the role level definition strings.
|
|
func parseLevelValue(scope *securityScope, value string) uint16 {
|
|
switch value {
|
|
case "LMIN":
|
|
return scope.lowbandLow
|
|
case "LMAX":
|
|
return scope.lowbandHigh
|
|
case "HMIN":
|
|
return scope.highbandLow
|
|
case "HMAX":
|
|
return scope.highbandHigh
|
|
}
|
|
if strings.HasPrefix(value, "L+") {
|
|
v, err := strconv.Atoi(value[2:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return scope.lowbandLow + uint16(v)
|
|
}
|
|
if strings.HasPrefix(value, "H+") {
|
|
v, err := strconv.Atoi(value[2:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return scope.highbandLow + uint16(v)
|
|
}
|
|
v, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return uint16(v)
|
|
}
|
|
|
|
// init sets up all the security data.
|
|
func init() {
|
|
if err := yaml.Unmarshal(initSecurityData, &securityRoot); err != nil {
|
|
panic(err) // can't happen
|
|
}
|
|
securityRoot.scopeMap = make(map[string]*CfgScope)
|
|
for i, sc := range securityRoot.Scopes {
|
|
securityRoot.Scopes[i].bounds = &(scopelist[sc.Index])
|
|
securityRoot.scopeMap[sc.Name] = &(securityRoot.Scopes[i])
|
|
}
|
|
securityRoot.roleMap = make(map[string]*CfgRole)
|
|
securityRoot.roleMapReverse = make(map[uint16]*CfgRole)
|
|
for i, ro := range securityRoot.Roles {
|
|
scope := securityRoot.scopeMap[ro.Scope]
|
|
securityRoot.Roles[i].level = parseLevelValue(scope.bounds, ro.Value)
|
|
securityRoot.roleMap[ro.Internal] = &(securityRoot.Roles[i])
|
|
securityRoot.roleMapReverse[securityRoot.Roles[i].level] = &(securityRoot.Roles[i])
|
|
}
|
|
securityRoot.defaultsMap = make(map[string]*CfgDefault)
|
|
for i, def := range securityRoot.Defaults {
|
|
securityRoot.Defaults[i].roleptr = securityRoot.roleMap[def.Role]
|
|
securityRoot.defaultsMap[def.Name] = &(securityRoot.Defaults[i])
|
|
}
|
|
securityRoot.listsMap = make(map[string]*CfgRoleList)
|
|
for i, li := range securityRoot.Lists {
|
|
if li.DDefault != "" {
|
|
securityRoot.Lists[i].defptr = securityRoot.roleMap[li.DDefault]
|
|
}
|
|
securityRoot.Lists[i].roleptrs = make([]*CfgRole, len(li.DRoles))
|
|
for j, rn := range li.DRoles {
|
|
securityRoot.Lists[i].roleptrs[j] = securityRoot.roleMap[rn]
|
|
}
|
|
securityRoot.listsMap[li.Name] = &(securityRoot.Lists[i])
|
|
}
|
|
securityRoot.permsMap = make(map[string]*CfgPermission)
|
|
for i, pm := range securityRoot.Permissions {
|
|
securityRoot.Permissions[i].level = securityRoot.roleMap[pm.Role].level
|
|
securityRoot.permsMap[pm.Name] = &(securityRoot.Permissions[i])
|
|
}
|
|
}
|
|
|
|
// Role defines a security role.
|
|
type Role interface {
|
|
ID() string
|
|
Name() string
|
|
Level() uint16
|
|
LevelStr() string
|
|
}
|
|
|
|
func (r *CfgRole) ID() string {
|
|
return r.Internal
|
|
}
|
|
|
|
func (r *CfgRole) Name() string {
|
|
return r.Display
|
|
}
|
|
|
|
func (r *CfgRole) Level() uint16 {
|
|
return r.level
|
|
}
|
|
|
|
func (r *CfgRole) LevelStr() string {
|
|
return fmt.Sprintf("%d", r.level)
|
|
}
|
|
|
|
// RoleList defines a list of security roles.
|
|
type RoleList interface {
|
|
Roles() []Role
|
|
Default() Role
|
|
FindForLevel(uint16) Role
|
|
}
|
|
|
|
func (r *CfgRoleList) Roles() []Role {
|
|
rc := make([]Role, len(r.roleptrs))
|
|
for i := range r.roleptrs {
|
|
rc[i] = r.roleptrs[i]
|
|
}
|
|
return rc
|
|
}
|
|
|
|
func (r *CfgRoleList) Default() Role {
|
|
return r.defptr
|
|
}
|
|
|
|
func (r *CfgRoleList) FindForLevel(level uint16) Role {
|
|
for _, rp := range r.roleptrs {
|
|
if rp.level == level {
|
|
return rp
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/* AmRole returns a Role given a string ID.
|
|
* Parameters:
|
|
* id - ID of the role to look up.
|
|
* Returns:
|
|
* The specified role.
|
|
*/
|
|
func AmRole(id string) Role {
|
|
rc, ok := securityRoot.roleMap[id]
|
|
if !ok {
|
|
log.Errorf("AmRole('%s') - role not found!", id)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
/* AmRoleForLevel returns a Role given an integer level.
|
|
* Parameters:
|
|
* level - Level of the Role to look up.
|
|
* Returns:
|
|
* The specified role.
|
|
*/
|
|
func AmRoleForLevel(level uint16) Role {
|
|
rc, ok := securityRoot.roleMapReverse[level]
|
|
if !ok {
|
|
log.Errorf("AmRoleForLevel('%d') - role not found!", level)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
/* AmDefaultRole returns a Role given a default ID.
|
|
* Parameters:
|
|
* id - ID of the default to look up.
|
|
* Returns:
|
|
* The specified role.
|
|
*/
|
|
func AmDefaultRole(id string) Role {
|
|
dr, ok := securityRoot.defaultsMap[id]
|
|
if !ok {
|
|
log.Errorf("AmDefaultRole('%s') - default role not found!", id)
|
|
return nil
|
|
}
|
|
return dr.roleptr
|
|
}
|
|
|
|
/* AmRoleList returns a RoleList given a list ID.
|
|
* Parameters:
|
|
* id - ID of the list to look up.
|
|
* Returns:
|
|
* The specified role list.
|
|
*/
|
|
func AmRoleList(id string) RoleList {
|
|
rc, ok := securityRoot.listsMap[id]
|
|
if !ok {
|
|
log.Errorf("AmRoleList('%s') - role list not found!", id)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
/* AmTestPermission tests a specified access level to see if it satisfies the given permission.
|
|
* Parameters:
|
|
* id - ID of the permission to check.
|
|
* level - The access level to be verified.
|
|
* Returns:
|
|
* true if the permission test is satisfied, false if not.
|
|
*/
|
|
func AmTestPermission(id string, level uint16) bool {
|
|
perm, ok := securityRoot.permsMap[id]
|
|
if !ok {
|
|
log.Errorf("AmTestPermission('%s') - permission not found!", id)
|
|
return false
|
|
}
|
|
return perm.level <= level
|
|
}
|
|
|
|
// AmPermissionLevel returns a level value for a permission.
|
|
func AmPermissionLevel(id string) uint16 {
|
|
perm, ok := securityRoot.permsMap[id]
|
|
if !ok {
|
|
log.Errorf("AmPermissionLevel('%s') - permission not found!", id)
|
|
return 0
|
|
}
|
|
return perm.level
|
|
}
|
|
|
|
/* AmCombinePermissionRole combines a permission and a role into a single permission level.
|
|
* Parameters:
|
|
* perm - Permission to use.
|
|
* role - Role to use.
|
|
* Returns:
|
|
* The combined permission level.
|
|
*/
|
|
func AmCombinePermissionRole(perm string, role string) uint16 {
|
|
pperm, ok := securityRoot.permsMap[perm]
|
|
if !ok {
|
|
log.Errorf("AmCombinePermissionRole('%s', '%s') - permission not found!", perm, role)
|
|
return 0
|
|
}
|
|
prole, ok := securityRoot.roleMap[role]
|
|
if !ok {
|
|
log.Errorf("AmCombinePermissionRole('%s', '%s') - role not found!", perm, role)
|
|
return 0
|
|
}
|
|
if pperm.level > prole.level {
|
|
return pperm.level
|
|
}
|
|
return prole.level
|
|
}
|