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

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"errors"
     7  	"regexp"
     8  	"sync"
     9  	"testing"
    10  
    11  	lru "github.com/hashicorp/golang-lru"
    12  	"github.com/keybase/client/go/kbtest"
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func toErr(attempt keybase1.BoxAuditAttempt) error {
    19  	if attempt.Error != nil {
    20  		return errors.New(*attempt.Error)
    21  	}
    22  	return nil
    23  }
    24  
    25  func mustGetJailLRU(tc *libkb.TestContext, a libkb.TeamBoxAuditor) *lru.Cache {
    26  	b := a.(*BoxAuditor)
    27  	return b.jailLRU
    28  }
    29  
    30  func countTrues(t *testing.T, cache *lru.Cache) int {
    31  	c := 0
    32  	for _, k := range cache.Keys() {
    33  		v, ok := cache.Get(k)
    34  		require.True(t, ok)
    35  		vBool := v.(bool)
    36  		if vBool {
    37  			c++
    38  		}
    39  	}
    40  	return c
    41  }
    42  
    43  func mustGetBoxState(tc *libkb.TestContext, a libkb.TeamBoxAuditor, mctx libkb.MetaContext, teamID keybase1.TeamID) (*BoxAuditLog, *BoxAuditQueue, *BoxAuditJail) {
    44  	b := a.(*BoxAuditor)
    45  	log, err := b.maybeGetLog(mctx, teamID)
    46  	require.NoError(tc.T, err)
    47  	queue, err := b.maybeGetQueue(mctx)
    48  	require.NoError(tc.T, err)
    49  	jail, err := b.maybeGetJail(mctx)
    50  	require.NoError(tc.T, err)
    51  	return log, queue, jail
    52  }
    53  
    54  func TestBoxAuditAttempt(t *testing.T) {
    55  	fus, tcs, cleanup := setupNTests(t, 3)
    56  	defer cleanup()
    57  
    58  	_, bU, cU := fus[0], fus[1], fus[2]
    59  	aTc, bTc, cTc := tcs[0], tcs[1], tcs[2]
    60  	aM, bM, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc)
    61  	aA, bA, cA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor()
    62  
    63  	t.Logf("A creates team")
    64  	teamName, teamID := createTeam2(*aTc)
    65  
    66  	t.Logf("adding B as admin")
    67  	_, err := AddMember(aM.Ctx(), aTc.G, teamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
    68  	require.NoError(t, err)
    69  
    70  	t.Logf("adding C as reader")
    71  	_, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil)
    72  	require.NoError(t, err)
    73  
    74  	require.NoError(t, toErr(aA.Attempt(aM, teamID, false)), "A can attempt")
    75  	require.NoError(t, toErr(bA.Attempt(aM, teamID, false)), "B can attempt")
    76  
    77  	attempt := aA.Attempt(aM, teamID, false)
    78  	require.NoError(t, toErr(attempt))
    79  	require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED, "owner can attempt")
    80  	require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1))
    81  
    82  	attempt = bA.Attempt(bM, teamID, false)
    83  	require.NoError(t, toErr(attempt))
    84  	require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED, "admins can attempt")
    85  	require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1))
    86  
    87  	attempt = cA.Attempt(cM, teamID, false)
    88  	require.NoError(t, toErr(attempt))
    89  	require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_ROLE, "readers can attempt but don't verify")
    90  	require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1))
    91  
    92  	kbtest.RotatePaper(*cTc, cU)
    93  	attempt = aA.Attempt(aM, teamID, false)
    94  	require.Error(t, toErr(attempt), "team not rotated after puk rotate so attempt fails")
    95  	team, err := Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true})
    96  	require.NoError(t, err)
    97  	err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE)
    98  	require.NoError(t, err)
    99  	attempt = aA.Attempt(aM, teamID, false)
   100  	require.NoError(t, toErr(attempt), "team rotated, so audit works")
   101  
   102  	t.Logf("check rotate-before-attempt option")
   103  	kbtest.RotatePaper(*cTc, cU)
   104  	attempt = aA.Attempt(aM, teamID, false)
   105  	require.Error(t, toErr(attempt), "team not rotated after puk rotate so attempt fails")
   106  	attempt = aA.Attempt(aM, teamID, true)
   107  	require.NoError(t, toErr(attempt), "rotate-before-attempt option works")
   108  
   109  	t.Logf("check after reset")
   110  	kbtest.ResetAccount(*cTc, cU)
   111  	attempt = aA.Attempt(aM, teamID, false)
   112  	require.Error(t, toErr(attempt), "team not rotated after reset")
   113  	attempt = aA.Attempt(aM, teamID, true)
   114  	require.NoError(t, toErr(attempt), "attempt OK after rotate")
   115  
   116  	attempt = cA.Attempt(cM, teamID, false)
   117  	require.Error(t, toErr(attempt), "check that someone not in a team cannot audit")
   118  
   119  	t.Logf("C provisions and A adds C back after account reset")
   120  	err = cU.Login(cTc.G)
   121  	require.NoError(t, err)
   122  	_, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil)
   123  	require.NoError(t, err)
   124  
   125  	attempt = cA.Attempt(cM, teamID, false)
   126  	require.NoError(t, toErr(attempt), "check that attempt is OK after allow reset member back in without another rotate")
   127  
   128  	t.Logf("check after delete")
   129  	kbtest.DeleteAccount(*cTc, cU)
   130  	attempt = aA.Attempt(aM, teamID, false)
   131  	require.Error(t, toErr(attempt), "team not rotated after delete")
   132  	attempt = aA.Attempt(aM, teamID, true)
   133  	require.NoError(t, toErr(attempt), "attempt OK after rotate")
   134  }
   135  
   136  func requireFatalError(t *testing.T, err error, args ...interface{}) {
   137  	_, ok := err.(FatalBoxAuditError)
   138  	require.True(t, ok, args...)
   139  }
   140  
   141  func requireNonfatalError(t *testing.T, err error, args ...interface{}) {
   142  	_, ok := err.(NonfatalBoxAuditError)
   143  	require.True(t, ok, args...)
   144  }
   145  
   146  func auditTeam(a libkb.TeamBoxAuditor, mctx libkb.MetaContext, teamID keybase1.TeamID) error {
   147  	_, err := a.BoxAuditTeam(mctx, teamID)
   148  	return err
   149  }
   150  
   151  func TestBoxAuditAudit(t *testing.T) {
   152  	fus, tcs, cleanup := setupNTests(t, 5)
   153  	defer cleanup()
   154  
   155  	_, bU, cU, dU, eU := fus[0], fus[1], fus[2], fus[3], fus[4]
   156  	aTc, bTc, cTc, dTc, eTc := tcs[0], tcs[1], tcs[2], tcs[3], tcs[4]
   157  	aM, bM, cM, dM, eM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc), libkb.NewMetaContextForTest(*dTc), libkb.NewMetaContextForTest(*eTc)
   158  	aA, bA, cA, dA, eA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor(), dTc.G.GetTeamBoxAuditor(), eTc.G.GetTeamBoxAuditor()
   159  
   160  	t.Logf("A creates team")
   161  	teamName, teamID := createTeam2(*aTc)
   162  
   163  	t.Logf("adding B as admin")
   164  	_, err := AddMember(aM.Ctx(), aTc.G, teamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   165  	require.NoError(t, err)
   166  
   167  	t.Logf("adding C as reader")
   168  	_, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil)
   169  	require.NoError(t, err)
   170  
   171  	t.Logf("adding D as bot")
   172  	_, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), dU.Username, keybase1.TeamRole_BOT, nil)
   173  	require.NoError(t, err)
   174  
   175  	t.Logf("adding E as restricted bot")
   176  	_, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), eU.Username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{})
   177  	require.NoError(t, err)
   178  
   179  	require.NoError(t, auditTeam(aA, aM, teamID), "A can audit")
   180  	require.NoError(t, auditTeam(bA, bM, teamID), "B can audit")
   181  	require.NoError(t, auditTeam(cA, cM, teamID), "C can audit (this is vacuous, since C is a reader)")
   182  	require.NoError(t, auditTeam(dA, dM, teamID), "D can audit (this is vacuous, since D is a bot)")
   183  	require.NoError(t, auditTeam(eA, eM, teamID), "E can audit (this is vacuous, since E is a restricted bot)")
   184  
   185  	var nullstring *string
   186  	g1 := keybase1.PerTeamKeyGeneration(1)
   187  
   188  	t.Logf("check A's view of the successful audit in db")
   189  	log, queue, jail := mustGetBoxState(aTc, aA, aM, teamID)
   190  	log.Audits[0].ID = nil
   191  	log.Audits[0].Attempts[0].Ctime = 0
   192  	require.Equal(t, *log, BoxAuditLog{
   193  		Audits: []BoxAudit{
   194  			{
   195  				ID: nil,
   196  				Attempts: []keybase1.BoxAuditAttempt{
   197  					{
   198  						Ctime:      0,
   199  						Error:      nullstring,
   200  						Result:     keybase1.BoxAuditAttemptResult_OK_VERIFIED,
   201  						Generation: &g1,
   202  					},
   203  				},
   204  			},
   205  		},
   206  		InProgress: false,
   207  		Version:    CurrentBoxAuditVersion,
   208  	})
   209  	require.Nil(t, queue)
   210  	require.Equal(t, *jail, BoxAuditJail{
   211  		TeamIDs: map[keybase1.TeamID]bool{},
   212  		Version: CurrentBoxAuditVersion,
   213  	})
   214  
   215  	t.Logf("check B's view of the successful audit in db")
   216  	log, queue, jail = mustGetBoxState(bTc, bA, bM, teamID)
   217  	log.Audits[0].ID = nil
   218  	log.Audits[0].Attempts[0].Ctime = 0
   219  	require.Equal(t, *log, BoxAuditLog{
   220  		Audits: []BoxAudit{
   221  			{
   222  				ID: nil,
   223  				Attempts: []keybase1.BoxAuditAttempt{
   224  					{
   225  						Ctime:      0,
   226  						Error:      nullstring,
   227  						Result:     keybase1.BoxAuditAttemptResult_OK_VERIFIED,
   228  						Generation: &g1,
   229  					},
   230  				},
   231  			},
   232  		},
   233  		InProgress: false,
   234  		Version:    CurrentBoxAuditVersion,
   235  	})
   236  	require.Nil(t, queue)
   237  	require.Equal(t, *jail, BoxAuditJail{
   238  		TeamIDs: map[keybase1.TeamID]bool{},
   239  		Version: CurrentBoxAuditVersion,
   240  	})
   241  
   242  	t.Logf("check C's & D's view of the successful no-op audit in db")
   243  	vacuousLog := BoxAuditLog{
   244  		Audits: []BoxAudit{
   245  			{
   246  				ID: nil,
   247  				Attempts: []keybase1.BoxAuditAttempt{
   248  					{
   249  						Ctime:      0,
   250  						Error:      nullstring,
   251  						Result:     keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_ROLE,
   252  						Generation: &g1,
   253  					},
   254  				},
   255  			},
   256  		},
   257  		InProgress: false,
   258  		Version:    CurrentBoxAuditVersion,
   259  	}
   260  	log, queue, jail = mustGetBoxState(cTc, cA, cM, teamID)
   261  	log.Audits[0].ID = nil
   262  	log.Audits[0].Attempts[0].Ctime = 0
   263  	require.Equal(t, *log, vacuousLog)
   264  	require.Nil(t, queue)
   265  	require.Equal(t, *jail, BoxAuditJail{
   266  		TeamIDs: map[keybase1.TeamID]bool{},
   267  		Version: CurrentBoxAuditVersion,
   268  	})
   269  	log, queue, jail = mustGetBoxState(dTc, dA, dM, teamID)
   270  	log.Audits[0].ID = nil
   271  	log.Audits[0].Attempts[0].Ctime = 0
   272  	require.Equal(t, *log, vacuousLog)
   273  	require.Nil(t, queue)
   274  	require.Equal(t, *jail, BoxAuditJail{
   275  		TeamIDs: map[keybase1.TeamID]bool{},
   276  		Version: CurrentBoxAuditVersion,
   277  	})
   278  	log, queue, jail = mustGetBoxState(eTc, eA, eM, teamID)
   279  	log.Audits[0].ID = nil
   280  	log.Audits[0].Attempts[0].Ctime = 0
   281  	require.Equal(t, *log, vacuousLog)
   282  	require.Nil(t, queue)
   283  	require.Equal(t, *jail, BoxAuditJail{
   284  		TeamIDs: map[keybase1.TeamID]bool{},
   285  		Version: CurrentBoxAuditVersion,
   286  	})
   287  
   288  	require.Equal(t, countTrues(t, mustGetJailLRU(aTc, aA)), 0)
   289  	require.Equal(t, countTrues(t, mustGetJailLRU(bTc, bA)), 0)
   290  	require.Equal(t, countTrues(t, mustGetJailLRU(cTc, cA)), 0)
   291  	require.Equal(t, countTrues(t, mustGetJailLRU(cTc, dA)), 0)
   292  	require.Equal(t, countTrues(t, mustGetJailLRU(cTc, eA)), 0)
   293  
   294  	t.Logf("checking state after failed attempts")
   295  	t.Logf("disable autorotate on retry")
   296  	aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = true
   297  	t.Logf("c rotates and a check's state")
   298  	kbtest.RotatePaper(*cTc, cU)
   299  	err = auditTeam(aA, aM, teamID)
   300  	requireNonfatalError(t, err, "audit failure on unrotated puk")
   301  	_, ok := err.(NonfatalBoxAuditError)
   302  	require.True(t, ok)
   303  	log, queue, _ = mustGetBoxState(aTc, aA, aM, teamID)
   304  	require.Equal(t, len(log.Audits), 2)
   305  	require.True(t, log.InProgress, "failed audit causes it to be in progress")
   306  	require.Equal(t, len(queue.Items), 1)
   307  	require.Equal(t, queue.Items[0].TeamID, teamID)
   308  	require.Equal(t, queue.Version, CurrentBoxAuditVersion)
   309  	err = auditTeam(aA, aM, teamID)
   310  	requireNonfatalError(t, err, "another audit failure on unrotated puk")
   311  	_, queue, _ = mustGetBoxState(aTc, aA, aM, teamID)
   312  	require.Equal(t, len(queue.Items), 1, "no duplicates in retry queue")
   313  
   314  	t.Logf("checking that we can load a team in retry queue, but that is not jailed yet")
   315  	_, err = Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true})
   316  	require.NoError(t, err)
   317  
   318  	t.Logf("rotate until we hit max retry attempts; should result in fatal error")
   319  	for i := 0; i < MaxBoxAuditRetryAttempts-3; i++ {
   320  		err = auditTeam(aA, aM, teamID)
   321  		requireNonfatalError(t, err, "another audit failure on unrotated puk")
   322  	}
   323  	err = auditTeam(aA, aM, teamID)
   324  	requireFatalError(t, err)
   325  	log, queue, jail = mustGetBoxState(aTc, aA, aM, teamID)
   326  	require.Equal(t, len(log.Last().Attempts), MaxBoxAuditRetryAttempts)
   327  	require.True(t, log.InProgress, "fatal state still counts as in progress even though it won't be retried")
   328  	require.Equal(t, len(queue.Items), 0, "jailed teams not in retry queue")
   329  	require.Equal(t, *jail, BoxAuditJail{
   330  		TeamIDs: map[keybase1.TeamID]bool{
   331  			teamID: true,
   332  		},
   333  		Version: CurrentBoxAuditVersion,
   334  	})
   335  	require.Equal(t, 1, countTrues(t, mustGetJailLRU(aTc, aA)))
   336  
   337  	// NOTE We may eventually cause the jailed team load that did not pass
   338  	// reaudit to fail entirely instead of just putting up a black bar in the
   339  	// GUI.
   340  	t.Logf("checking that we can load a jailed team that won't pass auto-reaudit")
   341  	_, err = Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true})
   342  	require.NoError(t, err)
   343  
   344  	inJail, err := aA.IsInJail(aM, teamID)
   345  	require.NoError(t, err)
   346  	require.True(t, inJail)
   347  
   348  	t.Logf("reenable autorotate on retry")
   349  	aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = false
   350  
   351  	// Not in queue anymore so this is a noop
   352  	_, err = aA.RetryNextBoxAudit(aM)
   353  	require.NoError(t, err)
   354  
   355  	// We are jailed, but reaudit passes
   356  	didReaudit, err := aA.AssertUnjailedOrReaudit(aM, teamID)
   357  	require.NoError(t, err)
   358  	require.True(t, didReaudit)
   359  
   360  	require.NoError(t, err, "no error since we rotate on retry now")
   361  	log, queue, jail = mustGetBoxState(aTc, aA, aM, teamID)
   362  	require.False(t, log.InProgress)
   363  	attempts := log.Last().Attempts
   364  	require.Equal(t, attempts[len(attempts)-1].Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED)
   365  	require.Equal(t, len(queue.Items), 0, "not in queue")
   366  	require.Equal(t, len(jail.TeamIDs), 0, "unjailed")
   367  
   368  	// Just check these public methods are ok
   369  	teamIDs, err := KnownTeamIDs(aM)
   370  	require.NoError(t, err)
   371  	require.Equal(t, len(teamIDs), 1)
   372  	require.Equal(t, teamIDs[0], teamID)
   373  
   374  	randomID, err := randomKnownTeamID(aM)
   375  	require.NoError(t, err)
   376  	require.NotNil(t, randomID)
   377  	require.Equal(t, teamID, *randomID)
   378  
   379  	_, err = aA.BoxAuditRandomTeam(aM)
   380  	require.NoError(t, err)
   381  }
   382  
   383  // TestBoxAuditRaces makes 3 users, 3 teams with all 3 users, and audits all
   384  // of them many times at the same time in separate goroutines.  If tested with
   385  // the -race option, it will fail if there's any data races. Also, we check
   386  // that all the routines eventually finish, which might catch some deadlocks.
   387  // Note that the race detector only catches memory races, so it doesn't really
   388  // mean there are no data races in the code even if it passes the detector, i.e.,
   389  // one goroutine could have overwritten a queue add of another goroutine, and this
   390  // would not be caught by the detector.
   391  // However, we *do* check that the error is Nonfatal, not Client, which means there
   392  // were no errors in the leveldb or lru operations, but rather the audit itself.
   393  func TestBoxAuditRaces(t *testing.T) {
   394  	fus, tcs, cleanup := setupNTests(t, 3)
   395  	defer cleanup()
   396  
   397  	aU, bU, cU := fus[0], fus[1], fus[2]
   398  	aTc, bTc, cTc := tcs[0], tcs[1], tcs[2]
   399  	aM, bM, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc)
   400  	aA, bA, cA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor()
   401  
   402  	aTeamName, aTeamID := createTeam2(*aTc)
   403  	_, err := AddMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   404  	require.NoError(t, err)
   405  	_, err = AddMember(aM.Ctx(), aTc.G, aTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil)
   406  	require.NoError(t, err)
   407  
   408  	bTeamName, bTeamID := createTeam2(*bTc)
   409  	_, err = AddMember(bM.Ctx(), bTc.G, bTeamName.String(), aU.Username, keybase1.TeamRole_ADMIN, nil)
   410  	require.NoError(t, err)
   411  	_, err = AddMember(bM.Ctx(), bTc.G, bTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil)
   412  	require.NoError(t, err)
   413  
   414  	cTeamName, cTeamID := createTeam2(*cTc)
   415  	_, err = AddMember(cM.Ctx(), cTc.G, cTeamName.String(), aU.Username, keybase1.TeamRole_ADMIN, nil)
   416  	require.NoError(t, err)
   417  	_, err = AddMember(cM.Ctx(), cTc.G, cTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   418  	require.NoError(t, err)
   419  
   420  	// We do this so the audits will access the shared jail and queue data
   421  	// structures, not just the logs.
   422  	t.Logf("Turning off autorotate on retry and putting teams in failing audit state")
   423  	aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = true
   424  	kbtest.RotatePaper(*aTc, aU)
   425  	kbtest.RotatePaper(*bTc, bU)
   426  	kbtest.RotatePaper(*cTc, cU)
   427  
   428  	auditors := []libkb.TeamBoxAuditor{aA, bA, cA}
   429  	metacontexts := []libkb.MetaContext{aM, bM, cM}
   430  	teamIDs := []keybase1.TeamID{aTeamID, bTeamID, cTeamID}
   431  	var wg sync.WaitGroup
   432  	total := 9
   433  	errCh := make(chan error, total)
   434  	wg.Add(total)
   435  	for i := 0; i < 3; i++ {
   436  		for j := 0; j < 3; j++ {
   437  			go func(i, j int) {
   438  				_, auditErr := auditors[i].BoxAuditTeam(metacontexts[i], teamIDs[j])
   439  				errCh <- auditErr
   440  				wg.Done()
   441  			}(i, j)
   442  		}
   443  	}
   444  	wg.Wait()
   445  	i := 0
   446  	for err := range errCh {
   447  		require.NotNil(t, err)
   448  		boxErr := err.(NonfatalBoxAuditError)
   449  		require.Regexp(t, regexp.MustCompile(`.*box summary hash mismatch.*`), boxErr.inner.Error())
   450  		// stop reading after 9 handled errors, otherwise the for loop goes
   451  		// forever since we don't close errCh
   452  		i++
   453  		if i >= total {
   454  			break
   455  		}
   456  	}
   457  }
   458  
   459  // TestBoxAuditCalculation makes sure we calculate summaries at different
   460  // merkle seqnos properly, regardless of users who are reset or who rotate
   461  // their devices.
   462  func TestBoxAuditCalculation(t *testing.T) {
   463  	fus, tcs, cleanup := setupNTests(t, 3)
   464  	defer cleanup()
   465  
   466  	aU, bU, cU := fus[0], fus[1], fus[2]
   467  	aTc, bTc, cTc := tcs[0], tcs[1], tcs[2]
   468  	aM, _, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc)
   469  
   470  	aTeamName, aTeamID := createTeam2(*aTc)
   471  	_, err := AddMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   472  	require.NoError(t, err)
   473  	_, err = AddMember(aM.Ctx(), aTc.G, aTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil)
   474  	require.NoError(t, err)
   475  
   476  	puk := aU.User.GetComputedKeyFamily().GetLatestPerUserKey()
   477  	initSeqno := puk.Seqno
   478  
   479  	load := func(mctx libkb.MetaContext, teamID keybase1.TeamID) (team *Team, chainSummary, currentSummary *boxPublicSummary) {
   480  		team, err := loadTeamForBoxAudit(mctx, teamID)
   481  		require.NoError(t, err)
   482  		chainSummary, err = calculateChainSummary(mctx, team)
   483  		require.NoError(t, err)
   484  		currentSummary, err = calculateCurrentSummary(mctx, team)
   485  		require.NoError(t, err)
   486  		return team, chainSummary, currentSummary
   487  	}
   488  
   489  	_, chainSummary, currentSummary := load(aM, aTeamID)
   490  	expected := boxPublicSummaryTable{
   491  		aU.User.GetUID(): initSeqno,
   492  		bU.User.GetUID(): initSeqno,
   493  		cU.User.GetUID(): initSeqno,
   494  	}
   495  	require.Equal(t, expected, chainSummary.table)
   496  	require.Equal(t, expected, currentSummary.table)
   497  
   498  	t.Logf("B rotates PUK")
   499  	kbtest.RotatePaper(*bTc, bU)
   500  
   501  	team, chainSummary, currentSummary := load(cM, aTeamID)
   502  	newExpected := boxPublicSummaryTable{
   503  		aU.User.GetUID(): initSeqno,
   504  		bU.User.GetUID(): initSeqno + 3,
   505  		cU.User.GetUID(): initSeqno,
   506  	}
   507  	require.Equal(t, expected, chainSummary.table)
   508  	require.Equal(t, newExpected, currentSummary.table)
   509  
   510  	t.Logf("A rotates team")
   511  	err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE)
   512  	require.NoError(t, err)
   513  
   514  	t.Logf("C checks summary")
   515  	_, chainSummary, currentSummary = load(cM, aTeamID)
   516  	require.Equal(t, newExpected, chainSummary.table)
   517  	require.Equal(t, newExpected, currentSummary.table)
   518  
   519  	t.Logf("check after reset")
   520  	kbtest.ResetAccount(*cTc, cU)
   521  	newerExpected := boxPublicSummaryTable{
   522  		aU.User.GetUID(): initSeqno,
   523  		bU.User.GetUID(): initSeqno + 3,
   524  	}
   525  	_, chainSummary, currentSummary = load(aM, aTeamID)
   526  	require.Equal(t, newExpected, chainSummary.table)
   527  	require.Equal(t, newerExpected, currentSummary.table)
   528  
   529  	t.Logf("make some dummy links to test historical chain summary")
   530  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil)
   531  	require.NoError(t, err)
   532  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   533  	require.NoError(t, err)
   534  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil)
   535  	require.NoError(t, err)
   536  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   537  	require.NoError(t, err)
   538  	_, chainSummary, currentSummary = load(aM, aTeamID)
   539  	require.Equal(t, newExpected, chainSummary.table)
   540  	require.Equal(t, newerExpected, currentSummary.table)
   541  
   542  	t.Logf("make some more dummy links")
   543  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil)
   544  	require.NoError(t, err)
   545  	err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   546  	require.NoError(t, err)
   547  
   548  	team, _, _ = load(aM, aTeamID)
   549  	err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE)
   550  	require.NoError(t, err)
   551  	_, chainSummary, currentSummary = load(aM, aTeamID)
   552  	require.Equal(t, newerExpected, chainSummary.table)
   553  	require.Equal(t, newerExpected, currentSummary.table)
   554  }
   555  
   556  func TestBoxAuditSubteamCalculation(t *testing.T) {
   557  	fus, tcs, cleanup := setupNTests(t, 3)
   558  	defer cleanup()
   559  
   560  	aU, bU, cU := fus[0], fus[1], fus[2]
   561  	aTc, bTc, cTc := tcs[0], tcs[1], tcs[2]
   562  	aM, _, _ := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc)
   563  
   564  	parentName, parentID := createTeam2(*tcs[0])
   565  	// A is not in subteam
   566  	subteamID, err := CreateSubteam(aM.Ctx(), aTc.G, "abc", parentName, keybase1.TeamRole_NONE /* addSelfAs */)
   567  	require.NoError(t, err)
   568  	subteamName, err := parentName.Append("abc")
   569  	require.NoError(t, err)
   570  	t.Logf("adding B as writer of team")
   571  	_, err = AddMember(aM.Ctx(), aTc.G, parentName.String(), bU.Username, keybase1.TeamRole_WRITER, nil)
   572  	require.NoError(t, err)
   573  	t.Logf("adding C as writer of subteam")
   574  	_, err = AddMember(aM.Ctx(), aTc.G, subteamName.String(), cU.Username, keybase1.TeamRole_WRITER, nil)
   575  	require.NoError(t, err)
   576  
   577  	puk := aU.User.GetComputedKeyFamily().GetLatestPerUserKey()
   578  	initSeqno := puk.Seqno
   579  	load := func(mctx libkb.MetaContext, teamID keybase1.TeamID) (team *Team, chainSummary, currentSummary *boxPublicSummary) {
   580  		team, err := loadTeamForBoxAudit(mctx, teamID)
   581  		require.NoError(t, err)
   582  		chainSummary, err = calculateChainSummary(mctx, team)
   583  		require.NoError(t, err)
   584  		currentSummary, err = calculateCurrentSummary(mctx, team)
   585  		require.NoError(t, err)
   586  		return team, chainSummary, currentSummary
   587  	}
   588  
   589  	_, chainSummary, currentSummary := load(aM, parentID)
   590  	teamTable1 := boxPublicSummaryTable{
   591  		aU.User.GetUID(): initSeqno,
   592  		bU.User.GetUID(): initSeqno,
   593  	}
   594  	require.Equal(t, teamTable1, chainSummary.table)
   595  	require.Equal(t, teamTable1, currentSummary.table)
   596  
   597  	_, chainSummary, currentSummary = load(aM, *subteamID)
   598  	subteamTable1 := boxPublicSummaryTable{
   599  		aU.User.GetUID(): initSeqno,
   600  		cU.User.GetUID(): initSeqno,
   601  	}
   602  	require.Equal(t, subteamTable1, chainSummary.table)
   603  	require.Equal(t, subteamTable1, currentSummary.table)
   604  
   605  	t.Logf("make b an admin of parent, giving him boxes")
   606  	err = EditMember(aM.Ctx(), aTc.G, parentName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil)
   607  	require.NoError(t, err)
   608  	_, chainSummary, currentSummary = load(aM, *subteamID)
   609  	t.Logf("check do not need to rotate to know about new admin in chainsummary")
   610  	subteamTable2 := boxPublicSummaryTable{
   611  		aU.User.GetUID(): initSeqno,
   612  		bU.User.GetUID(): initSeqno,
   613  		cU.User.GetUID(): initSeqno,
   614  	}
   615  	require.Equal(t, subteamTable2, chainSummary.table)
   616  	require.Equal(t, subteamTable2, currentSummary.table)
   617  }
   618  
   619  func TestBoxAuditOpen(t *testing.T) {
   620  	_, tcs, cleanup := setupNTests(t, 1)
   621  	defer cleanup()
   622  	tc := tcs[0]
   623  
   624  	b, err := libkb.RandBytes(4)
   625  	auditor := tc.G.GetTeamBoxAuditor()
   626  	require.NoError(t, err)
   627  	name := hex.EncodeToString(b)
   628  	_, err = CreateRootTeam(context.Background(), tc.G, name, keybase1.TeamSettings{
   629  		Open:   true,
   630  		JoinAs: keybase1.TeamRole_WRITER,
   631  	})
   632  	require.NoError(t, err)
   633  
   634  	mctx := libkb.NewMetaContextForTest(*tc)
   635  	teamname, err := keybase1.TeamNameFromString(name)
   636  	require.NoError(t, err)
   637  	attempt := auditor.Attempt(mctx, teamname.ToPrivateTeamID(), false)
   638  
   639  	require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_OPENTEAM)
   640  }
   641  
   642  func TestBoxAuditImplicit(t *testing.T) {
   643  	fus, tcs, cleanup := setupNTests(t, 2)
   644  	defer cleanup()
   645  
   646  	var teamIDs []keybase1.TeamID
   647  
   648  	for idx, isPublic := range []bool{false, true} {
   649  		t.Logf("testing audit implicit with public=%t", isPublic)
   650  		team1, _, _, err := LookupOrCreateImplicitTeam(context.TODO(), tcs[idx].G, "t_tracy,"+fus[idx].Username, isPublic)
   651  		require.NoError(t, err)
   652  		auditor := tcs[idx].G.GetTeamBoxAuditor()
   653  		mctx := libkb.NewMetaContextForTest(*tcs[idx])
   654  		attempt := auditor.Attempt(mctx, team1.ID, false)
   655  		require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED)
   656  		require.NoError(t, auditTeam(auditor, mctx, team1.ID))
   657  		teamIDs = append(teamIDs, team1.ID)
   658  	}
   659  
   660  	// Check we keep track of public and private implicit teams too
   661  	mctx := libkb.NewMetaContextForTest(*tcs[0])
   662  	knownTeamIDs, err := KnownTeamIDs(mctx)
   663  	require.NoError(t, err)
   664  	require.Equal(t, len(knownTeamIDs), 1)
   665  	require.Equal(t, teamIDs[0], knownTeamIDs[0])
   666  	randomID, err := randomKnownTeamID(mctx)
   667  	require.NoError(t, err)
   668  	require.NotNil(t, randomID)
   669  	require.True(t, teamIDs[0] == *randomID)
   670  
   671  	mctx = libkb.NewMetaContextForTest(*tcs[1])
   672  	knownTeamIDs, err = KnownTeamIDs(mctx)
   673  	require.NoError(t, err)
   674  	require.Equal(t, len(knownTeamIDs), 1)
   675  	require.Equal(t, teamIDs[1], knownTeamIDs[0])
   676  	randomID, err = randomKnownTeamID(mctx)
   677  	require.NoError(t, err)
   678  	require.NotNil(t, randomID)
   679  	require.True(t, teamIDs[1] == *randomID)
   680  }
   681  
   682  func TestBoxAuditSubteamWithImplicitAdmins(t *testing.T) {
   683  	fus, tcs, cleanup := setupNTests(t, 3)
   684  	_, bU := fus[0], fus[1]
   685  	aTc, bTc := tcs[0], tcs[1]
   686  	aM, bM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc)
   687  	aA, bA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor()
   688  	defer cleanup()
   689  
   690  	parentName, _ := createTeam2(*tcs[0])
   691  	// A is not in subteam
   692  	subteamID, err := CreateSubteam(aM.Ctx(), aTc.G, "abc", parentName, keybase1.TeamRole_NONE /* addSelfAs */)
   693  	require.NoError(t, err)
   694  	subTeamName, err := parentName.Append("abc")
   695  	require.NoError(t, err)
   696  
   697  	t.Logf("adding B as admin to subteam")
   698  	_, err = AddMember(aM.Ctx(), aTc.G, subTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil)
   699  	require.NoError(t, err)
   700  
   701  	// Even though A is not in subteam, A has a box because of implicit
   702  	// adminship. Check that B can still audit successfully.
   703  	require.NoError(t, auditTeam(bA, bM, *subteamID))
   704  
   705  	// Add third user as an admin to parent team. They will be boxed for
   706  	// subteam as well.
   707  	_, err = AddMember(aM.Ctx(), aM.G(), parentName.String(), fus[2].Username, keybase1.TeamRole_ADMIN, nil)
   708  	require.NoError(t, err)
   709  
   710  	// Audit both teams again
   711  	require.NoError(t, auditTeam(aA, aM, parentName.RootID()))
   712  
   713  	require.NoError(t, auditTeam(bA, bM, *subteamID))
   714  }
   715  
   716  func TestBoxAuditTransactionsWithBoxSummaries(t *testing.T) {
   717  	tc, owner, otherA, otherB, name := memberSetupMultiple(t)
   718  	defer tc.Cleanup()
   719  
   720  	otherC, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
   721  	require.NoError(t, err)
   722  
   723  	kbtest.Logout(tc)
   724  	require.NoError(t, owner.Login(tc.G))
   725  
   726  	t.Logf("Team name is %s\n", name)
   727  
   728  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
   729  		Name:      name,
   730  		NeedAdmin: true,
   731  	})
   732  	require.NoError(t, err)
   733  
   734  	tx := CreateAddMemberTx(team)
   735  	for _, otherUser := range []*kbtest.FakeUser{otherA, otherB, otherC} {
   736  		val := &keybase1.TeamChangeReq{}
   737  		err = val.AddUVWithRole(otherUser.GetUserVersion(), keybase1.TeamRole_WRITER, nil)
   738  		require.NoError(t, err)
   739  		payload := txPayload{
   740  			Tag: txPayloadTagCryptomembers,
   741  			Val: val,
   742  		}
   743  		tx.payloads = append(tx.payloads, payload)
   744  	}
   745  
   746  	err = tx.Post(libkb.NewMetaContextForTest(tc))
   747  	require.NoError(t, err)
   748  
   749  	auditor := tc.G.GetTeamBoxAuditor()
   750  	attempt := auditor.Attempt(libkb.NewMetaContextForTest(tc), team.ID, false /* rotateBeforeAudit */)
   751  	require.Nil(t, attempt.Error)
   752  	require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED)
   753  }
   754  
   755  func TestBoxAuditVersionBump(t *testing.T) {
   756  	_, tcs, cleanup := setupNTests(t, 1)
   757  	defer cleanup()
   758  	aTc := tcs[0]
   759  	aM := libkb.NewMetaContextForTest(*aTc)
   760  
   761  	teamID := keybase1.TeamID("YELLOW_SUBMARINE")
   762  
   763  	a1 := newBoxAuditorWithVersion(aM.G(), 5)
   764  
   765  	err := a1.jail(aM, teamID)
   766  	require.NoError(t, err)
   767  
   768  	jailed, err := a1.IsInJail(aM, teamID)
   769  	require.NoError(t, err)
   770  	require.True(t, jailed)
   771  
   772  	jailed, err = a1.IsInJail(aM, teamID)
   773  	require.NoError(t, err)
   774  	require.True(t, jailed)
   775  
   776  	a2 := newBoxAuditorWithVersion(aM.G(), 6)
   777  	jailed, err = a2.IsInJail(aM, teamID)
   778  	require.NoError(t, err)
   779  	require.False(t, jailed)
   780  
   781  	jailed, err = a1.IsInJail(aM, teamID)
   782  	require.NoError(t, err)
   783  	require.True(t, jailed)
   784  }
   785  
   786  type timeoutAPI struct {
   787  	*libkb.APIArgRecorder
   788  }
   789  
   790  var errFakeNetworkTimeout = errors.New("fake network timeout in test")
   791  
   792  func (r *timeoutAPI) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error {
   793  	return libkb.APINetError{Err: errFakeNetworkTimeout}
   794  }
   795  func (r *timeoutAPI) PostDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error {
   796  	return libkb.APINetError{Err: errFakeNetworkTimeout}
   797  }
   798  
   799  func (r *timeoutAPI) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) {
   800  	return nil, libkb.APINetError{Err: errFakeNetworkTimeout}
   801  }
   802  
   803  func TestBoxAuditRetryBehavior(t *testing.T) {
   804  	_, tcs, cleanup := setupNTests(t, 1)
   805  	defer cleanup()
   806  
   807  	aTc := tcs[0]
   808  	aM := libkb.NewMetaContextForTest(*aTc)
   809  	aA := aTc.G.GetTeamBoxAuditor()
   810  
   811  	t.Logf("A creates team")
   812  	teamName, teamID := createTeam2(*aTc)
   813  
   814  	require.NoError(t, auditTeam(aA, aM, teamID), "A can audit")
   815  
   816  	origAPI := aTc.G.API
   817  	aTc.G.API = &timeoutAPI{}
   818  	err := auditTeam(aA, aM, teamID)
   819  	require.Error(t, err, "A can't audit when offline")
   820  	require.Contains(t, err.Error(), "fake network timeout in test")
   821  
   822  	aTc.G.API = origAPI
   823  	require.NoError(t, auditTeam(aA, aM, teamID), "A can audit")
   824  	team, err := Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true})
   825  	require.NoError(t, err)
   826  	require.Equal(t, keybase1.PerTeamKeyGeneration(1), team.Generation())
   827  }