file.go 3.25 KB
Newer Older
ale's avatar
ale committed
1 2 3 4
package server

import (
	"context"
5 6
	"encoding/base64"
	"encoding/hex"
ale's avatar
ale committed
7

8 9
	ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
	"github.com/tstranex/u2f"
ale's avatar
ale committed
10
	"gopkg.in/yaml.v2"
ale's avatar
ale committed
11 12

	"git.autistici.org/id/auth/backend"
ale's avatar
ale committed
13 14
)

ale's avatar
ale committed
15 16 17 18 19 20
// BackendSpec parameters for the file backend.
type fileServiceParams struct {
	Src string `yaml:"src"`
}

type fileUser struct {
ale's avatar
ale committed
21 22
	Name              string   `yaml:"name"`
	Email             string   `yaml:"email"`
ale's avatar
ale committed
23
	Shard             string   `yaml:"shard"`
ale's avatar
ale committed
24 25 26
	EncryptedPassword string   `yaml:"password"`
	TOTPSecret        string   `yaml:"totp_secret"`
	Groups            []string `yaml:"groups"`
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

	// U2F registrations are encoded in a similar format as the
	// one produced by '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"`
	} `yaml:"u2f_registrations"`
}

func (f *fileUser) getU2FRegistrations() []u2f.Registration {
	var out []u2f.Registration
	for _, r := range f.U2FRegistrations {
		kh, err := base64.RawURLEncoding.DecodeString(r.KeyHandle)
		if err != nil {
			continue
		}
		pk, err := hex.DecodeString(r.PublicKey)
		if err != nil {
			continue
		}
		ctr := ct.U2FRegistration{
			KeyHandle: kh,
			PublicKey: pk,
		}
		reg, err := ctr.Decode()
		if err != nil {
			continue
		}
		out = append(out, *reg)
	}
	return out
ale's avatar
ale committed
60 61
}

ale's avatar
ale committed
62 63
func (f *fileUser) ToUser() *backend.User {
	return &backend.User{
ale's avatar
ale committed
64 65
		Name:              f.Name,
		Email:             f.Email,
ale's avatar
ale committed
66
		Shard:             f.Shard,
ale's avatar
ale committed
67 68 69
		EncryptedPassword: []byte(f.EncryptedPassword),
		TOTPSecret:        f.TOTPSecret,
		Groups:            f.Groups,
70
		U2FRegistrations:  f.getU2FRegistrations(),
ale's avatar
ale committed
71 72 73 74 75 76
	}
}

// Simple file-based authentication backend, list users and their
// credentials in a YAML-encoded file.
type fileBackend struct {
ale's avatar
ale committed
77 78
	files     map[string]map[string]*fileUser
	configDir string
ale's avatar
ale committed
79 80
}

ale's avatar
ale committed
81 82
func loadUsersFile(path string) (map[string]*fileUser, error) {
	var userList []*fileUser
ale's avatar
ale committed
83
	if err := backend.LoadYAML(path, &userList); err != nil {
ale's avatar
ale committed
84 85
		return nil, err
	}
ale's avatar
ale committed
86
	users := make(map[string]*fileUser)
ale's avatar
ale committed
87 88 89 90 91 92
	for _, u := range userList {
		users[u.Name] = u
	}
	return users, nil
}

ale's avatar
ale committed
93 94
// New creates a new file-based UserBackend.
func New(_ yaml.MapSlice, configDir string) (backend.UserBackend, error) {
ale's avatar
ale committed
95 96
	return &fileBackend{
		files:     make(map[string]map[string]*fileUser),
ale's avatar
ale committed
97
		configDir: configDir,
ale's avatar
ale committed
98 99 100 101 102 103 104 105 106 107
	}, nil
}

func (b *fileBackend) getUserMap(path string) (map[string]*fileUser, error) {
	m, ok := b.files[path]
	if !ok {
		var err error
		m, err = loadUsersFile(path)
		if err != nil {
			return nil, err
ale's avatar
ale committed
108
		}
ale's avatar
ale committed
109 110 111
		b.files[path] = m
	}
	return m, nil
ale's avatar
ale committed
112 113 114 115
}

func (b *fileBackend) Close() {}

ale's avatar
ale committed
116
func (b *fileBackend) NewServiceBackend(spec *backend.Spec) (backend.ServiceBackend, error) {
ale's avatar
ale committed
117
	var params fileServiceParams
ale's avatar
ale committed
118
	if err := backend.UnmarshalMapSlice(spec.Params, &params); err != nil {
ale's avatar
ale committed
119
		return nil, err
ale's avatar
ale committed
120
	}
ale's avatar
ale committed
121
	m, err := b.getUserMap(backend.ResolvePath(params.Src, b.configDir))
ale's avatar
ale committed
122 123 124 125 126 127 128
	if err != nil {
		return nil, err
	}
	return fileServiceBackend(m), nil
}

type fileServiceBackend map[string]*fileUser
ale's avatar
ale committed
129

ale's avatar
ale committed
130
func (b fileServiceBackend) GetUser(_ context.Context, name string) (*backend.User, bool) {
ale's avatar
ale committed
131
	u, ok := b[name]
ale's avatar
ale committed
132 133 134 135 136
	if !ok {
		return nil, false
	}
	return u.ToUser(), true
}