Skip to content

Commit df7ad67

Browse files
authored
Merge pull request #136 from linuxfoundation/lfxv2-2662-link-email-guard
fix: skip linking email identity that matches primary account's email
2 parents bef0d42 + dd8327c commit df7ad67

6 files changed

Lines changed: 424 additions & 101 deletions

File tree

cmd/lfx-v1-sync-helper/auth0_identity_test.go

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ type fakeAuth0Users struct {
4949
// created tracks users passed to Create.
5050
created []*management.User
5151

52+
// updateErr is returned by Update.
53+
updateErr error
54+
// updated tracks (id, user) pairs passed to Update.
55+
updated []*management.User
56+
5257
// linkErr is returned by Link.
5358
linkErr error
5459
// linked tracks (primaryID, provider, userID) tuples.
@@ -94,7 +99,11 @@ func (f *fakeAuth0Users) Create(_ context.Context, u *management.User, _ ...mana
9499
return nil
95100
}
96101

97-
func (f *fakeAuth0Users) Update(_ context.Context, _ string, _ *management.User, _ ...management.RequestOption) error {
102+
func (f *fakeAuth0Users) Update(_ context.Context, _ string, u *management.User, _ ...management.RequestOption) error {
103+
if f.updateErr != nil {
104+
return f.updateErr
105+
}
106+
f.updated = append(f.updated, u)
98107
return nil
99108
}
100109

@@ -145,10 +154,13 @@ func TestLinkEmailIdentity(t *testing.T) {
145154
cleanup := setupLinkTest(t, fake)
146155
defer cleanup()
147156

148-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
157+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
149158
if err != nil {
150159
t.Fatalf("unexpected error: %v", err)
151160
}
161+
if !linked {
162+
t.Error("expected linked=true for happy path")
163+
}
152164
if len(fake.created) != 1 {
153165
t.Fatalf("expected 1 create call, got %d", len(fake.created))
154166
}
@@ -174,10 +186,94 @@ func TestLinkEmailIdentity(t *testing.T) {
174186
cleanup := setupLinkTest(t, fake)
175187
defer cleanup()
176188

177-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
189+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
190+
if err != nil {
191+
t.Fatalf("unexpected error: %v", err)
192+
}
193+
if !linked {
194+
t.Error("expected linked=true for already-linked idempotent case")
195+
}
196+
if len(fake.created) != 0 {
197+
t.Errorf("expected no create calls, got %d", len(fake.created))
198+
}
199+
if len(fake.linked) != 0 {
200+
t.Errorf("expected no link calls, got %d", len(fake.linked))
201+
}
202+
})
203+
204+
t.Run("email matches primary account's own email is skipped", func(t *testing.T) {
205+
fake := &fakeAuth0Users{
206+
users: map[string]*management.User{
207+
"auth0|primary": {
208+
ID: auth0.String("auth0|primary"),
209+
Email: auth0.String("alt@example.com"),
210+
},
211+
},
212+
}
213+
cleanup := setupLinkTest(t, fake)
214+
defer cleanup()
215+
216+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
217+
if err != nil {
218+
t.Fatalf("unexpected error: %v", err)
219+
}
220+
if linked {
221+
t.Error("expected linked=false when email matches primary")
222+
}
223+
if len(fake.created) != 0 {
224+
t.Errorf("expected no create calls, got %d", len(fake.created))
225+
}
226+
if len(fake.linked) != 0 {
227+
t.Errorf("expected no link calls, got %d", len(fake.linked))
228+
}
229+
})
230+
231+
t.Run("email matches primary account's own email case-insensitive is skipped", func(t *testing.T) {
232+
fake := &fakeAuth0Users{
233+
users: map[string]*management.User{
234+
"auth0|primary": {
235+
ID: auth0.String("auth0|primary"),
236+
Email: auth0.String("Alt@Example.COM"),
237+
},
238+
},
239+
}
240+
cleanup := setupLinkTest(t, fake)
241+
defer cleanup()
242+
243+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
178244
if err != nil {
179245
t.Fatalf("unexpected error: %v", err)
180246
}
247+
if linked {
248+
t.Error("expected linked=false when email matches primary (case-insensitive)")
249+
}
250+
if len(fake.created) != 0 {
251+
t.Errorf("expected no create calls, got %d", len(fake.created))
252+
}
253+
if len(fake.linked) != 0 {
254+
t.Errorf("expected no link calls, got %d", len(fake.linked))
255+
}
256+
})
257+
258+
t.Run("blocked primary account is skipped", func(t *testing.T) {
259+
fake := &fakeAuth0Users{
260+
users: map[string]*management.User{
261+
"auth0|primary": {
262+
ID: auth0.String("auth0|primary"),
263+
Blocked: auth0.Bool(true),
264+
},
265+
},
266+
}
267+
cleanup := setupLinkTest(t, fake)
268+
defer cleanup()
269+
270+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
271+
if err != nil {
272+
t.Fatalf("unexpected error: %v", err)
273+
}
274+
if linked {
275+
t.Error("expected linked=false for blocked account")
276+
}
181277
if len(fake.created) != 0 {
182278
t.Errorf("expected no create calls, got %d", len(fake.created))
183279
}
@@ -200,10 +296,13 @@ func TestLinkEmailIdentity(t *testing.T) {
200296
cleanup := setupLinkTest(t, fake)
201297
defer cleanup()
202298

203-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
299+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
204300
if err != nil {
205301
t.Fatalf("unexpected error: %v", err)
206302
}
303+
if !linked {
304+
t.Error("expected linked=true for already-linked case-insensitive match")
305+
}
207306
if len(fake.created) != 0 {
208307
t.Errorf("expected no create calls for case-insensitive match")
209308
}
@@ -228,10 +327,13 @@ func TestLinkEmailIdentity(t *testing.T) {
228327
cleanup := setupLinkTest(t, fake)
229328
defer cleanup()
230329

231-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
330+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
232331
if err != nil {
233332
t.Fatalf("unexpected error: %v", err)
234333
}
334+
if !linked {
335+
t.Error("expected linked=true when email is already linked (idempotent)")
336+
}
235337
if len(fake.created) != 0 {
236338
t.Errorf("should not create when email is linked to another user")
237339
}
@@ -265,10 +367,13 @@ func TestLinkEmailIdentity(t *testing.T) {
265367
cleanup := setupLinkTest(t, fake)
266368
defer cleanup()
267369

268-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
370+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
269371
if err != nil {
270372
t.Fatalf("unexpected error: %v", err)
271373
}
374+
if !linked {
375+
t.Error("expected linked=true when only non-email identities match")
376+
}
272377
if len(fake.created) != 1 {
273378
t.Errorf("should proceed to create when only non-email identities match, got %d creates", len(fake.created))
274379
}
@@ -291,10 +396,13 @@ func TestLinkEmailIdentity(t *testing.T) {
291396
cleanup := setupLinkTest(t, fake)
292397
defer cleanup()
293398

294-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
399+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
295400
if err != nil {
296401
t.Fatalf("unexpected error: %v", err)
297402
}
403+
if !linked {
404+
t.Error("expected linked=true for case-insensitive collision (idempotent)")
405+
}
298406
if len(fake.created) != 0 {
299407
t.Errorf("case-insensitive collision should still abort the link")
300408
}
@@ -310,8 +418,7 @@ func TestLinkEmailIdentity(t *testing.T) {
310418
cleanup := setupLinkTest(t, fake)
311419
defer cleanup()
312420

313-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
314-
if err == nil {
421+
if _, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com"); err == nil {
315422
t.Fatal("expected Lucene search error to propagate")
316423
}
317424
})
@@ -331,10 +438,13 @@ func TestLinkEmailIdentity(t *testing.T) {
331438
cleanup := setupLinkTest(t, fake)
332439
defer cleanup()
333440

334-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
441+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
335442
if err != nil {
336443
t.Fatalf("unexpected error: %v", err)
337444
}
445+
if !linked {
446+
t.Error("expected linked=true for 409+resolve path")
447+
}
338448
if len(fake.linked) != 1 {
339449
t.Fatalf("expected 1 link call, got %d", len(fake.linked))
340450
}
@@ -356,8 +466,7 @@ func TestLinkEmailIdentity(t *testing.T) {
356466
cleanup := setupLinkTest(t, fake)
357467
defer cleanup()
358468

359-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
360-
if err == nil {
469+
if _, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com"); err == nil {
361470
t.Fatal("expected error when 409 but no email| user found")
362471
}
363472
})
@@ -373,10 +482,13 @@ func TestLinkEmailIdentity(t *testing.T) {
373482
cleanup := setupLinkTest(t, fake)
374483
defer cleanup()
375484

376-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
485+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
377486
if err != nil {
378487
t.Fatalf("link 409 should be treated as success, got: %v", err)
379488
}
489+
if !linked {
490+
t.Error("expected linked=true for link 409 idempotent case")
491+
}
380492
})
381493

382494
t.Run("link wrapped 409 is still idempotent (errors.As unwraps)", func(t *testing.T) {
@@ -390,10 +502,13 @@ func TestLinkEmailIdentity(t *testing.T) {
390502
cleanup := setupLinkTest(t, fake)
391503
defer cleanup()
392504

393-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
505+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
394506
if err != nil {
395507
t.Fatalf("wrapped 409 should unwrap and be treated as success, got: %v", err)
396508
}
509+
if !linked {
510+
t.Error("expected linked=true for wrapped link 409 idempotent case")
511+
}
397512
})
398513

399514
t.Run("create wrapped 409 unwraps and resolves existing user", func(t *testing.T) {
@@ -411,10 +526,13 @@ func TestLinkEmailIdentity(t *testing.T) {
411526
cleanup := setupLinkTest(t, fake)
412527
defer cleanup()
413528

414-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
529+
linked, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
415530
if err != nil {
416531
t.Fatalf("wrapped create 409 should unwrap and proceed to link, got: %v", err)
417532
}
533+
if !linked {
534+
t.Error("expected linked=true for wrapped create 409 path")
535+
}
418536
if len(fake.linked) != 1 {
419537
t.Fatalf("expected 1 link call, got %d", len(fake.linked))
420538
}
@@ -431,8 +549,7 @@ func TestLinkEmailIdentity(t *testing.T) {
431549
cleanup := setupLinkTest(t, fake)
432550
defer cleanup()
433551

434-
err := linkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
435-
if err == nil {
552+
if _, err := linkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com"); err == nil {
436553
t.Fatal("expected error to propagate")
437554
}
438555
})
@@ -453,7 +570,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
453570
cleanup := setupLinkTest(t, fake)
454571
defer cleanup()
455572

456-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
573+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
457574
if err != nil {
458575
t.Fatalf("unexpected error: %v", err)
459576
}
@@ -474,7 +591,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
474591
cleanup := setupLinkTest(t, fake)
475592
defer cleanup()
476593

477-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
594+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
478595
if err != nil {
479596
t.Fatalf("unexpected error: %v", err)
480597
}
@@ -498,7 +615,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
498615
cleanup := setupLinkTest(t, fake)
499616
defer cleanup()
500617

501-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
618+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
502619
if err != nil {
503620
t.Fatalf("unlink 404 should be treated as success, got: %v", err)
504621
}
@@ -519,7 +636,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
519636
cleanup := setupLinkTest(t, fake)
520637
defer cleanup()
521638

522-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
639+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
523640
if err != nil {
524641
t.Fatalf("wrapped 404 should unwrap and be treated as success, got: %v", err)
525642
}
@@ -540,7 +657,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
540657
cleanup := setupLinkTest(t, fake)
541658
defer cleanup()
542659

543-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
660+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
544661
if err == nil {
545662
t.Fatal("expected error to propagate")
546663
}
@@ -560,7 +677,7 @@ func TestUnlinkEmailIdentity(t *testing.T) {
560677
cleanup := setupLinkTest(t, fake)
561678
defer cleanup()
562679

563-
err := unlinkEmailIdentity(context.Background(), "auth0|primary", "alt@example.com")
680+
err := unlinkEmailIdentity(context.Background(), fake.users["auth0|primary"], "alt@example.com")
564681
if err != nil {
565682
t.Fatalf("unexpected error: %v", err)
566683
}

0 commit comments

Comments
 (0)