auth
2015-06-29
Parent:8ecb60d29b0d
auth/profile_test.go
Create Docker image for authd. Create a Dockerfile for authd, which will wrap the compiled Go binary up into a tiny little Docker image. Create an authd/build-docker.sh script that will build the statically-linked binary in a Docker container, so the authd Docker image can use it. We had to include ca-certificates.crt in the Dockerfile, as well, so we could communicate over SSL with things. A wrapper.sh file is included that will pull the JWT_SECRET environment variable out of a kubernetes secrets file, which is a handy wrapper to have. Finally, we added the authd/docker-authd binary to the .hgignore.
1 package auth
3 import (
4 "fmt"
5 "os"
6 "testing"
7 "time"
9 "code.secondbit.org/uuid.hg"
10 )
12 const (
13 profileChangeName = 1 << iota
14 profileChangePassphrase
15 profileChangeIterations
16 profileChangeSalt
17 profileChangePassphraseScheme
18 profileChangeCompromised
19 profileChangeLockedUntil
20 profileChangePassphraseReset
21 profileChangePassphraseResetCreated
22 profileChangeLastSeen
23 )
25 func init() {
26 if os.Getenv("PG_TEST_DB") != "" {
27 p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
28 if err != nil {
29 panic(err)
30 }
31 profileStores = append(profileStores, &p)
32 }
33 }
35 var profileStores = []profileStore{NewMemstore()}
37 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
38 if !profile1.ID.Equal(profile2.ID) {
39 return false, "ID", profile1.ID, profile2.ID
40 }
41 if profile1.Name != profile2.Name {
42 return false, "name", profile1.Name, profile2.Name
43 }
44 if profile1.Passphrase != profile2.Passphrase {
45 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
46 }
47 if profile1.Iterations != profile2.Iterations {
48 return false, "iterations", profile1.Iterations, profile2.Iterations
49 }
50 if profile1.Salt != profile2.Salt {
51 return false, "salt", profile1.Salt, profile2.Salt
52 }
53 if profile1.PassphraseScheme != profile2.PassphraseScheme {
54 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
55 }
56 if profile1.Compromised != profile2.Compromised {
57 return false, "compromised", profile1.Compromised, profile2.Compromised
58 }
59 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
60 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
61 }
62 if profile1.PassphraseReset != profile2.PassphraseReset {
63 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
64 }
65 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
66 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
67 }
68 if !profile1.Created.Equal(profile2.Created) {
69 return false, "created", profile1.Created, profile2.Created
70 }
71 if !profile1.LastSeen.Equal(profile2.LastSeen) {
72 return false, "last seen", profile1.LastSeen, profile2.LastSeen
73 }
74 return true, "", nil, nil
75 }
77 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
78 if login1.Type != login2.Type {
79 return false, "Type", login1.Type, login2.Type
80 }
81 if login1.Value != login2.Value {
82 return false, "Value", login1.Value, login2.Value
83 }
84 if !login1.ProfileID.Equal(login2.ProfileID) {
85 return false, "ProfileID", login1.ProfileID, login2.ProfileID
86 }
87 if !login1.Created.Equal(login2.Created) {
88 return false, "Created", login1.Created, login2.Created
89 }
90 if !login1.LastUsed.Equal(login2.LastUsed) {
91 return false, "LastUsed", login1.LastUsed, login2.LastUsed
92 }
93 if login1.Verification != login2.Verification {
94 return false, "Verification", login1.Verification, login2.Verification
95 }
96 if login1.Verified != login2.Verified {
97 return false, "Verified", login1.Verified, login2.Verified
98 }
99 return true, "", nil, nil
100 }
102 func TestProfileStoreSuccess(t *testing.T) {
103 t.Parallel()
104 profile := Profile{
105 ID: uuid.NewID(),
106 Name: "name",
107 Passphrase: "passphrase",
108 Iterations: 10000,
109 Salt: "salt",
110 PassphraseScheme: 1,
111 Compromised: false,
112 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
113 PassphraseReset: "passphrase reset",
114 PassphraseResetCreated: time.Now().Round(time.Millisecond),
115 Created: time.Now().Round(time.Millisecond),
116 LastSeen: time.Now().Round(time.Millisecond),
117 }
118 for _, store := range profileStores {
119 context := Context{profiles: store}
120 err := context.SaveProfile(profile)
121 if err != nil {
122 t.Errorf("Error saving profile to %T: %s", store, err)
123 }
124 err = context.SaveProfile(profile)
125 if err != ErrProfileAlreadyExists {
126 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
127 }
128 retrieved, err := context.GetProfileByID(profile.ID)
129 if err != nil {
130 t.Errorf("Error retrieving profile from %T: %s", store, err)
131 }
132 match, field, expectation, result := compareProfiles(profile, retrieved)
133 if !match {
134 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
135 }
136 err = context.DeleteProfile(profile.ID)
137 if err != nil {
138 t.Errorf("Error removing profile from %T: %s", store, err)
139 }
140 retrieved, err = context.GetProfileByID(profile.ID)
141 if err != ErrProfileNotFound {
142 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
143 }
144 }
145 }
147 func TestProfileUpdates(t *testing.T) {
148 t.Parallel()
149 variations := 1 << 10
150 profile := Profile{
151 ID: uuid.NewID(),
152 Name: "name",
153 Passphrase: "passphrase",
154 Iterations: 10000,
155 Salt: "salt",
156 PassphraseScheme: 1,
157 Compromised: false,
158 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
159 PassphraseReset: "passphrase reset",
160 PassphraseResetCreated: time.Now().Round(time.Millisecond),
161 Created: time.Now().Round(time.Millisecond),
162 LastSeen: time.Now().Round(time.Millisecond),
163 }
164 for i := 0; i < variations; i++ {
165 var name, passphrase, salt, passphraseReset string
166 var iterations int
167 var lockedUntil, passphraseResetCreated, lastSeen time.Time
168 var passphraseScheme int
169 var compromised bool
171 profile.ID = uuid.NewID()
172 change := ProfileChange{}
173 expectation := profile
174 result := profile
175 if i&profileChangeName != 0 {
176 name = fmt.Sprintf("name-%d", i)
177 change.Name = &name
178 expectation.Name = name
179 }
180 if i&profileChangePassphrase != 0 {
181 passphrase = fmt.Sprintf("passphrase-%d", i)
182 change.Passphrase = &passphrase
183 expectation.Passphrase = passphrase
184 }
185 if i&profileChangeIterations != 0 {
186 iterations = i
187 change.Iterations = &iterations
188 expectation.Iterations = iterations
189 }
190 if i&profileChangeSalt != 0 {
191 salt = fmt.Sprintf("salt-%d", i)
192 change.Salt = &salt
193 expectation.Salt = salt
194 }
195 if i&profileChangePassphraseScheme != 0 {
196 passphraseScheme = i
197 change.PassphraseScheme = &passphraseScheme
198 expectation.PassphraseScheme = passphraseScheme
199 }
200 if i&profileChangeCompromised != 0 {
201 compromised = i%2 != 0
202 change.Compromised = &compromised
203 expectation.Compromised = compromised
204 }
205 if i&profileChangeLockedUntil != 0 {
206 lockedUntil = time.Now().Round(time.Millisecond)
207 change.LockedUntil = &lockedUntil
208 expectation.LockedUntil = lockedUntil
209 }
210 if i&profileChangePassphraseReset != 0 {
211 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
212 change.PassphraseReset = &passphraseReset
213 expectation.PassphraseReset = passphraseReset
214 }
215 if i&profileChangePassphraseResetCreated != 0 {
216 passphraseResetCreated = time.Now().Round(time.Millisecond)
217 change.PassphraseResetCreated = &passphraseResetCreated
218 expectation.PassphraseResetCreated = passphraseResetCreated
219 }
220 if i&profileChangeLastSeen != 0 {
221 lastSeen = time.Now().Round(time.Millisecond)
222 change.LastSeen = &lastSeen
223 expectation.LastSeen = lastSeen
224 }
225 result.ApplyChange(change)
226 match, field, expected, got := compareProfiles(expectation, result)
227 if !match {
228 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
229 }
230 for _, store := range profileStores {
231 context := Context{profiles: store}
232 err := context.SaveProfile(profile)
233 if err != nil {
234 t.Errorf("Error saving profile in %T: %s", store, err)
235 }
236 err = context.UpdateProfile(profile.ID, change)
237 if err != nil {
238 t.Errorf("Error updating profile in %T: %s", store, err)
239 }
240 retrieved, err := context.GetProfileByID(profile.ID)
241 if err != nil {
242 t.Errorf("Error getting profile from %T: %s", store, err)
243 }
244 match, field, expected, got = compareProfiles(expectation, retrieved)
245 if !match {
246 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
247 }
248 }
249 }
250 }
252 func TestProfilesUpdates(t *testing.T) {
253 profile1 := Profile{
254 ID: uuid.NewID(),
255 }
256 profile2 := Profile{
257 ID: uuid.NewID(),
258 }
259 profile3 := Profile{
260 ID: uuid.NewID(),
261 }
262 truth := true
263 change := BulkProfileChange{
264 Compromised: &truth,
265 }
266 for _, store := range profileStores {
267 context := Context{profiles: store}
268 err := context.SaveProfile(profile1)
269 if err != nil {
270 t.Errorf("Error saving profile in %T: %s", store, err)
271 }
272 err = context.SaveProfile(profile2)
273 if err != nil {
274 t.Errorf("Error saving profile in %T: %s", store, err)
275 }
276 err = context.SaveProfile(profile3)
277 if err != nil {
278 t.Errorf("Error saving profile in %T: %s", store, err)
279 }
280 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
281 if err != nil {
282 t.Errorf("Error updating profile in %T: %s", store, err)
283 }
284 profile1.Compromised = truth
285 profile3.Compromised = truth
286 retrieved, err := context.GetProfileByID(profile1.ID)
287 if err != nil {
288 t.Errorf("Error getting profile from %T: %s", store, err)
289 }
290 match, field, expected, got := compareProfiles(profile1, retrieved)
291 if !match {
292 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
293 }
294 retrieved, err = context.GetProfileByID(profile2.ID)
295 if err != nil {
296 t.Errorf("Error getting profile from %T: %s", store, err)
297 }
298 match, field, expected, got = compareProfiles(profile2, retrieved)
299 if !match {
300 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
301 }
302 retrieved, err = context.GetProfileByID(profile3.ID)
303 if err != nil {
304 t.Errorf("Error getting profile from %T: %s", store, err)
305 }
306 match, field, expected, got = compareProfiles(profile3, retrieved)
307 if !match {
308 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
309 }
310 }
311 }
313 func TestProfileStoreLoginSuccess(t *testing.T) {
314 t.Parallel()
315 login := Login{
316 Type: "type",
317 Value: "value",
318 ProfileID: uuid.NewID(),
319 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond),
320 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
321 }
322 for _, store := range profileStores {
323 context := Context{profiles: store}
324 err := context.AddLogin(login)
325 if err != nil {
326 t.Errorf("Error adding login to %T: %s", store, err)
327 }
328 err = context.AddLogin(login)
329 if err != ErrLoginAlreadyExists {
330 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
331 }
332 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
333 if err != nil {
334 t.Errorf("Error retrieving logins from %T: %s", store, err)
335 }
336 if len(retrieved) != 1 {
337 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
338 }
339 match, field, expectation, result := compareLogins(login, retrieved[0])
340 if !match {
341 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
342 }
343 lastUsed := time.Now().Round(time.Millisecond)
344 err = context.RecordLoginUse(login.Value, lastUsed)
345 if err != nil {
346 t.Errorf("Error recording use of login to %T: %s", store, err)
347 }
348 login.LastUsed = lastUsed
349 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
350 if err != nil {
351 t.Errorf("Error retrieving logins from %T: %s", store, err)
352 }
353 if len(retrieved) != 1 {
354 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
355 }
356 match, field, expectation, result = compareLogins(login, retrieved[0])
357 if !match {
358 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
359 }
360 err = context.RemoveLogin(login.Value, login.ProfileID)
361 if err != nil {
362 t.Errorf("Error removing login from %T: %s", store, err)
363 }
364 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
365 if len(retrieved) != 0 {
366 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
367 }
368 err = context.RemoveLogin(login.Value, login.ProfileID)
369 if err != ErrLoginNotFound {
370 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
371 }
372 }
373 }
375 func TestProfileStoreLoginRetrieval(t *testing.T) {
376 t.Parallel()
377 profile := Profile{
378 ID: uuid.NewID(),
379 Name: "name",
380 Passphrase: "passphrase",
381 Iterations: 10000,
382 Salt: "salt",
383 PassphraseScheme: 1,
384 Compromised: false,
385 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
386 PassphraseReset: "passphrase reset",
387 PassphraseResetCreated: time.Now().Round(time.Millisecond),
388 Created: time.Now().Round(time.Millisecond),
389 LastSeen: time.Now().Round(time.Millisecond),
390 }
391 login := Login{
392 Type: "type",
393 Value: "value",
394 ProfileID: profile.ID,
395 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond),
396 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
397 }
398 for _, store := range profileStores {
399 context := Context{profiles: store}
400 err := context.SaveProfile(profile)
401 if err != nil {
402 t.Errorf("Error saving profile in %T: %s", store, err)
403 }
404 err = context.AddLogin(login)
405 if err != nil {
406 t.Errorf("Error storing login in %T: %s", store, err)
407 }
408 retrieved, err := context.GetProfileByLogin(login.Value)
409 if err != nil {
410 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
411 }
412 match, field, expectation, result := compareProfiles(profile, retrieved)
413 if !match {
414 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
415 }
416 }
417 }
419 func TestProfileChangeValidation(t *testing.T) {
420 t.Parallel()
421 passphraseScheme := 1
422 passphraseReset := "reset"
423 salt := "salt"
424 iterations := 100
425 emptyName := ""
426 enteredName := "Paddy"
427 okPassphrase := "this is a decent passphrase"
428 compromised := true
429 lockedUntil := time.Now().Round(time.Millisecond)
430 resetCreated := time.Now().Round(time.Millisecond)
431 lastSeen := time.Now().Round(time.Millisecond)
432 changes := map[*ProfileChange]error{
433 &ProfileChange{}: ErrEmptyChange,
434 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
435 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
436 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
437 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
438 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
439 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
440 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
441 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
442 &ProfileChange{Passphrase: &okPassphrase}: nil,
443 &ProfileChange{Name: &emptyName}: nil,
444 &ProfileChange{Name: &enteredName}: nil,
445 &ProfileChange{Compromised: &compromised}: nil,
446 &ProfileChange{LockedUntil: &lockedUntil}: nil,
447 &ProfileChange{LastSeen: &lastSeen}: nil,
448 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
449 }
450 for change, expectedErr := range changes {
451 if err := change.Validate(); err != expectedErr {
452 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
453 }
454 }
455 }
457 func TestBulkProfileChangeValidation(t *testing.T) {
458 t.Parallel()
459 compromised := true
460 changes := map[*BulkProfileChange]error{
461 &BulkProfileChange{}: ErrEmptyChange,
462 &BulkProfileChange{Compromised: &compromised}: nil,
463 }
464 for change, expectedErr := range changes {
465 if err := change.Validate(); err != expectedErr {
466 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
467 }
468 }
469 }
471 // BUG(paddy): We need to test the validateNewProfileRequest helper.
472 // BUG(paddy): We need to test the CreateProfileHandler.
473 // BUG(paddy): We need to test that deleting works as we expect.