130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type AccessContext struct {
|
|
Kind string
|
|
TenantID int64
|
|
UserID int64
|
|
Role string
|
|
Token string
|
|
}
|
|
|
|
func (s *Server) ResolveAccess(r *http.Request, masterToken uint64) (*AccessContext, bool) {
|
|
tok := BearerToken(r)
|
|
if tok == "" {
|
|
return nil, false
|
|
}
|
|
|
|
if tok == strconv.FormatUint(masterToken, 10) {
|
|
return &AccessContext{Kind: "master", Role: "admin", Token: tok}, true
|
|
}
|
|
|
|
return s.ResolveTenantAccessToken(tok)
|
|
}
|
|
|
|
func GetAccessContext(r *http.Request) *AccessContext {
|
|
v := r.Context().Value(ServerCtxKeyAccess{})
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if ac, ok := v.(*AccessContext); ok {
|
|
return ac
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) ResolveTenantAccessToken(tok string) (*AccessContext, bool) {
|
|
if tok == "" || s.store == nil {
|
|
return nil, false
|
|
}
|
|
|
|
if ss, err := s.store.VerifySessionToken(tok); err == nil && ss != nil {
|
|
return &AccessContext{
|
|
Kind: "session",
|
|
TenantID: ss.TenantID,
|
|
UserID: ss.UserID,
|
|
Role: ss.Role,
|
|
Token: tok,
|
|
}, true
|
|
}
|
|
|
|
if ten, err := s.store.VerifyAPIKey(tok); err == nil && ten != nil {
|
|
return &AccessContext{
|
|
Kind: "apikey",
|
|
TenantID: ten.ID,
|
|
Role: "apikey",
|
|
Token: tok,
|
|
}, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// TenantCan implements action-level RBAC for tenant-scoped APIs.
|
|
func TenantCan(ac *AccessContext, action string) bool {
|
|
if ac == nil {
|
|
return false
|
|
}
|
|
switch action {
|
|
case "view":
|
|
return (ac.Kind == "session" && (ac.Role == "admin" || ac.Role == "operator")) || ac.Kind == "apikey"
|
|
case "mutate_node_meta", "mutate_sdwan", "mutate_connect", "mutate_node_control":
|
|
return ac.Kind == "session" && (ac.Role == "admin" || ac.Role == "operator")
|
|
case "enroll_manage":
|
|
return ac.Kind == "session" && ac.Role == "admin"
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func settingOn(v string) bool {
|
|
v = strings.TrimSpace(strings.ToLower(v))
|
|
return v == "1" || v == "true" || v == "on" || v == "yes"
|
|
}
|
|
|
|
// TenantCanWithOverride evaluates baseline RBAC then optional advanced override switches.
|
|
// Return values: allowed, usedOverride
|
|
func (s *Server) TenantCanWithOverride(ac *AccessContext, action string) (bool, bool) {
|
|
if TenantCan(ac, action) {
|
|
return true, false
|
|
}
|
|
if ac == nil || s == nil || s.store == nil || ac.TenantID <= 0 {
|
|
return false, false
|
|
}
|
|
if ac.Kind == "master" {
|
|
return false, false
|
|
}
|
|
// global override gate
|
|
if v, _, err := s.store.GetSetting("advanced_override_enabled"); err != nil || !settingOn(v) {
|
|
return false, false
|
|
}
|
|
|
|
switch action {
|
|
case "mutate_sdwan":
|
|
if ac.Kind == "apikey" {
|
|
if v, _, err := s.store.GetSetting("allow_apikey_mutate_sdwan"); err == nil && settingOn(v) {
|
|
return true, true
|
|
}
|
|
}
|
|
case "mutate_connect":
|
|
if ac.Kind == "session" && ac.Role == "operator" {
|
|
if v, _, err := s.store.GetSetting("allow_connect_cross_tenant"); err == nil && settingOn(v) {
|
|
return true, true
|
|
}
|
|
}
|
|
case "mutate_node_control":
|
|
if ac.Kind == "session" && ac.Role == "operator" {
|
|
if v, _, err := s.store.GetSetting("allow_operator_kick_cross_node"); err == nil && settingOn(v) {
|
|
return true, true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, false
|
|
}
|