mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 21:13:00 +08:00
169 lines
6.3 KiB
Go
169 lines
6.3 KiB
Go
package config
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
)
|
|
|
|
// Normalize canonicalises the fields where a canonical form exists, and leaves
|
|
// the rest exactly as the user typed them.
|
|
//
|
|
// There is no general "clean every string" pass. Trimming a password or a
|
|
// wireless key turns a working credential into a failing one that looks
|
|
// correct in the form, and an SSID may legitimately begin or end with a space.
|
|
// The fields listed here are the ones with a defined canonical form:
|
|
//
|
|
// trimmed identifiers, labels, interface and radio names, URLs
|
|
// lowercased enum-like values and BSSIDs
|
|
// untouched user_id, password, key, ssid, operator_suffix, login.*
|
|
//
|
|
// Normalize also clears the half of an account that its access mode does not
|
|
// use, so a stored account never carries an effective configuration for a mode
|
|
// it is not in.
|
|
func Normalize(cfg domain.Config) domain.Config {
|
|
cfg.School = strings.TrimSpace(cfg.School)
|
|
if cfg.School == "" {
|
|
cfg.School = DefaultSchool
|
|
}
|
|
cfg.STAIface = strings.TrimSpace(cfg.STAIface)
|
|
cfg.Log.Level = domain.LogLevel(strings.ToUpper(strings.TrimSpace(string(cfg.Log.Level))))
|
|
cfg.Checks.Mode = domain.CheckMode(strings.ToLower(strings.TrimSpace(string(cfg.Checks.Mode))))
|
|
|
|
cfg.Selection.ActiveCampusID = strings.TrimSpace(cfg.Selection.ActiveCampusID)
|
|
cfg.Selection.DefaultCampusID = strings.TrimSpace(cfg.Selection.DefaultCampusID)
|
|
cfg.Selection.ActiveHotspotID = strings.TrimSpace(cfg.Selection.ActiveHotspotID)
|
|
cfg.Selection.DefaultHotspotID = strings.TrimSpace(cfg.Selection.DefaultHotspotID)
|
|
|
|
accounts := make([]domain.CampusAccount, len(cfg.CampusAccounts))
|
|
for i, account := range cfg.CampusAccounts {
|
|
accounts[i] = NormalizeCampusAccount(account)
|
|
}
|
|
cfg.CampusAccounts = accounts
|
|
|
|
hotspots := make([]domain.HotspotProfile, len(cfg.HotspotProfiles))
|
|
for i, hotspot := range cfg.HotspotProfiles {
|
|
hotspots[i] = NormalizeHotspot(hotspot)
|
|
}
|
|
cfg.HotspotProfiles = hotspots
|
|
|
|
cfg.SchoolExtra, _ = FilterSchoolExtra(cfg.School, cfg.SchoolExtra)
|
|
return cfg
|
|
}
|
|
|
|
// FilterSchoolExtra keeps only the keys the named strategy declares, and
|
|
// reports the ones it dropped.
|
|
//
|
|
// Spec 03: school_extra accepts only the current strategy's declared
|
|
// descriptors, unknown keys are dropped with a diagnostic, and switching
|
|
// strategy must not carry the previous one's private parameters along. Until
|
|
// this existed the map was stored verbatim, so a key nothing declared lived in
|
|
// the configuration forever and a strategy could be handed parameters it never
|
|
// asked for.
|
|
//
|
|
// The dropped names are returned rather than logged here: this package does no
|
|
// I/O, and the caller knows whether a person is waiting for the answer.
|
|
func FilterSchoolExtra(school string, extra map[string]any) (map[string]any, []string) {
|
|
kept := make(map[string]any, len(extra))
|
|
if len(extra) == 0 {
|
|
return kept, nil
|
|
}
|
|
declared, known := SchoolRegistry.Lookup(school)
|
|
var dropped []string
|
|
for key, value := range extra {
|
|
// An unknown strategy declares nothing, so everything goes. That is the
|
|
// same answer as a known strategy with no fields, and it is the right
|
|
// one: values whose meaning depends on code this build does not have
|
|
// are not values this build can honour.
|
|
if known && declared.DeclaresField(key) {
|
|
kept[key] = value
|
|
continue
|
|
}
|
|
dropped = append(dropped, key)
|
|
}
|
|
slices.Sort(dropped)
|
|
return kept, dropped
|
|
}
|
|
|
|
// NormalizeCampusAccount canonicalises one account and clears the fields its
|
|
// access mode does not use.
|
|
func NormalizeCampusAccount(account domain.CampusAccount) domain.CampusAccount {
|
|
account.ID = strings.TrimSpace(account.ID)
|
|
account.Label = strings.TrimSpace(account.Label)
|
|
account.Operator = strings.TrimSpace(account.Operator)
|
|
account.BaseURL = strings.TrimSpace(account.BaseURL)
|
|
account.ACID = strings.TrimSpace(account.ACID)
|
|
account.PresetID = strings.TrimSpace(account.PresetID)
|
|
account.AccessMode = domain.AccessMode(
|
|
strings.ToLower(strings.TrimSpace(string(account.AccessMode))))
|
|
|
|
if account.IsWired() {
|
|
account.WiredIface = strings.TrimSpace(account.WiredIface)
|
|
if account.WiredIface == "" {
|
|
account.WiredIface = DefaultWiredIface
|
|
}
|
|
account.SSID = ""
|
|
account.Radio = ""
|
|
account.Encryption = ""
|
|
account.Key = ""
|
|
account.APSelection = ""
|
|
account.BSSID = ""
|
|
return account
|
|
}
|
|
|
|
account.Radio = strings.TrimSpace(account.Radio)
|
|
account.Encryption = NormalizeEncryption(account.Encryption)
|
|
account.BSSID = strings.ToLower(strings.TrimSpace(account.BSSID))
|
|
account.APSelection = normalizeAPSelection(account.APSelection, account.BSSID)
|
|
account.WiredIface = ""
|
|
account.AuthEnabled = false
|
|
return account
|
|
}
|
|
|
|
// NormalizeHotspot canonicalises one hotspot profile.
|
|
func NormalizeHotspot(hotspot domain.HotspotProfile) domain.HotspotProfile {
|
|
hotspot.ID = strings.TrimSpace(hotspot.ID)
|
|
hotspot.Label = strings.TrimSpace(hotspot.Label)
|
|
hotspot.Radio = strings.TrimSpace(hotspot.Radio)
|
|
hotspot.Encryption = NormalizeEncryption(hotspot.Encryption)
|
|
return hotspot
|
|
}
|
|
|
|
// EncryptionNone is the canonical spelling for an open network.
|
|
const EncryptionNone = "none"
|
|
|
|
// NormalizeEncryption folds the several ways an open network is written into
|
|
// one value, so "same SSID, different encryption" stays a real distinction.
|
|
func NormalizeEncryption(value string) string {
|
|
folded := strings.ToLower(strings.TrimSpace(value))
|
|
switch folded {
|
|
case "", "none", "open", "nopass":
|
|
return EncryptionNone
|
|
default:
|
|
return folded
|
|
}
|
|
}
|
|
|
|
// KeyRequired reports whether a passphrase is needed for this encryption.
|
|
func KeyRequired(encryption string) bool {
|
|
return NormalizeEncryption(encryption) != EncryptionNone
|
|
}
|
|
|
|
// normalizeAPSelection keeps the policy consistent with whether a BSSID was
|
|
// actually supplied: an unrecognised policy with a BSSID means "fixed", and
|
|
// without one means "auto". A *stated* fixed policy with a missing or invalid
|
|
// BSSID is not quietly downgraded here -- validation rejects it, because
|
|
// silently roaming away from a pinned AP is exactly the surprise a user who
|
|
// pinned it was trying to avoid.
|
|
func normalizeAPSelection(policy domain.APSelection, bssid string) domain.APSelection {
|
|
folded := domain.APSelection(strings.ToLower(strings.TrimSpace(string(policy))))
|
|
if folded.Valid() {
|
|
return folded
|
|
}
|
|
if bssid != "" {
|
|
return domain.APSelectionFixed
|
|
}
|
|
return domain.APSelectionAuto
|
|
}
|