package server import ( "context" "log" ct "git.autistici.org/ai3/go-common/ldap/compositetypes" "gopkg.in/yaml.v3" "git.autistici.org/id/auth/backend" ) // BackendSpec parameters for the file backend. type fileServiceParams struct { Src string `yaml:"src"` } type fileUser struct { Name string `yaml:"name"` Email string `yaml:"email"` Shard string `yaml:"shard"` EncryptedPassword string `yaml:"password"` TOTPSecret string `yaml:"totp_secret"` Groups []string `yaml:"groups"` // Legacy U2F registrations are encoded in a similar format as // the one produced by old versions of 'pamu2fcfg': the key // handle is base64-encoded (this is "websafe" base64, without // padding), the public key is hex encoded. U2FRegistrations []struct { KeyHandle string `yaml:"key_handle"` PublicKey string `yaml:"public_key"` Comment string `yaml:"comment"` } `yaml:"u2f_registrations"` // WebAuthN registrations are encoded as emitted by modern // versions of pamu2fcfg: both values are base64-encoded // (standard, with padding). The key is actually in COSE format. WebAuthNRegistrations []struct { KeyHandle string `yaml:"key_handle"` PublicKey string `yaml:"public_key"` Comment string `yaml:"comment"` } `yaml:"webauthn_registrations"` AppSpecificPasswords []struct { ID string `yaml:"id"` Service string `yaml:"service"` EncryptedPassword string `yaml:"password"` Comment string `yaml:"comment"` } `yaml:"app_specific_passwords"` } func (f *fileUser) toUser(filename string) *backend.User { u := &backend.User{ Name: f.Name, Email: f.Email, Shard: f.Shard, EncryptedPassword: f.EncryptedPassword, TOTPSecret: f.TOTPSecret, Groups: f.Groups, } for _, asp := range f.AppSpecificPasswords { u.AppSpecificPasswords = append(u.AppSpecificPasswords, &backend.AppSpecificPassword{ ID: asp.ID, Service: asp.Service, EncryptedPassword: asp.EncryptedPassword, }) } for _, r := range f.WebAuthNRegistrations { reg, err := ct.ParseU2FRegistrationFromStrings(r.KeyHandle, r.PublicKey) if err != nil { log.Printf("warning: %s: user %s: could not decode WebAuthN registration: %v", filename, f.Name, err) continue } cred, err := reg.Decode() if err != nil { log.Printf("warning: %s: user %s: could not decode WebAuthN registration: %v", filename, f.Name, err) continue } u.WebAuthnRegistrations = append(u.WebAuthnRegistrations, cred) } for _, r := range f.U2FRegistrations { reg, err := ct.ParseLegacyU2FRegistrationFromStrings(r.KeyHandle, r.PublicKey) if err != nil { log.Printf("warning: %s: user %s: could not decode U2F registration: %v", filename, f.Name, err) continue } cred, err := reg.Decode() if err != nil { log.Printf("warning: %s: user %s: could not decode U2F registration: %v", filename, f.Name, err) continue } u.WebAuthnRegistrations = append(u.WebAuthnRegistrations, cred) } return u } // Simple file-based authentication backend, list users and their // credentials in a YAML-encoded file. type fileBackend struct { files map[string]map[string]*backend.User configDir string } func loadUsersFile(path string) (map[string]*backend.User, error) { var userList []*fileUser if err := backend.LoadYAML(path, &userList); err != nil { return nil, err } users := make(map[string]*backend.User) for _, u := range userList { users[u.Name] = u.toUser(path) } return users, nil } // New creates a new file-based UserBackend. func New(_ *yaml.Node, configDir string) (backend.UserBackend, error) { return &fileBackend{ files: make(map[string]map[string]*backend.User), configDir: configDir, }, nil } func (b *fileBackend) getUserMap(path string) (map[string]*backend.User, error) { m, ok := b.files[path] if !ok { var err error m, err = loadUsersFile(path) if err != nil { return nil, err } b.files[path] = m } return m, nil } func (b *fileBackend) Close() {} func (b *fileBackend) NewServiceBackend(spec *backend.Spec) (backend.ServiceBackend, error) { var params fileServiceParams if err := spec.Params.Decode(¶ms); err != nil { return nil, err } m, err := b.getUserMap(backend.ResolvePath(params.Src, b.configDir)) if err != nil { return nil, err } return fileServiceBackend(m), nil } type fileServiceBackend map[string]*backend.User func (b fileServiceBackend) GetUser(_ context.Context, name string) (*backend.User, bool) { u, ok := b[name] return u, ok }