api/policy.go

175 lines
4.0 KiB
Go

package api
import (
"errors"
"fmt"
"reflect"
"strings"
"apigo.cc/go/cast"
"apigo.cc/go/safe"
)
var overrideBlacklist = map[string]bool{
"tokens": true, "enabled": true, "test": true, "logging": true,
}
func verifyToken(config map[string]any, token string) error {
raw, exists := config["tokens"]
if !exists || raw == nil {
return nil
}
tokens := make([]string, 0)
switch values := raw.(type) {
case []string:
for _, value := range values {
if value != "" {
tokens = append(tokens, value)
}
}
case []any:
for _, value := range values {
if token := cast.String(value); token != "" {
tokens = append(tokens, token)
}
}
default:
v := reflect.ValueOf(raw)
if v.IsValid() && (v.Kind() == reflect.Array || v.Kind() == reflect.Slice) {
for i := 0; i < v.Len(); i++ {
tokens = append(tokens, cast.String(v.Index(i).Interface()))
}
}
}
if len(tokens) == 0 {
return nil
}
for _, allowed := range tokens {
if allowed == token {
return nil
}
}
return errors.New("API token is required or invalid")
}
func mergeOverrides(config, overrides map[string]any) error {
if len(overrides) == 0 {
return nil
}
return mergeAllowed(config, overrides, "")
}
func mergeAllowed(dst, src map[string]any, prefix string) error {
for key, value := range src {
path := key
if prefix != "" {
path = prefix + "." + key
}
if overrideBlacklist[key] {
return fmt.Errorf("configuration field %s cannot be overridden", path)
}
current, exists := dst[key]
if exists && isSecretValue(current) {
return fmt.Errorf("encrypted configuration field %s cannot be overridden", path)
}
if child, ok := value.(map[string]any); ok {
currentMap, _ := current.(map[string]any)
if currentMap == nil {
currentMap = map[string]any{}
dst[key] = currentMap
}
if err := mergeAllowed(currentMap, child, path); err != nil {
return err
}
continue
}
dst[key] = value
}
return nil
}
func isSecretValue(value any) bool {
switch t := value.(type) {
case string:
return strings.HasPrefix(t, "**")
case *safe.SafeBuf, *safe.SecretPlaintext:
return true
default:
return false
}
}
func applyResponseRules(config map[string]any, result *Result) {
response, _ := config["response"].(map[string]any)
result.Code = firstPathString(result.Data, response["codeFields"])
if result.Error == "" {
result.Error = firstPathString(result.Data, response["errorFields"])
}
result.Ok = result.StatusCode >= 200 && result.StatusCode < 300 && result.Error == ""
if (result.StatusCode < 200 || result.StatusCode >= 300) && result.Error == "" {
result.Error = fmt.Sprintf("HTTP status %d", result.StatusCode)
}
if codes := stringList(response["successCodes"]); len(codes) > 0 {
if result.Code == "" {
result.Ok = false
if result.Error == "" {
result.Error = "success code field was not found"
}
return
}
result.Ok = false
for _, code := range codes {
if result.Code == code {
result.Ok = result.Error == "" && result.StatusCode >= 200 && result.StatusCode < 300
break
}
}
if !result.Ok && result.Error == "" {
result.Error = "unexpected response code: " + result.Code
}
}
}
func stringList(value any) []string {
var result []string
switch values := value.(type) {
case []string:
return append(result, values...)
case []any:
for _, item := range values {
result = append(result, cast.String(item))
}
case string:
if values != "" {
result = append(result, values)
}
}
return result
}
func firstPathString(data, fields any) string {
for _, path := range stringList(fields) {
if value, ok := pathValue(data, path); ok {
if text := cast.String(value); text != "" {
return text
}
}
}
return ""
}
func pathValue(data any, path string) (any, bool) {
current := data
for _, part := range strings.Split(path, ".") {
m, ok := current.(map[string]any)
if !ok {
return nil, false
}
current, ok = m[part]
if !ok {
return nil, false
}
}
return current, true
}