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 }