Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Identify accounts by their registered name

62576fa8157d431dcf2d360f3ebe481de77bda03 · Keenan Nemetz · 15d ago · parent 642e5994

Identify accounts by their registered name

Drop the internal account id and display name. Users, keys, passkeys,
invites, sessions, ACL entries, approvals, locks and CI records now all
use the registered name as the single identity.

The roster migrates in place: old id-keyed tables are rebuilt around the
name, and access-rule/lock references are rewritten through the same
mapping. Display-name editing and whoami --id are removed.

forge/identity/identity_test.go +153−132

@@ -9,7 +9,6 @@ import (
9 9 "sync"
10 10 "testing"
11 11
12 "github.com/rodent-software/vole/core/id"
13 12 "golang.org/x/crypto/ssh"
14 13 )
15 14
@@ -49,7 +48,7 @@ func mustCreate(t *testing.T, s *Store, name string) *Account {
49 48 func mustCreateBy(t *testing.T, s *Store, name, actor string) *Account {
50 49 t.Helper()
51 50 line, _ := pubLine(t)
52 a, err := s.Create(name, "", line, actor)
51 a, err := s.Create(name, line, actor)
53 52 if err != nil {
54 53 t.Fatalf("create %s: %v", name, err)
55 54 }
@@ -75,22 +74,15 @@ func TestCreateAccountWithKeyAndAddKey(t *testing.T) {
75 74 t.Parallel()
76 75 s := newStore(t)
77 76 line, wantFP := pubLine(t)
78 a, err := s.Create("keenan", "", line, "test")
77 a, err := s.Create("keenan", line, "test")
79 78 if err != nil {
80 79 t.Fatal(err)
81 80 }
82 if !id.Valid(a.ID) {
83 t.Fatalf("account id %q is not a ULID", a.ID)
84 }
85 81 if len(a.Keys) != 1 || a.Keys[0].Fingerprint != wantFP {
86 82 t.Fatalf("created account keys = %+v, want the supplied key %s", a.Keys, wantFP)
87 83 }
88 accountID, name, ok := s.Lookup(wantFP)
89 if !ok || accountID != a.ID || name != "keenan" {
90 t.Fatalf("Lookup = %q,%q,%v; want %q,keenan,true", accountID, name, ok, a.ID)
91 }
92 if got, ok := s.ResolveID("keenan"); !ok || got != a.ID {
93 t.Fatalf("ResolveID = %q,%v", got, ok)
84 if name, ok := s.Lookup(wantFP); !ok || name != "keenan" {
85 t.Fatalf("Lookup = %q,%v; want keenan,true", name, ok)
94 86 }
95 87
96 88 // add-key remains the way to attach a second credential.
@@ -111,7 +103,7 @@ func TestCreateRequiresAKeyAndDoesNotReserveTheName(t *testing.T) {
111 103 t.Parallel()
112 104 s := newStore(t)
113 105 for _, bad := range []string{"", "not an SSH key"} {
114 if _, err := s.Create("alice", "", bad, "test"); err == nil {
106 if _, err := s.Create("alice", bad, "test"); err == nil {
115 107 t.Fatalf("Create accepted key %q", bad)
116 108 }
117 109 }
@@ -119,7 +111,7 @@ func TestCreateRequiresAKeyAndDoesNotReserveTheName(t *testing.T) {
119 111 t.Fatal("a failed create left an account behind")
120 112 }
121 113 line, _ := pubLine(t)
122 a, err := s.Create("alice", "", line, "test")
114 a, err := s.Create("alice", line, "test")
123 115 if err != nil {
124 116 t.Fatalf("the failed attempts reserved the name: %v", err)
125 117 }
@@ -134,10 +126,10 @@ func TestCreateRejectsAKeyAlreadyRegistered(t *testing.T) {
134 126 t.Parallel()
135 127 s := newStore(t)
136 128 line, _ := pubLine(t)
137 if _, err := s.Create("alice", "", line, "test"); err != nil {
129 if _, err := s.Create("alice", line, "test"); err != nil {
138 130 t.Fatal(err)
139 131 }
140 if _, err := s.Create("bob", "", line, "test"); err == nil {
132 if _, err := s.Create("bob", line, "test"); err == nil {
141 133 t.Fatal("a registered key was accepted for a second account")
142 134 } else if !strings.Contains(err.Error(), "already registered") {
143 135 t.Fatalf("unclear error: %v", err)
@@ -146,7 +138,7 @@ func TestCreateRejectsAKeyAlreadyRegistered(t *testing.T) {
146 138 t.Fatal("the duplicate-key failure left an account behind")
147 139 }
148 140 fresh, _ := pubLine(t)
149 if _, err := s.Create("bob", "", fresh, "test"); err != nil {
141 if _, err := s.Create("bob", fresh, "test"); err != nil {
150 142 t.Fatalf("the duplicate-key failure reserved bob's name: %v", err)
151 143 }
152 144 }
@@ -155,7 +147,7 @@ func TestMultipleKeysPerAccount(t *testing.T) {
155 147 t.Parallel()
156 148 s := newStore(t)
157 149 line, _ := pubLine(t)
158 if _, err := s.Create("keenan", "", line, "test"); err != nil {
150 if _, err := s.Create("keenan", line, "test"); err != nil {
159 151 t.Fatal(err)
160 152 }
161 153 var fps []string
@@ -178,7 +170,7 @@ func TestMultipleKeysPerAccount(t *testing.T) {
178 170 t.Fatalf("keys = %d, want 3", got)
179 171 }
180 172 for _, fp := range fps {
181 if _, _, ok := s.Lookup(fp); !ok {
173 if _, ok := s.Lookup(fp); !ok {
182 174 t.Fatalf("key %s lost", fp)
183 175 }
184 176 }
@@ -188,11 +180,11 @@ func TestDuplicateKeyAcrossAccountsRejected(t *testing.T) {
188 180 t.Parallel()
189 181 s := newStore(t)
190 182 line, _ := pubLine(t)
191 if _, err := s.Create("alice", "", line, "test"); err != nil {
183 if _, err := s.Create("alice", line, "test"); err != nil {
192 184 t.Fatal(err)
193 185 }
194 186 bobLine, _ := pubLine(t)
195 if _, err := s.Create("bob", "", bobLine, "test"); err != nil {
187 if _, err := s.Create("bob", bobLine, "test"); err != nil {
196 188 t.Fatal(err)
197 189 }
198 190 if _, err := s.AddKey("bob", line, "test"); err == nil {
@@ -224,7 +216,7 @@ func TestOneKeyBelongsToOneAccount(t *testing.T) {
224 216 }
225 217 defer raw.Close()
226 218 if _, err := raw.Exec(`INSERT INTO user_keys(fingerprint, user_id, type, line) VALUES(?,?,'ssh-ed25519',?)`,
227 fp, bob.ID, line); err == nil {
219 fp, bob.Name, line); err == nil {
228 220 t.Fatal("a second account was allowed to claim a registered key")
229 221 }
230 222 }
@@ -240,7 +232,7 @@ func TestCorruptRosterFailsStartup(t *testing.T) {
240 232 if err != nil {
241 233 t.Fatal(err)
242 234 }
243 if _, err := raw.Exec(`UPDATE users SET name='Not A Name'`); err != nil {
235 if _, err := raw.Exec(`INSERT INTO users(name, status, created) VALUES('Not A Name', 'active', 0)`); err != nil {
244 236 t.Fatal(err)
245 237 }
246 238 raw.Close()
@@ -260,7 +252,7 @@ func TestRemoveKey(t *testing.T) {
260 252 s := newStore(t)
261 253 keepLine, keepFP := pubLine(t)
262 254 dropLine, dropFP := pubLine(t)
263 if _, err := s.Create("keenan", "", keepLine, "test"); err != nil {
255 if _, err := s.Create("keenan", keepLine, "test"); err != nil {
264 256 t.Fatal(err)
265 257 }
266 258 if _, err := s.AddKey("keenan", dropLine, "test"); err != nil {
@@ -269,10 +261,10 @@ func TestRemoveKey(t *testing.T) {
269 261 if err := s.RemoveKey("keenan", dropFP, "test"); err != nil {
270 262 t.Fatal(err)
271 263 }
272 if _, _, ok := s.Lookup(dropFP); ok {
264 if _, ok := s.Lookup(dropFP); ok {
273 265 t.Fatal("revoked key still authenticates")
274 266 }
275 if _, _, ok := s.Lookup(keepFP); !ok {
267 if _, ok := s.Lookup(keepFP); !ok {
276 268 t.Fatal("removing one key took the other with it")
277 269 }
278 270 }
@@ -283,107 +275,50 @@ func TestRetireStopsAuthenticationAndReservesTheName(t *testing.T) {
283 275 t.Parallel()
284 276 s := newStore(t)
285 277 line, fp := pubLine(t)
286 a, err := s.Create("wilbur", "", line, "test")
278 a, err := s.Create("wilbur", line, "test")
287 279 if err != nil {
288 280 t.Fatal(err)
289 281 }
290 282 if _, err := s.Retire("wilbur", "test"); err != nil {
291 283 t.Fatal(err)
292 284 }
293 if _, _, ok := s.Lookup(fp); ok {
285 if _, ok := s.Lookup(fp); ok {
294 286 t.Fatal("a retired account's key still authenticates")
295 287 }
296 if _, ok := s.ResolveID("wilbur"); ok {
297 t.Fatal("a retired name still resolves")
298 }
299 288 reissue, _ := pubLine(t)
300 if _, err := s.Create("wilbur", "", reissue, "test"); err == nil {
289 if _, err := s.Create("wilbur", reissue, "test"); err == nil {
301 290 t.Fatalf("the retired name was reissued to a new account")
302 291 } else if !strings.Contains(err.Error(), "cannot be reused") {
303 292 t.Fatalf("unclear error: %v", err)
304 293 }
305 // The account itself still exists for display and history.
306 if got, err := s.Find(a.ID); err != nil || got.Status != "retired" {
294 // The account itself still exists for history.
295 if got, err := s.Find(a.Name); err != nil || got.Status != "retired" {
307 296 t.Fatalf("Find after retire = %v, %v", got, err)
308 297 }
309 298 }
310 299
311 // TestDisplayNameIsALabelAndTheNameIsFixed: the display name is what people
312 // read, and it may change; the account name is what refs and metadata files were
313 // written with, so it may not.
314 func TestDisplayNameIsALabelAndTheNameIsFixed(t *testing.T) {
300 // TestNameIsTheOnlyIdentity: an account is its registered name. The name may
301 // not be taken by a second account, retired or not.
302 func TestNameIsTheOnlyIdentity(t *testing.T) {
315 303 t.Parallel()
316 304 s := newStore(t)
317 305 line, fp := pubLine(t)
318 a, err := s.Create("wilbur", "Wilbur Wright", line, "test")
306 a, err := s.Create("wilbur", line, "test")
319 307 if err != nil {
320 308 t.Fatal(err)
321 309 }
322 if a.Label() != "Wilbur Wright" {
323 t.Fatalf("Label = %q, want the display name", a.Label())
324 }
325 // The display name is not an identity: only the fixed name resolves.
326 if _, ok := s.ResolveID("Wilbur Wright"); ok {
327 t.Fatal("a display name resolved as an account name")
310 if a.Name != "wilbur" {
311 t.Fatalf("created account name = %q", a.Name)
328 312 }
329 if got, ok := s.ResolveID("wilbur"); !ok || got != a.ID {
330 t.Fatalf("ResolveID(wilbur) = %q,%v; want %s", got, ok, a.ID)
313 if name, ok := s.Lookup(fp); !ok || name != a.Name {
314 t.Fatalf("Lookup = %q,%v; want %q", name, ok, a.Name)
331 315 }
332
333 changed, err := s.SetDisplay("wilbur", "Wilbur", "test")
334 if err != nil {
335 t.Fatal(err)
336 }
337 if changed.ID != a.ID || changed.Name != "wilbur" {
338 t.Fatalf("SetDisplay touched the name: %+v", changed)
339 }
340 if changed.Label() != "Wilbur" {
341 t.Fatalf("Label after SetDisplay = %q", changed.Label())
342 }
343 if got, _, ok := s.Lookup(fp); !ok || got != a.ID {
344 t.Fatalf("Lookup after SetDisplay = %q,%v; want %s", got, ok, a.ID)
345 }
346
347 // Clearing the display falls back to the name.
348 cleared, err := s.SetDisplay(a.ID, "", "test")
349 if err != nil {
350 t.Fatal(err)
351 }
352 if cleared.Display != "" || cleared.Label() != "wilbur" {
353 t.Fatalf("cleared display = %q, label %q", cleared.Display, cleared.Label())
354 }
355
356 // The name cannot be taken by a second account, and the API that used to
357 // move it is gone.
358 316 reissue, _ := pubLine(t)
359 if _, err := s.Create("wilbur", "", reissue, "test"); err == nil {
317 if _, err := s.Create("wilbur", reissue, "test"); err == nil {
360 318 t.Fatal("a second account took the name")
361 319 }
362 if _, err := s.SetDisplay("nobody", "x", "test"); err == nil {
363 t.Fatal("SetDisplay accepted an unknown account")
364 }
365 }
366
367 func TestInvalidDisplaysRefused(t *testing.T) {
368 t.Parallel()
369 s := newStore(t)
370 line, _ := pubLine(t)
371 if _, err := s.Create("keenan", "", line, "test"); err != nil {
372 t.Fatal(err)
373 }
374 for _, display := range []string{"two\nlines", "tab\there", strings.Repeat("x", maxDisplayRunes+1)} {
375 if err := ValidateDisplay(display); err == nil {
376 t.Fatalf("ValidateDisplay accepted %q", display)
377 }
378 if _, err := s.SetDisplay("keenan", display, "test"); err == nil {
379 t.Fatalf("SetDisplay accepted %q", display)
380 }
381 }
382 // Spaces, capitals and non-Latin scripts are exactly what a label is for.
383 for _, display := range []string{"Keenan O'Brien", "田中 健", " padded "} {
384 if _, err := s.SetDisplay("keenan", display, "test"); err != nil {
385 t.Fatalf("SetDisplay(%q): %v", display, err)
386 }
320 if _, err := s.Find("nobody"); err == nil {
321 t.Fatal("Find accepted an unknown account")
387 322 }
388 323 }
389 324
@@ -392,7 +327,7 @@ func TestInvalidNamesRefused(t *testing.T) {
392 327 s := newStore(t)
393 328 line, _ := pubLine(t)
394 329 for _, name := range []string{"Keenan", "has space", "a", "with/slash", "trailing."} {
395 if _, err := s.Create(name, "", line, "test"); err == nil {
330 if _, err := s.Create(name, line, "test"); err == nil {
396 331 t.Fatalf("%q accepted as a name", name)
397 332 }
398 333 }
@@ -408,7 +343,7 @@ func TestConcurrentAddsAllLand(t *testing.T) {
408 343 for i := range lines {
409 344 lines[i], _ = pubLine(t)
410 345 }
411 if _, err := s.Create("keenan", "", lines[0], "test"); err != nil {
346 if _, err := s.Create("keenan", lines[0], "test"); err != nil {
412 347 t.Fatal(err)
413 348 }
414 349 var wg sync.WaitGroup
@@ -438,16 +373,13 @@ func TestAuditLogRecordsChanges(t *testing.T) {
438 373 t.Parallel()
439 374 s := newStore(t)
440 375 line, fp := pubLine(t)
441 if _, err := s.Create("keenan", "", line, "test"); err != nil {
376 if _, err := s.Create("keenan", line, "test"); err != nil {
442 377 t.Fatal(err)
443 378 }
444 379 second, secondFP := pubLine(t)
445 380 if _, err := s.AddKey("keenan", second, "test"); err != nil {
446 381 t.Fatal(err)
447 382 }
448 if _, err := s.SetDisplay("keenan", "Keenan", "test"); err != nil {
449 t.Fatal(err)
450 }
451 383 if err := s.RemoveKey("keenan", secondFP, "test"); err != nil {
452 384 t.Fatal(err)
453 385 }
@@ -456,28 +388,29 @@ func TestAuditLogRecordsChanges(t *testing.T) {
456 388 t.Fatal(err)
457 389 }
458 390 log := string(data)
459 for _, want := range []string{"identity.create", "identity.add-key", "identity.display", "identity.remove-key", fp, secondFP} {
391 for _, want := range []string{"identity.create", "identity.add-key", "identity.remove-key", fp, secondFP} {
460 392 if !strings.Contains(log, want) {
461 393 t.Fatalf("audit log missing %q:\n%s", want, log)
462 394 }
463 395 }
464 396 }
465 397
466 // TestRosterUpgradesFromTheAliasTable: rosters written before names were fixed
467 // kept a user_names alias table and no display column. Opening one must add the
468 // column, keep every account readable, and drop the alias table rather than
469 // leave a list of former names nothing resolves.
470 func TestRosterUpgradesFromTheAliasTable(t *testing.T) {
398 // TestRosterUpgradesFromTheOldSchema: rosters used to key accounts by an internal
399 // id. Opening one must rebuild every table around the registered name and drop
400 // the alias table rather than leave a list of former names nothing resolves.
401 func TestRosterUpgradesFromTheOldSchema(t *testing.T) {
471 402 t.Parallel()
472 403 path := filepath.Join(t.TempDir(), "users.db")
473 404 db, err := openDB(path)
474 405 if err != nil {
475 406 t.Fatal(err)
476 407 }
408 keyLine, keyFP := pubLine(t)
477 409 if _, err := db.Exec(`
478 410 CREATE TABLE users (
479 411 id TEXT PRIMARY KEY,
480 412 name TEXT NOT NULL UNIQUE,
413 display TEXT NOT NULL DEFAULT '',
481 414 status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','retired')),
482 415 created INTEGER NOT NULL
483 416 );
@@ -485,9 +418,77 @@ CREATE TABLE user_names (
485 418 name TEXT PRIMARY KEY,
486 419 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE
487 420 );
488 INSERT INTO users(id, name, status, created) VALUES('01M5JR2JK29KKMAA1T9MDNTPBP', 'wilbur', 'active', 0);
421 CREATE TABLE user_keys (
422 fingerprint TEXT PRIMARY KEY,
423 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
424 type TEXT NOT NULL,
425 line TEXT NOT NULL,
426 comment TEXT NOT NULL DEFAULT ''
427 );
428 CREATE TABLE instance_admins (
429 user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE
430 );
431 CREATE TABLE user_credentials (
432 credential_id TEXT PRIMARY KEY,
433 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
434 public_key BLOB NOT NULL,
435 attestation_type TEXT NOT NULL DEFAULT '',
436 transports TEXT NOT NULL DEFAULT '[]',
437 flags INTEGER NOT NULL DEFAULT 0,
438 aaguid BLOB,
439 sign_count INTEGER NOT NULL DEFAULT 0,
440 clone_warning INTEGER NOT NULL DEFAULT 0,
441 attachment TEXT NOT NULL DEFAULT '',
442 label TEXT NOT NULL DEFAULT '',
443 created INTEGER NOT NULL,
444 last_used INTEGER NOT NULL DEFAULT 0
445 );
446 CREATE TABLE user_invites (
447 token_hash TEXT PRIMARY KEY,
448 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
449 created INTEGER NOT NULL,
450 expires INTEGER NOT NULL,
451 used_at INTEGER,
452 created_by TEXT NOT NULL DEFAULT ''
453 );
454 CREATE TABLE sessions (
455 token_hash TEXT PRIMARY KEY,
456 account_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
457 credential_id TEXT REFERENCES user_credentials(credential_id) ON DELETE CASCADE,
458 created INTEGER NOT NULL,
459 last_seen INTEGER NOT NULL,
460 expires INTEGER NOT NULL,
461 user_agent TEXT NOT NULL DEFAULT ''
462 );
463 INSERT INTO users(id, name, display, status, created) VALUES('01M5JR2JK29KKMAA1T9MDNTPBP', 'wilbur', 'Wilbur Wright', 'active', 0);
489 464 INSERT INTO user_names(name, user_id) VALUES('wilbur', '01M5JR2JK29KKMAA1T9MDNTPBP');
490 INSERT INTO user_names(name, user_id) VALUES('robert', '01M5JR2JK29KKMAA1T9MDNTPBP');`); err != nil {
465 INSERT INTO user_names(name, user_id) VALUES('robert', '01M5JR2JK29KKMAA1T9MDNTPBP');
466 INSERT INTO instance_admins(user_id) VALUES('01M5JR2JK29KKMAA1T9MDNTPBP');
467 INSERT INTO user_credentials(credential_id, user_id, public_key, created) VALUES('cred-1', '01M5JR2JK29KKMAA1T9MDNTPBP', x'00', 0);
468 INSERT INTO user_invites(token_hash, user_id, created, expires) VALUES('` + hashToken("tok") + `', '01M5JR2JK29KKMAA1T9MDNTPBP', 0, 9999999999);
469 INSERT INTO sessions(token_hash, account_id, credential_id, created, last_seen, expires) VALUES('sess-1', '01M5JR2JK29KKMAA1T9MDNTPBP', 'cred-1', 0, 0, 9999999999);
470 CREATE TABLE repo_acl (
471 repo_id TEXT PRIMARY KEY,
472 acl TEXT NOT NULL,
473 updated INTEGER NOT NULL,
474 updated_by TEXT NOT NULL DEFAULT ''
475 );
476 INSERT INTO repo_acl(repo_id, acl, updated, updated_by) VALUES('repo-1',
477 '{"version":1,"default":"none","entries":{"01M5JR2JK29KKMAA1T9MDNTPBP":"admin"},"runners":["01M5JR2JK29KKMAA1T9MDNTPBP"]}',
478 0, '01M5JR2JK29KKMAA1T9MDNTPBP');
479 CREATE TABLE lfs_locks (
480 id TEXT PRIMARY KEY,
481 repo_id TEXT NOT NULL,
482 path TEXT NOT NULL,
483 ref TEXT NOT NULL DEFAULT '',
484 owner TEXT NOT NULL,
485 locked_at INTEGER NOT NULL
486 );
487 INSERT INTO lfs_locks(id, repo_id, path, owner, locked_at) VALUES('lock-1', 'repo-1', 'art.psd', '01M5JR2JK29KKMAA1T9MDNTPBP', 0);`); err != nil {
488 t.Fatal(err)
489 }
490 if _, err := db.Exec(`INSERT INTO user_keys(fingerprint, user_id, type, line) VALUES(?,?,?,?)`,
491 keyFP, "01M5JR2JK29KKMAA1T9MDNTPBP", "ssh-ed25519", keyLine); err != nil {
491 492 t.Fatal(err)
492 493 }
493 494 if err := db.Close(); err != nil {
@@ -503,8 +504,31 @@ INSERT INTO user_names(name, user_id) VALUES('robert', '01M5JR2JK29KKMAA1T9MDNTP
503 504 if err != nil {
504 505 t.Fatal(err)
505 506 }
506 if a.Display != "" || a.Label() != "wilbur" {
507 t.Fatalf("upgraded account display = %q label = %q", a.Display, a.Label())
507 if a.Name != "wilbur" || !a.Admin || len(a.Keys) != 1 {
508 t.Fatalf("upgraded account = %+v", a)
509 }
510 if name, ok := s.Lookup(a.Keys[0].Fingerprint); !ok || name != "wilbur" {
511 t.Fatalf("upgraded key resolves to %q,%v", name, ok)
512 }
513 if creds, err := s.Credentials("wilbur"); err != nil || len(creds) != 1 {
514 t.Fatalf("upgraded credentials = %v, err = %v", creds, err)
515 }
516 if inviter, err := s.InviteAccount("tok"); err != nil || inviter.Name != "wilbur" {
517 t.Fatalf("upgraded invite = %v, err = %v", inviter, err)
518 }
519 var sessions int
520 if err := s.db.QueryRow(`SELECT count(*) FROM sessions WHERE user_name='wilbur'`).Scan(&sessions); err != nil || sessions != 1 {
521 t.Fatalf("upgraded sessions = %d, err = %v", sessions, err)
522 }
523 var acl, aclUpdatedBy, lockOwner string
524 if err := s.db.QueryRow(`SELECT acl, updated_by FROM repo_acl WHERE repo_id='repo-1'`).Scan(&acl, &aclUpdatedBy); err != nil {
525 t.Fatal(err)
526 }
527 if !strings.Contains(acl, `"wilbur"`) || strings.Contains(acl, "01M5JR2JK29") || aclUpdatedBy != "wilbur" {
528 t.Fatalf("upgraded ACL = %s (updated_by %q)", acl, aclUpdatedBy)
529 }
530 if err := s.db.QueryRow(`SELECT owner FROM lfs_locks WHERE id='lock-1'`).Scan(&lockOwner); err != nil || lockOwner != "wilbur" {
531 t.Fatalf("upgraded lock owner = %q, err = %v", lockOwner, err)
508 532 }
509 533 // The former name was only ever an alias, so it no longer resolves.
510 534 if _, err := s.Find("robert"); err == nil {
@@ -526,7 +550,7 @@ func TestRosterIsOneDatabaseFile(t *testing.T) {
526 550 t.Parallel()
527 551 s := newStore(t)
528 552 line, fp := pubLine(t)
529 a, err := s.Create("keenan", "Keenan O'Brien", line, "test")
553 a, err := s.Create("keenan", line, "test")
530 554 if err != nil {
531 555 t.Fatal(err)
532 556 }
@@ -540,15 +564,12 @@ func TestRosterIsOneDatabaseFile(t *testing.T) {
540 564 t.Fatal(err)
541 565 }
542 566 defer other.Close()
543 if got, ok := other.ResolveID("keenan"); !ok || got != a.ID {
544 t.Fatalf("copied roster resolves keenan to %q,%v; want %s", got, ok, a.ID)
567 if got, err := other.Find("keenan"); err != nil || got.Name != a.Name {
568 t.Fatalf("copied roster does not resolve keenan: %v", err)
545 569 }
546 if _, _, ok := other.Lookup(fp); !ok {
570 if name, ok := other.Lookup(fp); !ok || name != a.Name {
547 571 t.Fatal("copied roster lost the key")
548 572 }
549 if got := other.Label(a.ID); got != "Keenan O'Brien" {
550 t.Fatalf("copied roster display = %q", got)
551 }
552 573
553 574 for _, absent := range []string{"system.git", "trusted_keys"} {
554 575 if _, err := os.Stat(filepath.Join(filepath.Dir(s.Path()), absent)); err == nil {
@@ -561,13 +582,13 @@ func TestInstanceAdminsAreExplicitAndAudited(t *testing.T) {
561 582 t.Parallel()
562 583 s := newStore(t)
563 584 alice := mustCreateBy(t, s, "alice", "operator")
564 if s.IsAdmin(alice.ID) {
585 if s.IsAdmin(alice.Name) {
565 586 t.Fatal("a new account is an instance admin")
566 587 }
567 588 if _, err := s.SetAdmin("alice", true, "operator"); err != nil {
568 589 t.Fatal(err)
569 590 }
570 if !s.IsAdmin(alice.ID) {
591 if !s.IsAdmin(alice.Name) {
571 592 t.Fatal("alice was not made an instance admin")
572 593 }
573 594 if a, err := s.Find("alice"); err != nil || !a.Admin {
@@ -581,7 +602,7 @@ func TestInstanceAdminsAreExplicitAndAudited(t *testing.T) {
581 602 t.Fatalf("admin audit line does not name the actor:\n%s", log)
582 603 }
583 604 bobKey, _ := pubLine(t)
584 if _, err := s.Create("bob", "", bobKey, "operator"); err != nil {
605 if _, err := s.Create("bob", bobKey, "operator"); err != nil {
585 606 t.Fatal(err)
586 607 }
587 608 if _, err := s.SetAdmin("bob", true, "operator"); err != nil {
@@ -590,7 +611,7 @@ func TestInstanceAdminsAreExplicitAndAudited(t *testing.T) {
590 611 if _, err := s.SetAdmin("alice", false, "operator"); err != nil {
591 612 t.Fatal(err)
592 613 }
593 if s.IsAdmin(alice.ID) {
614 if s.IsAdmin(alice.Name) {
594 615 t.Fatal("admin grant survived revocation")
595 616 }
596 617 }
@@ -600,22 +621,22 @@ func TestLastInstanceAdminCannotBeRemoved(t *testing.T) {
600 621 s := newStore(t)
601 622 alice := mustCreateBy(t, s, "alice", "operator")
602 623 bob := mustCreateBy(t, s, "bob", "operator")
603 if _, err := s.SetAdmin(alice.ID, true, "operator"); err != nil {
624 if _, err := s.SetAdmin(alice.Name, true, "operator"); err != nil {
604 625 t.Fatal(err)
605 626 }
606 if _, err := s.SetAdmin(alice.ID, false, "operator"); err == nil {
627 if _, err := s.SetAdmin(alice.Name, false, "operator"); err == nil {
607 628 t.Fatal("revoked the last instance admin")
608 629 }
609 if _, err := s.Retire(alice.ID, "operator"); err == nil {
630 if _, err := s.Retire(alice.Name, "operator"); err == nil {
610 631 t.Fatal("retired the last instance admin")
611 632 }
612 if _, err := s.SetAdmin(bob.ID, true, "operator"); err != nil {
633 if _, err := s.SetAdmin(bob.Name, true, "operator"); err != nil {
613 634 t.Fatal(err)
614 635 }
615 if _, err := s.SetAdmin(alice.ID, false, "operator"); err != nil {
636 if _, err := s.SetAdmin(alice.Name, false, "operator"); err != nil {
616 637 t.Fatalf("could not revoke an admin when another remained: %v", err)
617 638 }
618 if _, err := s.Retire(bob.ID, "operator"); err == nil {
639 if _, err := s.Retire(bob.Name, "operator"); err == nil {
619 640 t.Fatal("retired the last instance admin after revoking the other")
620 641 }
621 642 }