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

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/keybase/client/go/jsonhelpers"
    13  	libkb "github.com/keybase/client/go/libkb"
    14  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  type ProveUIMock struct {
    20  	username, recheck, overwrite, warning, checked bool
    21  	postID                                         string
    22  	outputInstructionsHook                         func(context.Context, keybase1.OutputInstructionsArg) error
    23  	okToCheckHook                                  func(context.Context, keybase1.OkToCheckArg) (bool, string, error)
    24  	checkingHook                                   func(context.Context, keybase1.CheckingArg) error
    25  }
    26  
    27  func (p *ProveUIMock) PromptOverwrite(_ context.Context, arg keybase1.PromptOverwriteArg) (bool, error) {
    28  	p.overwrite = true
    29  	return true, nil
    30  }
    31  
    32  func (p *ProveUIMock) PromptUsername(_ context.Context, arg keybase1.PromptUsernameArg) (string, error) {
    33  	p.username = true
    34  	return "", nil
    35  }
    36  
    37  func (p *ProveUIMock) OutputPrechecks(_ context.Context, arg keybase1.OutputPrechecksArg) error {
    38  	return nil
    39  }
    40  
    41  func (p *ProveUIMock) PreProofWarning(_ context.Context, arg keybase1.PreProofWarningArg) (bool, error) {
    42  	p.warning = true
    43  	return true, nil
    44  }
    45  
    46  func (p *ProveUIMock) OutputInstructions(ctx context.Context, arg keybase1.OutputInstructionsArg) error {
    47  	if p.outputInstructionsHook != nil {
    48  		return p.outputInstructionsHook(ctx, arg)
    49  	}
    50  	return nil
    51  }
    52  
    53  func (p *ProveUIMock) OkToCheck(ctx context.Context, arg keybase1.OkToCheckArg) (bool, error) {
    54  	if !p.checked {
    55  		p.checked = true
    56  		ok, postID, err := p.okToCheckHook(ctx, arg)
    57  		p.postID = postID
    58  		return ok, err
    59  	}
    60  	return false, fmt.Errorf("Check should have worked the first time!")
    61  }
    62  
    63  func (p *ProveUIMock) Checking(ctx context.Context, arg keybase1.CheckingArg) (err error) {
    64  	if p.checkingHook != nil {
    65  		err = p.checkingHook(ctx, arg)
    66  	}
    67  	p.checked = true
    68  	return err
    69  }
    70  
    71  func (p *ProveUIMock) ContinueChecking(ctx context.Context, _ int) (bool, error) {
    72  	return true, nil
    73  }
    74  
    75  func (p *ProveUIMock) DisplayRecheckWarning(_ context.Context, arg keybase1.DisplayRecheckWarningArg) error {
    76  	p.recheck = true
    77  	return nil
    78  }
    79  
    80  func proveRooter(g *libkb.GlobalContext, fu *FakeUser, sigVersion libkb.SigVersion) (*ProveUIMock, keybase1.SigID, error) {
    81  	return proveRooterWithSecretUI(g, fu, fu.NewSecretUI(), sigVersion)
    82  }
    83  
    84  func proveRooterWithSecretUI(g *libkb.GlobalContext, fu *FakeUser, secretUI libkb.SecretUI, sigVersion libkb.SigVersion) (*ProveUIMock, keybase1.SigID, error) {
    85  	sv := keybase1.SigVersion(sigVersion)
    86  	arg := keybase1.StartProofArg{
    87  		Service:      "rooter",
    88  		Username:     fu.Username,
    89  		Force:        false,
    90  		PromptPosted: true,
    91  		SigVersion:   &sv,
    92  	}
    93  	eng := NewProve(g, &arg)
    94  
    95  	okToCheckHook := func(ctx context.Context, arg keybase1.OkToCheckArg) (bool, string, error) {
    96  		sigID := eng.sigID
    97  		if sigID.IsNil() {
    98  			return false, "", fmt.Errorf("empty sigID; can't make a post")
    99  		}
   100  		apiArg := libkb.APIArg{
   101  			Endpoint:    "rooter",
   102  			SessionType: libkb.APISessionTypeREQUIRED,
   103  			Args: libkb.HTTPArgs{
   104  				"post": libkb.S{Val: sigID.ToMediumID()},
   105  			},
   106  		}
   107  		res, err := g.API.Post(libkb.NewMetaContext(ctx, g), apiArg)
   108  		ok := err == nil
   109  		var postID string
   110  		if ok {
   111  			pid, err := res.Body.AtKey("post_id").GetString()
   112  			if err == nil {
   113  				postID = pid
   114  			}
   115  		}
   116  		return ok, postID, err
   117  	}
   118  
   119  	proveUI := &ProveUIMock{okToCheckHook: okToCheckHook}
   120  
   121  	uis := libkb.UIs{
   122  		LogUI:    g.UI.GetLogUI(),
   123  		SecretUI: secretUI,
   124  		ProveUI:  proveUI,
   125  	}
   126  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
   127  	err := RunEngine2(m, eng)
   128  	return proveUI, eng.sigID, err
   129  }
   130  
   131  func proveRooterFail(g *libkb.GlobalContext, fu *FakeUser, sigVersion libkb.SigVersion) (*ProveUIMock, error) {
   132  	sv := keybase1.SigVersion(sigVersion)
   133  	arg := keybase1.StartProofArg{
   134  		Service:      "rooter",
   135  		Username:     fu.Username,
   136  		Force:        false,
   137  		PromptPosted: true,
   138  		SigVersion:   &sv,
   139  	}
   140  
   141  	eng := NewProve(g, &arg)
   142  
   143  	okToCheckHook := func(ctx context.Context, arg keybase1.OkToCheckArg) (bool, string, error) {
   144  		apiArg := libkb.APIArg{
   145  			Endpoint:    "rooter",
   146  			SessionType: libkb.APISessionTypeREQUIRED,
   147  			Args: libkb.HTTPArgs{
   148  				"post": libkb.S{Val: "XXXXXXX"},
   149  			},
   150  		}
   151  		res, err := g.API.Post(libkb.NewMetaContext(ctx, g), apiArg)
   152  		ok := err == nil
   153  		var postID string
   154  		if ok {
   155  			pid, err := res.Body.AtKey("post_id").GetString()
   156  			if err == nil {
   157  				postID = pid
   158  			}
   159  		}
   160  		return ok, postID, err
   161  	}
   162  
   163  	proveUI := &ProveUIMock{okToCheckHook: okToCheckHook}
   164  
   165  	uis := libkb.UIs{
   166  		LogUI:    g.UI.GetLogUI(),
   167  		SecretUI: fu.NewSecretUI(),
   168  		ProveUI:  proveUI,
   169  	}
   170  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
   171  	err := RunEngine2(m, eng)
   172  	return proveUI, err
   173  }
   174  
   175  func proveRooterRemove(g *libkb.GlobalContext, postID string) error {
   176  	apiArg := libkb.APIArg{
   177  		Endpoint:    "rooter/delete",
   178  		SessionType: libkb.APISessionTypeREQUIRED,
   179  		Args: libkb.HTTPArgs{
   180  			"post_id": libkb.S{Val: postID},
   181  		},
   182  	}
   183  	_, err := g.API.Post(libkb.NewMetaContextTODO(g), apiArg)
   184  	return err
   185  }
   186  
   187  func proveRooterOther(g *libkb.GlobalContext, fu *FakeUser, rooterUsername string, sigVersion libkb.SigVersion) (*ProveUIMock, keybase1.SigID, error) {
   188  	sv := keybase1.SigVersion(sigVersion)
   189  	arg := keybase1.StartProofArg{
   190  		Service:      "rooter",
   191  		Username:     rooterUsername,
   192  		Force:        false,
   193  		PromptPosted: true,
   194  		SigVersion:   &sv,
   195  	}
   196  
   197  	eng := NewProve(g, &arg)
   198  
   199  	okToCheckHook := func(ctx context.Context, arg keybase1.OkToCheckArg) (bool, string, error) {
   200  		sigID := eng.sigID
   201  		if sigID.IsNil() {
   202  			return false, "", fmt.Errorf("empty sigID; can't make a post")
   203  		}
   204  		apiArg := libkb.APIArg{
   205  			Endpoint:    "rooter",
   206  			SessionType: libkb.APISessionTypeREQUIRED,
   207  			Args: libkb.HTTPArgs{
   208  				"post":     libkb.S{Val: sigID.ToMediumID()},
   209  				"username": libkb.S{Val: rooterUsername},
   210  			},
   211  		}
   212  		res, err := g.API.Post(libkb.NewMetaContext(ctx, g), apiArg)
   213  		ok := err == nil
   214  		var postID string
   215  		if ok {
   216  			pid, err := res.Body.AtKey("post_id").GetString()
   217  			if err == nil {
   218  				postID = pid
   219  			}
   220  		}
   221  		return ok, postID, err
   222  	}
   223  
   224  	proveUI := &ProveUIMock{okToCheckHook: okToCheckHook}
   225  
   226  	uis := libkb.UIs{
   227  		LogUI:    g.UI.GetLogUI(),
   228  		SecretUI: fu.NewSecretUI(),
   229  		ProveUI:  proveUI,
   230  	}
   231  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
   232  	err := RunEngine2(m, eng)
   233  	return proveUI, eng.sigID, err
   234  }
   235  
   236  func proveGubbleSocial(tc libkb.TestContext, fu *FakeUser, sigVersion libkb.SigVersion) keybase1.SigID {
   237  	return proveGubbleUniverse(tc, "gubble.social", "gubble_social", fu, sigVersion)
   238  }
   239  
   240  func proveGubbleCloud(tc libkb.TestContext, fu *FakeUser, sigVersion libkb.SigVersion) keybase1.SigID {
   241  	return proveGubbleUniverse(tc, "gubble.cloud", "gubble_cloud", fu, sigVersion)
   242  }
   243  
   244  func proveGubbleUniverse(tc libkb.TestContext, serviceName, endpoint string, fu *FakeUser, sigVersion libkb.SigVersion) keybase1.SigID {
   245  	tc.T.Logf("proof for %s", serviceName)
   246  	g := tc.G
   247  	sv := keybase1.SigVersion(sigVersion)
   248  	proofService := g.GetProofServices().GetServiceType(context.Background(), serviceName)
   249  	require.NotNil(tc.T, proofService)
   250  
   251  	// Post a proof to the testing generic social service
   252  	arg := keybase1.StartProofArg{
   253  		Service:      proofService.GetTypeName(),
   254  		Username:     fu.Username,
   255  		Force:        false,
   256  		PromptPosted: true,
   257  		SigVersion:   &sv,
   258  	}
   259  	eng := NewProve(g, &arg)
   260  
   261  	// Post the proof to the gubble network and verify the sig hash
   262  	outputInstructionsHook := func(ctx context.Context, _ keybase1.OutputInstructionsArg) error {
   263  		sigID := eng.sigID
   264  		require.False(tc.T, sigID.IsNil())
   265  		mctx := libkb.NewMetaContext(ctx, g)
   266  
   267  		apiArg := libkb.APIArg{
   268  			Endpoint:    fmt.Sprintf("gubble_universe/%s", endpoint),
   269  			SessionType: libkb.APISessionTypeREQUIRED,
   270  			Args: libkb.HTTPArgs{
   271  				"sig_hash":      libkb.S{Val: sigID.String()},
   272  				"username":      libkb.S{Val: fu.Username},
   273  				"kb_username":   libkb.S{Val: fu.Username},
   274  				"kb_ua":         libkb.S{Val: libkb.UserAgent},
   275  				"json_redirect": libkb.B{Val: true},
   276  			},
   277  		}
   278  		_, err := g.API.Post(libkb.NewMetaContext(ctx, g), apiArg)
   279  		require.NoError(tc.T, err)
   280  
   281  		apiArg = libkb.APIArg{
   282  			Endpoint:    fmt.Sprintf("gubble_universe/%s/%s/proofs", endpoint, fu.Username),
   283  			SessionType: libkb.APISessionTypeNONE,
   284  		}
   285  		res, err := g.GetAPI().Get(mctx, apiArg)
   286  		require.NoError(tc.T, err)
   287  		objects, err := jsonhelpers.AtSelectorPath(res.Body, []keybase1.SelectorEntry{
   288  			{
   289  				IsKey: true,
   290  				Key:   "res",
   291  			},
   292  			{
   293  				IsKey: true,
   294  				Key:   "keybase_proofs",
   295  			},
   296  		}, tc.T.Logf, libkb.NewInvalidPVLSelectorError)
   297  		require.NoError(tc.T, err)
   298  		require.Len(tc.T, objects, 1)
   299  
   300  		var proofs []keybase1.ParamProofJSON
   301  		err = objects[0].UnmarshalAgain(&proofs)
   302  		require.NoError(tc.T, err)
   303  		require.True(tc.T, len(proofs) >= 1)
   304  		for _, proof := range proofs {
   305  			if proof.KbUsername == fu.Username && sigID.Eq(proof.SigHash) {
   306  				return nil
   307  			}
   308  		}
   309  		assert.Fail(tc.T, "proof not found")
   310  		return nil
   311  	}
   312  
   313  	proveUI := &ProveUIMock{outputInstructionsHook: outputInstructionsHook}
   314  	uis := libkb.UIs{
   315  		LogUI:    g.UI.GetLogUI(),
   316  		SecretUI: fu.NewSecretUI(),
   317  		ProveUI:  proveUI,
   318  	}
   319  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
   320  	err := RunEngine2(m, eng)
   321  	checkFailed(tc.T.(testing.TB))
   322  	require.NoError(tc.T, err)
   323  	require.False(tc.T, proveUI.overwrite)
   324  	require.False(tc.T, proveUI.warning)
   325  	require.False(tc.T, proveUI.recheck)
   326  	require.True(tc.T, proveUI.checked)
   327  	return eng.sigID
   328  }
   329  
   330  func proveGubbleSocialFail(tc libkb.TestContext, fu *FakeUser, sigVersion libkb.SigVersion) {
   331  	g := tc.G
   332  	sv := keybase1.SigVersion(sigVersion)
   333  	proofService := g.GetProofServices().GetServiceType(context.Background(), "gubble.social")
   334  	require.NotNil(tc.T, proofService, "expected to find gubble.social service type")
   335  	arg := keybase1.StartProofArg{
   336  		Service:      proofService.GetTypeName(),
   337  		Username:     fu.Username,
   338  		Force:        false,
   339  		PromptPosted: true,
   340  		SigVersion:   &sv,
   341  	}
   342  
   343  	eng := NewProve(g, &arg)
   344  	proveUI := &ProveUIMock{}
   345  	uis := libkb.UIs{
   346  		LogUI:    g.UI.GetLogUI(),
   347  		SecretUI: fu.NewSecretUI(),
   348  		ProveUI:  proveUI,
   349  	}
   350  	mctx, cancel2 := libkb.NewMetaContextTODO(g).WithUIs(uis).WithContextCancel()
   351  	defer cancel2()
   352  
   353  	proveUI.checkingHook = func(_ context.Context, _ keybase1.CheckingArg) error {
   354  		if mctx.Ctx().Err() != nil {
   355  			// This is supposed to be the first thing to cancel the context.
   356  			assert.Fail(tc.T, "unexpectedly cancelled")
   357  		}
   358  		cancel2()
   359  		return nil
   360  	}
   361  
   362  	// This proof will never succeed, so the Prove engine would never stop of its own accord.
   363  	err := RunEngine2(mctx, eng)
   364  	require.Error(tc.T, err)
   365  	checkFailed(tc.T.(testing.TB))
   366  }
   367  
   368  func checkFailed(t testing.TB) {
   369  	if t.Failed() {
   370  		// The test failed. Possibly in anothe goroutine. Look earlier in the logs for the real failure.
   371  		require.FailNow(t, "test already failed")
   372  	}
   373  }