github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/phone_number_test.go (about)

     1  package systests
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/keybase/client/go/client"
     9  	"github.com/keybase/client/go/emails"
    10  	"github.com/keybase/client/go/kbtest"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestImpTeamWithPhoneNumber(t *testing.T) {
    17  	tt := newTeamTester(t)
    18  	defer tt.cleanup()
    19  
    20  	ann := tt.addUser("ann")
    21  
    22  	phone := kbtest.GenerateTestPhoneNumber()
    23  	impteamName := fmt.Sprintf("%s@phone,%s", phone, ann.username)
    24  	teamID, err := ann.lookupImplicitTeam(true /* create */, impteamName, false /* public */)
    25  	require.NoError(t, err)
    26  
    27  	t.Logf("Created team %s -> %s", impteamName, teamID)
    28  
    29  	teamObj := ann.loadTeamByID(teamID, true /* admin */)
    30  	require.Equal(t, 1, teamObj.NumActiveInvites())
    31  	var invite keybase1.TeamInvite
    32  	for _, invite = range teamObj.GetActiveAndObsoleteInvites() {
    33  		// Get first invite to local var
    34  	}
    35  	require.EqualValues(t, phone, invite.Name)
    36  	invCat, err := invite.Type.C()
    37  	require.NoError(t, err)
    38  	require.Equal(t, keybase1.TeamInviteCategory_PHONE, invCat)
    39  
    40  	name, err := teamObj.ImplicitTeamDisplayName(context.Background())
    41  	require.NoError(t, err)
    42  	require.Len(t, name.Writers.KeybaseUsers, 1)
    43  	require.Len(t, name.Writers.UnresolvedUsers, 1)
    44  	require.Equal(t, impteamName, name.String())
    45  }
    46  
    47  func TestResolvePhoneToUser(t *testing.T) {
    48  	tt := newTeamTester(t)
    49  	defer tt.cleanup()
    50  
    51  	ann := tt.addUser("ann")
    52  	bob := tt.addUser("bob")
    53  	tt.logUserNames()
    54  
    55  	phone := kbtest.GenerateTestPhoneNumber()
    56  
    57  	assertion := fmt.Sprintf("%s@phone", phone)
    58  	for _, u := range tt.users {
    59  		_, res, err := u.tc.G.Resolver.ResolveUser(u.MetaContext(), assertion)
    60  		require.Error(t, err)
    61  		require.IsType(t, libkb.ResolutionError{}, err)
    62  		require.Contains(t, err.Error(), assertion)
    63  		require.Contains(t, err.Error(), "No resolution found")
    64  		require.Empty(t, res.GetUID())
    65  		require.Empty(t, res.GetUsername())
    66  	}
    67  
    68  	cli := &client.CmdAddPhoneNumber{
    69  		Contextified: libkb.NewContextified(bob.tc.G),
    70  		PhoneNumber:  "+" + phone,
    71  	}
    72  	err := cli.Run()
    73  	require.NoError(t, err)
    74  
    75  	code, err := kbtest.GetPhoneVerificationCode(bob.MetaContext(), keybase1.PhoneNumber("+"+phone))
    76  	require.NoError(t, err)
    77  
    78  	cli2 := &client.CmdVerifyPhoneNumber{
    79  		Contextified: libkb.NewContextified(bob.tc.G),
    80  		PhoneNumber:  "+" + phone,
    81  		Code:         code,
    82  	}
    83  	err = cli2.Run()
    84  	require.NoError(t, err)
    85  
    86  	cli3 := &client.CmdSetVisibilityPhoneNumber{
    87  		Contextified: libkb.NewContextified(bob.tc.G),
    88  		PhoneNumber:  "+" + phone,
    89  		Visibility:   keybase1.IdentityVisibility_PUBLIC,
    90  	}
    91  	err = cli3.Run()
    92  	require.NoError(t, err)
    93  
    94  	for _, u := range tt.users {
    95  		usr, res, err := u.tc.G.Resolver.ResolveUser(u.MetaContext(), assertion)
    96  		require.NoError(t, err)
    97  		require.Equal(t, bob.username, res.GetUsername())
    98  		require.Equal(t, bob.username, usr.Username)
    99  		require.Equal(t, bob.uid, res.GetUID())
   100  		require.Equal(t, bob.uid, usr.Uid)
   101  		require.True(t, res.IsServerTrust())
   102  	}
   103  
   104  	// Try to create impteam with now-resolvable phone number.
   105  	impteamName := fmt.Sprintf("%s,%s", assertion, ann.username)
   106  	lookupRes, err := ann.lookupImplicitTeam2(true /* create */, impteamName, false /* public */)
   107  	require.NoError(t, err)
   108  	require.Equal(t, fmt.Sprintf("%s,%s", ann.username, bob.username), lookupRes.DisplayName.String())
   109  }
   110  
   111  func TestServerTrustResolveInvalidInput(t *testing.T) {
   112  	tt := newTeamTester(t)
   113  	defer tt.cleanup()
   114  
   115  	ann := tt.addUser("ann")
   116  
   117  	checkErr := func(err error) {
   118  		require.Error(t, err)
   119  		require.IsType(t, libkb.ResolutionError{}, err)
   120  		resErr := err.(libkb.ResolutionError)
   121  		require.Equal(t, libkb.ResolutionErrorInvalidInput, resErr.Kind)
   122  	}
   123  
   124  	_, _, err := ann.tc.G.Resolver.ResolveUser(ann.MetaContext(), "111@phone")
   125  	checkErr(err)
   126  	_, _, err = ann.tc.G.Resolver.ResolveUser(ann.MetaContext(), "[notvalid@x]@email")
   127  	checkErr(err)
   128  }
   129  
   130  type mockPhoneNotification struct {
   131  	list        []keybase1.UserPhoneNumber
   132  	category    string
   133  	phoneNumber keybase1.PhoneNumber
   134  }
   135  
   136  type mockPhoneListener struct {
   137  	libkb.NoopNotifyListener
   138  	notifications []mockPhoneNotification
   139  }
   140  
   141  var _ libkb.NotifyListener = (*mockPhoneListener)(nil)
   142  
   143  func (n *mockPhoneListener) PhoneNumbersChanged(list []keybase1.UserPhoneNumber, category string, phoneNumber keybase1.PhoneNumber) {
   144  	n.notifications = append(n.notifications, mockPhoneNotification{
   145  		list, category, phoneNumber,
   146  	})
   147  }
   148  
   149  func (n *mockPhoneListener) DrainPhoneNumberNotifications() (ret []mockPhoneNotification) {
   150  	ret = n.notifications
   151  	n.notifications = nil
   152  	return ret
   153  }
   154  
   155  func setupUserWithMockPhoneListener(user *userPlusDevice) *mockPhoneListener {
   156  	userListener := &mockPhoneListener{}
   157  	user.tc.G.SetService()
   158  	user.tc.G.NotifyRouter.AddListener(userListener)
   159  	return userListener
   160  }
   161  
   162  func TestPhoneNumberNotifications(t *testing.T) {
   163  	tt := newTeamTester(t)
   164  	defer tt.cleanup()
   165  
   166  	ann := tt.addUser("ann")
   167  	annListener := setupUserWithMockPhoneListener(ann)
   168  	bob := tt.addUser("bob")
   169  	bobListener := setupUserWithMockPhoneListener(bob)
   170  	tt.logUserNames()
   171  
   172  	phone := "+" + kbtest.GenerateTestPhoneNumber()
   173  	kbPhone := keybase1.PhoneNumber(phone)
   174  	cli := &client.CmdAddPhoneNumber{
   175  		Contextified: libkb.NewContextified(ann.tc.G),
   176  		PhoneNumber:  phone,
   177  	}
   178  	err := cli.Run()
   179  	require.NoError(t, err)
   180  
   181  	assertNotificationGetList := func(listener *mockPhoneListener, phoneNumber keybase1.PhoneNumber, category string) []keybase1.UserPhoneNumber {
   182  		notifications := listener.DrainPhoneNumberNotifications()
   183  		require.Len(t, notifications, 1)
   184  		require.Equal(t, kbPhone, notifications[0].phoneNumber)
   185  		require.Equal(t, category, notifications[0].category)
   186  		return notifications[0].list
   187  	}
   188  
   189  	// adding the phone number generates a notification
   190  	ann.drainGregor()
   191  	list := assertNotificationGetList(annListener, kbPhone, "phone.added")
   192  	require.Len(t, list, 1)
   193  	require.Equal(t, kbPhone, list[0].PhoneNumber)
   194  	require.False(t, list[0].Verified)
   195  	require.False(t, list[0].Superseded)
   196  
   197  	// verifying the phone number generates a notification
   198  	code, err := kbtest.GetPhoneVerificationCode(ann.MetaContext(), kbPhone)
   199  	require.NoError(t, err)
   200  	cli2 := &client.CmdVerifyPhoneNumber{
   201  		Contextified: libkb.NewContextified(ann.tc.G),
   202  		PhoneNumber:  phone,
   203  		Code:         code,
   204  	}
   205  	err = cli2.Run()
   206  	require.NoError(t, err)
   207  	ann.drainGregor()
   208  	list = assertNotificationGetList(annListener, kbPhone, "phone.verified")
   209  	require.Len(t, list, 1)
   210  	require.Equal(t, kbPhone, list[0].PhoneNumber)
   211  	require.True(t, list[0].Verified)
   212  	require.False(t, list[0].Superseded)
   213  
   214  	// if bob now adds and verifies that same number, he should have new notifications for add and verify
   215  	// and ann should have one that her number was superseded
   216  	cli3 := &client.CmdAddPhoneNumber{
   217  		Contextified: libkb.NewContextified(bob.tc.G),
   218  		PhoneNumber:  phone,
   219  	}
   220  	err = cli3.Run()
   221  	require.NoError(t, err)
   222  	bob.drainGregor()
   223  	list = assertNotificationGetList(bobListener, kbPhone, "phone.added")
   224  	require.Len(t, list, 1)
   225  	code, err = kbtest.GetPhoneVerificationCode(bob.MetaContext(), kbPhone)
   226  	require.NoError(t, err)
   227  	cli4 := &client.CmdVerifyPhoneNumber{
   228  		Contextified: libkb.NewContextified(bob.tc.G),
   229  		PhoneNumber:  phone,
   230  		Code:         code,
   231  	}
   232  	err = cli4.Run()
   233  	require.NoError(t, err)
   234  
   235  	bob.drainGregor()
   236  	list = assertNotificationGetList(bobListener, kbPhone, "phone.verified")
   237  	require.Len(t, list, 1)
   238  	require.Equal(t, kbPhone, list[0].PhoneNumber)
   239  	require.True(t, list[0].Verified)
   240  	require.False(t, list[0].Superseded)
   241  
   242  	ann.drainGregor()
   243  	list = assertNotificationGetList(annListener, kbPhone, "phone.superseded")
   244  	require.Len(t, list, 1)
   245  	require.Equal(t, kbPhone, list[0].PhoneNumber)
   246  	require.True(t, list[0].Verified)
   247  	require.True(t, list[0].Superseded)
   248  }
   249  
   250  func TestImplicitTeamWithEmail(t *testing.T) {
   251  	tt := newTeamTester(t)
   252  	defer tt.cleanup()
   253  
   254  	ann := tt.addUser("ann")
   255  	bob := tt.addUser("bob")
   256  
   257  	email := bob.userInfo.email
   258  	assertion := fmt.Sprintf("[%s]@email", email)
   259  
   260  	impteamName := fmt.Sprintf("%s,%s", ann.username, assertion)
   261  	teamID, err := ann.lookupImplicitTeam(true /* create */, impteamName, false /* public */)
   262  	require.NoError(t, err)
   263  
   264  	teamObj := ann.loadTeamByID(teamID, true /* admin */)
   265  	require.Equal(t, 1, teamObj.NumActiveInvites())
   266  	var invite keybase1.TeamInvite
   267  	for _, invite = range teamObj.GetActiveAndObsoleteInvites() {
   268  		// Get first invite to local var
   269  	}
   270  	require.EqualValues(t, email, invite.Name)
   271  	invCat, err := invite.Type.C()
   272  	require.NoError(t, err)
   273  	require.Equal(t, keybase1.TeamInviteCategory_EMAIL, invCat)
   274  
   275  	name, err := teamObj.ImplicitTeamDisplayName(context.Background())
   276  	require.NoError(t, err)
   277  	require.Len(t, name.Writers.KeybaseUsers, 1)
   278  	require.Len(t, name.Writers.UnresolvedUsers, 1)
   279  	require.Equal(t, fmt.Sprintf("%s,%s", assertion, ann.username), name.String())
   280  
   281  	t.Logf("Got display name back: %q", name.String())
   282  
   283  	seqnoAfterResolve := teamObj.NextSeqno()
   284  
   285  	// Verifying an email should RSVP the invitation which will notify
   286  	// (using SBS gregor msg) ann to resolve it.
   287  	err = kbtest.VerifyEmailAuto(bob.MetaContext(), keybase1.EmailAddress(email))
   288  	require.NoError(t, err)
   289  	err = emails.SetVisibilityEmail(bob.MetaContext(), keybase1.EmailAddress(email), keybase1.IdentityVisibility_PUBLIC)
   290  	require.NoError(t, err)
   291  
   292  	ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ID: teamID}, seqnoAfterResolve)
   293  }