Files
Cyrene_v2/internal/user/load.go
T

60 lines
1.7 KiB
Go

package user
import (
"errors"
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
// ErrRequiredField is wrapped by validation errors so callers can detect an
// owner profile that is missing required fields.
var ErrRequiredField = errors.New("user: required field missing")
// LoadOwnerProfile reads and validates the owner profile YAML at path.
func LoadOwnerProfile(path string) (*OwnerProfile, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("user: read %s: %w", path, err)
}
p := &OwnerProfile{}
if err := yaml.Unmarshal(data, p); err != nil {
return nil, fmt.Errorf("user: parse %s: %w", path, err)
}
if err := p.validate(); err != nil {
return nil, err
}
return p, nil
}
// validate checks the owner profile for its required fields and for partial
// whitelist entries. An empty whitelist is allowed: on a fresh install nobody
// has been configured as owner, so the policy engine defaults to deny until the
// operator fills in account ids (fail-safe, per design/11).
func (p *OwnerProfile) validate() error {
var missing []string
if strings.TrimSpace(p.ID) == "" {
missing = append(missing, "id")
}
if strings.TrimSpace(p.DisplayName) == "" {
missing = append(missing, "displayName")
}
if len(missing) > 0 {
return fmt.Errorf("%w: missing %s", ErrRequiredField, strings.Join(missing, ", "))
}
for i, a := range p.Accounts {
pe := strings.TrimSpace(a.Platform) == ""
ae := strings.TrimSpace(a.AccountID) == ""
switch {
case pe && ae:
// Fully-empty entry: an unconfigured placeholder, skip it.
continue
case pe || ae:
return fmt.Errorf("%w: accounts[%d] requires both platform and accountId", ErrRequiredField, i)
}
}
return nil
}