forked from jsoref/rqlite
/
credential_store.go
194 lines (168 loc) · 5.33 KB
/
credential_store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Package auth is a lightweight credential store.
// It provides functionality for loading credentials, as well as validating credentials.
package auth
import (
"encoding/json"
"io"
"os"
)
const (
// AllUsers is the username that indicates all users, even anonymous users (requests without
// any BasicAuth information).
AllUsers = "*"
// PermAll means all actions permitted.
PermAll = "all"
// PermJoin means user is permitted to join cluster.
PermJoin = "join"
// PermJoinReadOnly means user is permitted to join the cluster only as a read-only node
PermJoinReadOnly = "join-read-only"
// PermRemove means user is permitted to remove a node.
PermRemove = "remove"
// PermExecute means user can access execute endpoint.
PermExecute = "execute"
// PermQuery means user can access query endpoint
PermQuery = "query"
// PermStatus means user can retrieve node status.
PermStatus = "status"
// PermReady means user can retrieve ready status.
PermReady = "ready"
// PermBackup means user can backup node.
PermBackup = "backup"
// PermLoad means user can load a SQLite dump into a node.
PermLoad = "load"
)
// BasicAuther is the interface an object must support to return basic auth information.
type BasicAuther interface {
BasicAuth() (string, string, bool)
}
// Credential represents authentication and authorization configuration for a single user.
type Credential struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Perms []string `json:"perms,omitempty"`
}
// CredentialsStore stores authentication and authorization information for all users.
type CredentialsStore struct {
store map[string]string
perms map[string]map[string]bool
}
// NewCredentialsStore returns a new instance of a CredentialStore.
func NewCredentialsStore() *CredentialsStore {
return &CredentialsStore{
store: make(map[string]string),
perms: make(map[string]map[string]bool),
}
}
// NewCredentialsStoreFromFile returns a new instance of a CredentialStore loaded from a file.
func NewCredentialsStoreFromFile(path string) (*CredentialsStore, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
c := NewCredentialsStore()
return c, c.Load(f)
}
// Load loads credential information from a reader.
func (c *CredentialsStore) Load(r io.Reader) error {
dec := json.NewDecoder(r)
// Read open bracket
_, err := dec.Token()
if err != nil {
return err
}
var cred Credential
for dec.More() {
err := dec.Decode(&cred)
if err != nil {
return err
}
c.store[cred.Username] = cred.Password
c.perms[cred.Username] = make(map[string]bool, len(cred.Perms))
for _, p := range cred.Perms {
c.perms[cred.Username][p] = true
}
}
// Read closing bracket.
_, err = dec.Token()
if err != nil {
return err
}
return nil
}
// Check returns true if the password is correct for the given username.
func (c *CredentialsStore) Check(username, password string) bool {
pw, ok := c.store[username]
return ok && pw == password
}
// Password returns the password for the given user.
func (c *CredentialsStore) Password(username string) (string, bool) {
pw, ok := c.store[username]
return pw, ok
}
// CheckRequest returns true if b contains a valid username and password.
func (c *CredentialsStore) CheckRequest(b BasicAuther) bool {
username, password, ok := b.BasicAuth()
if !ok || !c.Check(username, password) {
return false
}
return true
}
// HasPerm returns true if username has the given perm, either directly or
// via AllUsers. It does not perform any password checking.
func (c *CredentialsStore) HasPerm(username string, perm string) bool {
if m, ok := c.perms[username]; ok {
if _, ok := m[perm]; ok {
return true
}
}
if m, ok := c.perms[AllUsers]; ok {
if _, ok := m[perm]; ok {
return true
}
}
return false
}
// HasAnyPerm returns true if username has at least one of the given perms,
// either directly, or via AllUsers. It does not perform any password checking.
func (c *CredentialsStore) HasAnyPerm(username string, perm ...string) bool {
return func(p []string) bool {
for i := range p {
if c.HasPerm(username, p[i]) {
return true
}
}
return false
}(perm)
}
// AA authenticates and checks authorization for the given username and password
// for the given perm. If the credential store is nil, then this function always
// returns true. If AllUsers have the given perm, authentication is not done.
// Only then are the credentials checked, and then the perm checked.
func (c *CredentialsStore) AA(username, password, perm string) bool {
// No credential store? Auth is not even enabled.
if c == nil {
return true
}
// Is the required perm granted to all users, including anonymous users?
if c.HasAnyPerm(AllUsers, perm, PermAll) {
return true
}
// At this point a username needs to have been supplied.
if username == "" {
return false
}
// Authenticate the user.
if !c.Check(username, password) {
return false
}
// Is the specified user authorized?
return c.HasAnyPerm(username, perm, PermAll)
}
// HasPermRequest returns true if the username returned by b has the given perm.
// It does not perform any password checking, but if there is no username
// in the request, it returns false.
func (c *CredentialsStore) HasPermRequest(b BasicAuther, perm string) bool {
username, _, ok := b.BasicAuth()
return ok && c.HasPerm(username, perm)
}