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 }