github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/kbfs/tlfhandle/resolve.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package tlfhandle 6 7 // This file has the online resolving functionality for TlfHandles. 8 9 import ( 10 "fmt" 11 "reflect" 12 "sort" 13 "strings" 14 15 "github.com/keybase/client/go/kbfs/idutil" 16 "github.com/keybase/client/go/kbfs/kbfscodec" 17 "github.com/keybase/client/go/kbfs/kbfsmd" 18 "github.com/keybase/client/go/kbfs/tlf" 19 kbname "github.com/keybase/client/go/kbun" 20 "github.com/keybase/client/go/libkb" 21 "github.com/keybase/client/go/logger" 22 "github.com/keybase/client/go/protocol/keybase1" 23 "github.com/pkg/errors" 24 "golang.org/x/net/context" 25 ) 26 27 type nameIDPair struct { 28 name kbname.NormalizedUsername 29 id keybase1.UserOrTeamID 30 } 31 32 type resolvableUser interface { 33 // resolve must do exactly one of the following: 34 // 35 // - return a non-zero nameIDPair; 36 // - return a non-zero keybase1.SocialAssertion; 37 // - return a non-nil error. 38 // 39 // The TLF ID may or may not be filled in, depending on whether 40 // the name is backed by an implicit team. 41 resolve(context.Context) ( 42 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) 43 } 44 45 func resolveOneUser( 46 ctx context.Context, user resolvableUser, 47 errCh chan<- error, userInfoResults chan<- nameIDPair, 48 socialAssertionResults chan<- keybase1.SocialAssertion, 49 idResults chan<- tlf.ID) { 50 userInfo, socialAssertion, id, err := user.resolve(ctx) 51 if err != nil { 52 select { 53 case errCh <- err: 54 default: 55 // another worker reported an error before us; 56 // first one wins 57 } 58 return 59 } 60 61 // The ID has to be sent first, to guarantee it's in the channel 62 // before it is closed. 63 if id != tlf.NullID { 64 select { 65 case idResults <- id: 66 default: 67 errCh <- fmt.Errorf( 68 "Sending ID %s failed; one ID has probably already been sent", 69 id) 70 return 71 } 72 } 73 74 if userInfo != (nameIDPair{}) { 75 userInfoResults <- userInfo 76 return 77 } 78 79 if socialAssertion != (keybase1.SocialAssertion{}) { 80 socialAssertionResults <- socialAssertion 81 return 82 } 83 84 errCh <- fmt.Errorf("Resolving %v resulted in empty userInfo and empty socialAssertion", user) 85 } 86 87 func getNames(idToName map[keybase1.UserOrTeamID]kbname.NormalizedUsername) []kbname.NormalizedUsername { 88 var names []kbname.NormalizedUsername 89 for _, name := range idToName { 90 names = append(names, name) 91 } 92 return names 93 } 94 95 func makeHandleHelper( 96 ctx context.Context, t tlf.Type, writers, readers []resolvableUser, 97 extensions []tlf.HandleExtension, idGetter IDGetter) ( 98 *Handle, error) { 99 if t != tlf.Private && len(readers) > 0 { 100 return nil, errors.New("public or team folder cannot have readers") 101 } else if t == tlf.SingleTeam && len(writers) != 1 { 102 return nil, errors.New("team folder cannot have more than one writer") 103 } 104 105 // parallelize the resolutions for each user 106 ctx, cancel := context.WithCancel(ctx) 107 defer cancel() 108 109 errCh := make(chan error, 1) 110 111 wc := make(chan nameIDPair, len(writers)) 112 uwc := make(chan keybase1.SocialAssertion, len(writers)) 113 // We are only expecting at most one ID. `resolveOneUser` should 114 // error if it can't send an ID immediately. 115 idc := make(chan tlf.ID, 1) 116 for _, writer := range writers { 117 go resolveOneUser(ctx, writer, errCh, wc, uwc, idc) 118 } 119 120 rc := make(chan nameIDPair, len(readers)) 121 urc := make(chan keybase1.SocialAssertion, len(readers)) 122 for _, reader := range readers { 123 go resolveOneUser(ctx, reader, errCh, rc, urc, idc) 124 } 125 126 usedWNames := 127 make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(writers)) 128 usedRNames := 129 make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(readers)) 130 usedUnresolvedWriters := make(map[keybase1.SocialAssertion]bool) 131 usedUnresolvedReaders := make(map[keybase1.SocialAssertion]bool) 132 for i := 0; i < len(writers)+len(readers); i++ { 133 select { 134 case err := <-errCh: 135 return nil, err 136 case userInfo := <-wc: 137 usedWNames[userInfo.id] = userInfo.name 138 case userInfo := <-rc: 139 usedRNames[userInfo.id] = userInfo.name 140 case socialAssertion := <-uwc: 141 usedUnresolvedWriters[socialAssertion] = true 142 case socialAssertion := <-urc: 143 usedUnresolvedReaders[socialAssertion] = true 144 case <-ctx.Done(): 145 return nil, ctx.Err() 146 } 147 } 148 // It's safe to close the channel now before we receive from it, 149 // since the ID is always sent first, before the usernames and 150 // assertions. 151 close(idc) 152 tlfID := tlf.NullID 153 more := false 154 select { 155 case tlfID, more = <-idc: 156 default: 157 } 158 159 if more { 160 // Just make sure a second one didn't slip in (only possible if 161 // `resolveOneUser` has a bug). 162 select { 163 case tlfID2, more := <-idc: 164 if more { 165 return nil, fmt.Errorf( 166 "More than one TLF ID returned: %s and %s", tlfID, tlfID2) 167 } 168 default: 169 } 170 } 171 172 for id := range usedWNames { 173 delete(usedRNames, id) 174 } 175 176 for sa := range usedUnresolvedWriters { 177 delete(usedUnresolvedReaders, sa) 178 } 179 180 unresolvedWriters := getSortedUnresolved(usedUnresolvedWriters) 181 182 var unresolvedReaders []keybase1.SocialAssertion 183 if t == tlf.Private { 184 unresolvedReaders = getSortedUnresolved(usedUnresolvedReaders) 185 } 186 187 extensionList := tlf.HandleExtensionList(extensions) 188 sort.Sort(extensionList) 189 conflictInfo, finalizedInfo := extensionList.Splat() 190 191 isImplicit := false 192 if t != tlf.SingleTeam && len(writers) == 1 && len(readers) == 0 { 193 // There's only one ID, but iterating is the only good way to 194 // get it out of the map. 195 for id := range usedWNames { 196 isImplicit = id.IsTeam() 197 } 198 } 199 200 var canonicalName tlf.CanonicalName 201 if isImplicit || t == tlf.SingleTeam { 202 canonicalName = tlf.MakeCanonicalNameForTeam( 203 getNames(usedWNames), unresolvedWriters, 204 getNames(usedRNames), unresolvedReaders, extensions) 205 } else { 206 canonicalName = tlf.MakeCanonicalName( 207 getNames(usedWNames), unresolvedWriters, 208 getNames(usedRNames), unresolvedReaders, extensions) 209 } 210 211 switch t { 212 case tlf.Private: 213 // All writers and readers must be users. 214 for id := range usedWNames { 215 if !isImplicit && !id.IsUser() { 216 return nil, NoSuchNameError{Name: string(canonicalName)} 217 } 218 } 219 for id := range usedRNames { 220 if !id.IsUser() { 221 return nil, NoSuchNameError{Name: string(canonicalName)} 222 } 223 } 224 case tlf.Public: 225 // All writers must be users. 226 for id := range usedWNames { 227 if !isImplicit && !id.IsUser() { 228 return nil, NoSuchNameError{Name: string(canonicalName)} 229 } 230 } 231 case tlf.SingleTeam: 232 // The writer must be a team. 233 for id := range usedWNames { 234 if !id.IsTeamOrSubteam() { 235 return nil, NoSuchNameError{Name: string(canonicalName)} 236 } 237 } 238 default: 239 panic(fmt.Sprintf("Unknown TLF type: %s", t)) 240 } 241 242 h := &Handle{ 243 tlfType: t, 244 resolvedWriters: usedWNames, 245 resolvedReaders: usedRNames, 246 unresolvedWriters: unresolvedWriters, 247 unresolvedReaders: unresolvedReaders, 248 conflictInfo: conflictInfo, 249 finalizedInfo: finalizedInfo, 250 name: canonicalName, 251 tlfID: tlfID, 252 } 253 254 needIDLookup := (!isImplicit && h.tlfID == tlf.NullID) || 255 (conflictInfo != nil && 256 conflictInfo.Type == tlf.HandleExtensionLocalConflict) 257 if needIDLookup && idGetter != nil { 258 // If this isn't an implicit team yet, look up possible 259 // pre-existing TLF ID from the mdserver. If this is a local 260 // conflict branch, look up the fake TLF ID from the journal. 261 tlfID, err := idGetter.GetIDForHandle(ctx, h) 262 if err != nil { 263 return nil, err 264 } 265 h.tlfID = tlfID 266 } 267 268 if h.tlfID != tlf.NullID && h.tlfID.Type() != t { 269 return nil, fmt.Errorf("ID type=%s doesn't match expected type=%s", 270 h.tlfID.Type(), t) 271 } 272 273 return h, nil 274 } 275 276 type resolvableID struct { 277 resolver idutil.Resolver 278 idGetter IDGetter 279 nug idutil.NormalizedUsernameGetter 280 id keybase1.UserOrTeamID 281 tlfType tlf.Type 282 offline keybase1.OfflineAvailability 283 } 284 285 func (ruid resolvableID) resolve(ctx context.Context) ( 286 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 287 if doResolveImplicit(ctx, ruid.tlfType) && ruid.id.IsTeamOrSubteam() { 288 // First check if this is an implicit team. 289 iteamInfo, err := ruid.resolver.ResolveImplicitTeamByID( 290 ctx, ruid.id.AsTeamOrBust(), ruid.tlfType, ruid.offline) 291 switch errors.Cause(err).(type) { 292 case nil: 293 if ruid.id != iteamInfo.TID.AsUserOrTeam() { 294 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, 295 fmt.Errorf("Implicit team ID %s doesn't match ID in "+ 296 "handle %s", iteamInfo.TID, ruid.id) 297 } 298 299 return nameIDPair{ 300 name: iteamInfo.Name, 301 id: iteamInfo.TID.AsUserOrTeam(), 302 }, keybase1.SocialAssertion{}, iteamInfo.TlfID, nil 303 case libkb.AppStatusError: 304 // This could indicate a temporary error, like a network 305 // failure. So fail it; the user should see a failure 306 // rather than possibly attempting to look up an implicit 307 // team through the path below. See HOTPOT-1698. 308 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 309 default: 310 // Fallthrough to the classic TLF case. Normally this 311 // would be an error string that looks like: "Operation 312 // only allowed on implicit teams: 313 // MapImplicitTeamIDToDisplayName". TODO: make that an 314 // exported error and treat only it specially. 315 } 316 } 317 318 // If not, resolve it the normal way, and assume it's a classic 319 // TLF. 320 name, err := ruid.nug.GetNormalizedUsername(ctx, ruid.id, ruid.offline) 321 if err != nil { 322 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 323 } 324 var tlfID tlf.ID 325 // Only get a team TLF ID if the caller expects to use it. 326 if ruid.idGetter != nil && ruid.id.IsTeamOrSubteam() { 327 tlfID, err = ruid.resolver.ResolveTeamTLFID( 328 ctx, ruid.id.AsTeamOrBust(), ruid.offline) 329 if err != nil { 330 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 331 } 332 } 333 return nameIDPair{ 334 name: name, 335 id: ruid.id, 336 }, keybase1.SocialAssertion{}, tlfID, nil 337 } 338 339 type resolvableSocialAssertion keybase1.SocialAssertion 340 341 func (rsa resolvableSocialAssertion) resolve(ctx context.Context) ( 342 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 343 return nameIDPair{}, keybase1.SocialAssertion(rsa), tlf.NullID, nil 344 } 345 346 // MakeHandle creates a Handle from the given tlf.Handle and the 347 // given NormalizedUsernameGetter (which is usually a KBPKI). `t` is 348 // the `tlf.Type` of the new handle. (Note this could be different 349 // from `bareHandle.Type()`, if this is an implicit team TLF.) 350 func MakeHandle( 351 ctx context.Context, bareHandle tlf.Handle, t tlf.Type, 352 resolver idutil.Resolver, nug idutil.NormalizedUsernameGetter, 353 idGetter IDGetter, offline keybase1.OfflineAvailability) ( 354 *Handle, error) { 355 writers := make([]resolvableUser, 0, len(bareHandle.Writers)+len(bareHandle.UnresolvedWriters)) 356 for _, w := range bareHandle.Writers { 357 writers = append( 358 writers, resolvableID{resolver, idGetter, nug, w, t, offline}) 359 } 360 for _, uw := range bareHandle.UnresolvedWriters { 361 writers = append(writers, resolvableSocialAssertion(uw)) 362 } 363 364 var readers []resolvableUser 365 if bareHandle.Type() == tlf.Private { 366 readers = make([]resolvableUser, 0, len(bareHandle.Readers)+len(bareHandle.UnresolvedReaders)) 367 for _, r := range bareHandle.Readers { 368 readers = append( 369 readers, resolvableID{resolver, idGetter, nug, r, t, offline}) 370 } 371 for _, ur := range bareHandle.UnresolvedReaders { 372 readers = append(readers, resolvableSocialAssertion(ur)) 373 } 374 } 375 376 h, err := makeHandleHelper( 377 ctx, t, writers, readers, bareHandle.Extensions(), idGetter) 378 if err != nil { 379 return nil, err 380 } 381 382 newHandle, err := h.ToBareHandle() 383 if err != nil { 384 return nil, err 385 } 386 if !reflect.DeepEqual(newHandle, bareHandle) { 387 panic(fmt.Errorf("newHandle=%+v unexpectedly not equal to bareHandle=%+v", newHandle, bareHandle)) 388 } 389 390 return h, nil 391 } 392 393 // MakeHandleWithTlfID is like `MakeHandle`, but it ensures the 394 // handle's TLF ID is always set to `tlfID`, even if it corresponds to 395 // an implicit team for which the iteam settings don't yet contain a 396 // TLF ID (due to a cross-device race or certain testing scenarios). 397 func MakeHandleWithTlfID( 398 ctx context.Context, bareHandle tlf.Handle, t tlf.Type, 399 resolver idutil.Resolver, nug idutil.NormalizedUsernameGetter, 400 tlfID tlf.ID, offline keybase1.OfflineAvailability) ( 401 *Handle, error) { 402 handle, err := MakeHandle( 403 ctx, bareHandle, t, resolver, nug, ConstIDGetter{ID: tlfID}, offline) 404 if err != nil { 405 return nil, err 406 } 407 if handle.TlfID() == tlf.NullID { 408 // In cases (mostly in testing) where the iteam settings don't 409 // yet contain the TLF ID, the idGetter will never even get 410 // called, so we need to set it manually here. 411 handle.SetTlfID(tlfID) 412 } 413 return handle, nil 414 } 415 416 type resolvableNameUIDPair nameIDPair 417 418 func (rp resolvableNameUIDPair) resolve(ctx context.Context) ( 419 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 420 return nameIDPair(rp), keybase1.SocialAssertion{}, tlf.NullID, nil 421 } 422 423 // ResolveAgainForUser tries to resolve any unresolved assertions in 424 // the given handle and returns a new handle with the results. As an 425 // optimization, if h contains no unresolved assertions, it just 426 // returns itself. If uid != keybase1.UID(""), it only allows 427 // assertions that resolve to uid. 428 func (h *Handle) ResolveAgainForUser( 429 ctx context.Context, resolver idutil.Resolver, idGetter IDGetter, 430 osg idutil.OfflineStatusGetter, uid keybase1.UID) (*Handle, error) { 431 if len(h.unresolvedWriters)+len(h.unresolvedReaders) == 0 { 432 return h, nil 433 } 434 435 offline := keybase1.OfflineAvailability_NONE 436 if osg != nil { 437 offline = osg.OfflineAvailabilityForID(h.tlfID) 438 } 439 440 writers := make([]resolvableUser, 0, len(h.resolvedWriters)+len(h.unresolvedWriters)) 441 for uid, w := range h.resolvedWriters { 442 writers = append(writers, resolvableNameUIDPair{w, uid}) 443 } 444 for _, uw := range h.unresolvedWriters { 445 writers = append(writers, resolvableAssertion{ 446 resolver, nil, idGetter, uw.String(), uid, offline}) 447 } 448 449 var readers []resolvableUser 450 if h.Type() == tlf.Private { 451 readers = make([]resolvableUser, 0, len(h.resolvedReaders)+len(h.unresolvedReaders)) 452 for uid, r := range h.resolvedReaders { 453 readers = append(readers, resolvableNameUIDPair{r, uid}) 454 } 455 for _, ur := range h.unresolvedReaders { 456 readers = append(readers, resolvableAssertion{ 457 resolver, nil, idGetter, ur.String(), uid, offline}) 458 } 459 } 460 461 newH, err := makeHandleHelper( 462 ctx, h.Type(), writers, readers, h.Extensions(), idGetter) 463 if err != nil { 464 return nil, err 465 } 466 467 return newH, nil 468 } 469 470 // ResolveAgain tries to resolve any unresolved assertions in the 471 // given handle and returns a new handle with the results. As an 472 // optimization, if h contains no unresolved assertions, it just 473 // returns itself. 474 func (h *Handle) ResolveAgain( 475 ctx context.Context, resolver idutil.Resolver, idGetter IDGetter, 476 osg idutil.OfflineStatusGetter) (*Handle, error) { 477 if h.IsFinal() { 478 // Don't attempt to further resolve final handles. 479 return h, nil 480 } 481 return h.ResolveAgainForUser( 482 ctx, resolver, idGetter, osg, keybase1.UID("")) 483 } 484 485 type partialResolver struct { 486 idutil.Resolver 487 unresolvedAssertions map[string]bool 488 } 489 490 func (pr partialResolver) Resolve( 491 ctx context.Context, assertion string, 492 offline keybase1.OfflineAvailability) ( 493 kbname.NormalizedUsername, keybase1.UserOrTeamID, error) { 494 if pr.unresolvedAssertions[assertion] { 495 // Force an unresolved assertion. 496 return kbname.NormalizedUsername(""), 497 keybase1.UserOrTeamID(""), idutil.NoSuchUserError{Input: assertion} 498 } 499 return pr.Resolver.Resolve(ctx, assertion, offline) 500 } 501 502 // ResolvesTo returns whether this handle resolves to the given one. 503 // It also returns the partially-resolved version of h, i.e. h 504 // resolved except for unresolved assertions in other; this should 505 // equal other if and only if true is returned. 506 func (h Handle) ResolvesTo( 507 ctx context.Context, codec kbfscodec.Codec, resolver idutil.Resolver, 508 idGetter IDGetter, osg idutil.OfflineStatusGetter, other Handle) ( 509 resolvesTo bool, partialResolvedH *Handle, err error) { 510 // Check the conflict extension. 511 var conflictAdded, finalizedAdded bool 512 if (h.IsConflict() && other.IsLocalConflict()) || 513 (h.IsLocalConflict() && other.IsConflict()) { 514 return false, nil, errors.New( 515 "Can't transition between conflict and local conflict") 516 } else if (!h.IsConflict() && other.IsConflict()) || 517 (!h.IsLocalConflict() && other.IsLocalConflict()) { 518 conflictAdded = true 519 // Ignore the added extension for resolution comparison purposes. 520 other.conflictInfo = nil 521 } 522 523 // Check the finalized extension. 524 if h.IsFinal() { 525 if conflictAdded { 526 // Can't add conflict info to a finalized handle. 527 return false, nil, HandleFinalizedError{} 528 } 529 } else if other.IsFinal() { 530 finalizedAdded = true 531 // Ignore the added extension for resolution comparison purposes. 532 other.finalizedInfo = nil 533 } 534 535 if h.TypeForKeying() == tlf.TeamKeying { 536 // Nothing to resolve for team-based TLFs, just use `other` by 537 // itself. 538 partialResolvedH = other.DeepCopy() 539 } else { 540 unresolvedAssertions := make(map[string]bool) 541 for _, uw := range other.unresolvedWriters { 542 unresolvedAssertions[uw.String()] = true 543 } 544 for _, ur := range other.unresolvedReaders { 545 unresolvedAssertions[ur.String()] = true 546 } 547 548 // TODO: Once we keep track of the original assertions in 549 // Handle, restrict the resolver to use other's assertions 550 // only, so that we don't hit the network at all. 551 partialResolvedH, err = h.ResolveAgain( 552 ctx, partialResolver{resolver, unresolvedAssertions}, idGetter, 553 osg) 554 if err != nil { 555 return false, nil, err 556 } 557 558 // If we're migrating, use the partially-resolved handle's 559 // list of writers, readers, and conflict info, rather than 560 // explicitly checking team membership. This is assuming that 561 // we've already validated the migrating folder's name against 562 // the user list in the current MD head (done via 563 // `MDOps.GetIDForHandle()`). 564 if other.TypeForKeying() == tlf.TeamKeying { 565 if h.IsFinal() { 566 return false, nil, 567 errors.New("Can't migrate a finalized folder") 568 } 569 570 other.resolvedWriters = partialResolvedH.resolvedWriters 571 other.resolvedReaders = partialResolvedH.resolvedReaders 572 other.unresolvedWriters = partialResolvedH.unresolvedWriters 573 other.unresolvedReaders = partialResolvedH.unresolvedReaders 574 other.conflictInfo = partialResolvedH.conflictInfo 575 } 576 } 577 578 if conflictAdded || finalizedAdded { 579 resolvesTo, err = partialResolvedH.EqualsIgnoreName(codec, other) 580 } else { 581 resolvesTo, err = partialResolvedH.Equals(codec, other) 582 } 583 if err != nil { 584 return false, nil, err 585 } 586 587 return resolvesTo, partialResolvedH, nil 588 } 589 590 // MutuallyResolvesTo checks that the target handle, and the provided 591 // `other` handle, resolve to each other. 592 func (h Handle) MutuallyResolvesTo( 593 ctx context.Context, codec kbfscodec.Codec, 594 resolver idutil.Resolver, idGetter IDGetter, 595 osg idutil.OfflineStatusGetter, other Handle, rev kbfsmd.Revision, 596 tlfID tlf.ID, log logger.Logger) error { 597 handleResolvesToOther, partialResolvedHandle, err := 598 h.ResolvesTo(ctx, codec, resolver, idGetter, osg, other) 599 if err != nil { 600 return err 601 } 602 603 // TODO: If h has conflict info, other should, too. 604 otherResolvesToHandle, partialResolvedOther, err := 605 other.ResolvesTo(ctx, codec, resolver, idGetter, osg, h) 606 if err != nil { 607 return err 608 } 609 610 handlePath := h.GetCanonicalPath() 611 otherPath := other.GetCanonicalPath() 612 if !handleResolvesToOther && !otherResolvesToHandle { 613 return HandleMismatchError{ 614 rev, h.GetCanonicalPath(), tlfID, 615 fmt.Errorf( 616 "MD contained unexpected handle path %s (%s -> %s) (%s -> %s)", 617 otherPath, 618 h.GetCanonicalPath(), 619 partialResolvedHandle.GetCanonicalPath(), 620 other.GetCanonicalPath(), 621 partialResolvedOther.GetCanonicalPath()), 622 } 623 } 624 625 if handlePath != otherPath { 626 log.CDebugf(ctx, "handle for %s resolved to %s", 627 handlePath, otherPath) 628 } 629 return nil 630 } 631 632 type resolvableAssertionWithChangeReport struct { 633 resolvableAssertion 634 changed chan struct{} 635 } 636 637 func (ra resolvableAssertionWithChangeReport) resolve(ctx context.Context) ( 638 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 639 nuid, sa, tlfID, err := ra.resolvableAssertion.resolve(ctx) 640 if err != nil { 641 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 642 } 643 sendIfPossible := func() { 644 select { 645 case ra.changed <- struct{}{}: 646 default: 647 } 648 } 649 if nuid.name.String() != "" { 650 if nuid.name.String() != strings.TrimPrefix(ra.assertion, "team:") { 651 sendIfPossible() 652 } 653 } else if sa != (keybase1.SocialAssertion{}) { 654 if sa.String() != ra.assertion { 655 sendIfPossible() 656 } 657 } 658 return nuid, sa, tlfID, nil 659 } 660 661 type resolvableAssertion struct { 662 resolver idutil.Resolver 663 identifier idutil.Identifier // only needed until KBFS-2022 is fixed 664 idGetter IDGetter 665 assertion string 666 mustBeUser keybase1.UID 667 offline keybase1.OfflineAvailability 668 } 669 670 func (ra resolvableAssertion) resolve(ctx context.Context) ( 671 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 672 if ra.assertion == idutil.PublicUIDName { 673 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, 674 fmt.Errorf("Invalid name %s", ra.assertion) 675 } 676 name, id, err := ra.resolver.Resolve(ctx, ra.assertion, ra.offline) 677 if err == nil && ra.mustBeUser != keybase1.UID("") && 678 ra.mustBeUser.AsUserOrTeam() != id { 679 // Force an unresolved assertion sinced the forced user doesn't match 680 err = idutil.NoSuchUserError{Input: ra.assertion} 681 } 682 // The service's Resolve2 doesn't handle compound assertions 683 // correctly because it would rely too much on server trust. It 684 // just resolves the first component, so something like 685 // "strib+therealdonaldtrump@twitter" would actually resolve 686 // correctly. So we need to do an explicit identify in that case, 687 // at least until KBFS-2022 is finished. Note that this 688 // explicitly avoids checking any of the extended identify context 689 // info, since we need to do this regardless of what the 690 // originator wants. 691 if strings.Contains(ra.assertion, "+") { 692 if ra.identifier == nil { 693 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, 694 errors.New( 695 "Can't resolve an AND assertion without an identifier") 696 } 697 reason := fmt.Sprintf("You accessed a folder with %s.", ra.assertion) 698 var resName kbname.NormalizedUsername 699 resName, err = IdentifySingleAssertion( 700 ctx, ra.assertion, reason, ra.identifier, ra.offline) 701 if err == nil && resName != name { 702 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, 703 fmt.Errorf( 704 "Resolved name %s doesn't match identified name %s for "+ 705 "assertion %s", name, resName, ra.assertion) 706 } 707 } 708 switch err := err.(type) { 709 default: 710 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 711 case nil: 712 var tlfID tlf.ID 713 // Only get a team TLF ID if the caller expects to use it. 714 if ra.idGetter != nil && id.IsTeamOrSubteam() { 715 tlfID, err = ra.resolver.ResolveTeamTLFID( 716 ctx, id.AsTeamOrBust(), ra.offline) 717 if err != nil { 718 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 719 } 720 } 721 722 return nameIDPair{ 723 name: name, 724 id: id, 725 }, keybase1.SocialAssertion{}, tlfID, nil 726 case idutil.NoSuchUserError: 727 socialAssertion, serr := ra.resolver.NormalizeSocialAssertion(ctx, ra.assertion) 728 if serr != nil { 729 // NOTE: we return the original `err` here since callers depend on 730 // the `NoSuchUserError` type. 731 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 732 } 733 return nameIDPair{}, socialAssertion, tlf.NullID, nil 734 } 735 } 736 737 type resolvableImplicitTeam struct { 738 resolver idutil.Resolver 739 name string 740 tlfType tlf.Type 741 offline keybase1.OfflineAvailability 742 } 743 744 func (rit resolvableImplicitTeam) resolve(ctx context.Context) ( 745 nameIDPair, keybase1.SocialAssertion, tlf.ID, error) { 746 // Need to separate the extension from the assertions. 747 assertions, extensionSuffix, err := tlf.SplitExtension(rit.name) 748 if err != nil { 749 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 750 } 751 752 iteamInfo, err := rit.resolver.ResolveImplicitTeam( 753 ctx, assertions, extensionSuffix, rit.tlfType, rit.offline) 754 if err != nil { 755 return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err 756 } 757 758 return nameIDPair{ 759 name: iteamInfo.Name, 760 id: iteamInfo.TID.AsUserOrTeam(), 761 }, keybase1.SocialAssertion{}, iteamInfo.TlfID, nil 762 } 763 764 func doResolveImplicit(_ context.Context, t tlf.Type) bool { 765 return t != tlf.SingleTeam 766 } 767 768 // parseHandleLoose parses a TLF handle but leaves some of the canonicality 769 // checking to public routines like ParseHandle and ParseHandlePreferred. 770 func parseHandleLoose( 771 ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter, 772 osg idutil.OfflineStatusGetter, name string, t tlf.Type) ( 773 *Handle, error) { 774 writerNames, readerNames, extensionSuffix, err := 775 idutil.SplitAndNormalizeTLFName(name, t) 776 if err != nil { 777 return nil, err 778 } 779 780 var extensions []tlf.HandleExtension 781 if len(extensionSuffix) != 0 { 782 extensions, err = tlf.ParseHandleExtensionSuffix(extensionSuffix) 783 if err != nil { 784 return nil, err 785 } 786 } 787 788 offline := keybase1.OfflineAvailability_NONE 789 if osg != nil { 790 normalizedName, _, err := idutil.NormalizeNamesInTLF( 791 writerNames, readerNames, t, extensionSuffix) 792 if err != nil { 793 return nil, err 794 } 795 offline = osg.OfflineAvailabilityForPath( 796 BuildCanonicalPathForTlfName(t, tlf.CanonicalName(normalizedName))) 797 798 // Make sure we always pass the normalized name to the 799 // service, so it can use its cache effectively. 800 name = normalizedName 801 } 802 803 // First try resolving this full name as an implicit team. If 804 // that doesn't work, fall through to individual name resolution. 805 var iteamHandle *Handle 806 if doResolveImplicit(ctx, t) { 807 // The service doesn't know about local conflict branches, so 808 // strip those out before resolving if needed. 809 assertions, extensionSuffix, err := tlf.SplitExtension(name) 810 if err != nil { 811 return nil, err 812 } 813 814 iteamName := name 815 var iteamExtensions tlf.HandleExtensionList 816 if tlf.ContainsLocalConflictExtensionPrefix(extensionSuffix) { 817 iteamName = assertions 818 iteamExtensions = tlf.HandleExtensionList(extensions) 819 } 820 821 rit := resolvableImplicitTeam{kbpki, iteamName, t, offline} 822 iteamHandle, err = makeHandleHelper( 823 ctx, t, []resolvableUser{rit}, nil, iteamExtensions, idGetter) 824 if err == nil && iteamHandle.tlfID != tlf.NullID { 825 // The iteam already has a TLF ID, let's use it. 826 827 extensionList := tlf.HandleExtensionList(extensions) 828 sort.Sort(extensionList) 829 _, finalizedInfo := extensionList.Splat() 830 831 if finalizedInfo == nil && idGetter != nil { 832 // Implicit team chat migration might have set the ID to an 833 // old folder that has since been reset. Check with the 834 // server to see if that has happened, and if so, don't use 835 // that ID. When we migrate existing TLFs to iteams, we can 836 // probably override the sigchain link with a new one 837 // containing the correct TLF ID. 838 valid, err := idGetter.ValidateLatestHandleNotFinal( 839 ctx, iteamHandle) 840 if err != nil { 841 return nil, err 842 } 843 if !valid { 844 iteamHandle.tlfID = tlf.NullID 845 } 846 } 847 848 if iteamHandle.tlfID != tlf.NullID { 849 return iteamHandle, nil 850 } 851 852 } else { 853 switch err.(type) { 854 case libkb.TeamContactSettingsBlockError: 855 // The implicit team couldn't be created due to one of the 856 // users' privacy settings, so fail the handle lookup completely. 857 return nil, err 858 default: 859 // This is not an implicit team, so continue on to check for a 860 // normal team. TODO: return non-nil errors immediately if they 861 // don't simply indicate the implicit team doesn't exist yet 862 // (i.e., when we start creating them by default). 863 } 864 } 865 } 866 867 // Before parsing the tlf handle (which results in identify 868 // calls that cause tracker popups), first see if there's any 869 // quick normalization of usernames we can do. For example, 870 // this avoids an identify in the case of "HEAD" which might 871 // just be a shell trying to look for a git repo rather than a 872 // real user lookup for "head" (KBFS-531). Note that the name 873 // might still contain assertions, which will result in 874 // another alias in a subsequent lookup. 875 // This also contains an offline check for canonicality and 876 // whether a public folder has readers. 877 changesCh := make(chan struct{}, 1) 878 writers := make([]resolvableUser, len(writerNames)) 879 for i, w := range writerNames { 880 if t == tlf.SingleTeam { 881 w = "team:" + w 882 } 883 writers[i] = resolvableAssertionWithChangeReport{resolvableAssertion{ 884 kbpki, kbpki, idGetter, w, keybase1.UID(""), offline}, changesCh} 885 } 886 readers := make([]resolvableUser, len(readerNames)) 887 for i, r := range readerNames { 888 readers[i] = resolvableAssertionWithChangeReport{resolvableAssertion{ 889 kbpki, kbpki, idGetter, r, keybase1.UID(""), offline}, changesCh} 890 } 891 892 h, err := makeHandleHelper( 893 ctx, t, writers, readers, extensions, idGetter) 894 if err != nil { 895 return nil, err 896 } 897 898 if h.tlfID == tlf.NullID && iteamHandle != nil { 899 // If the server hasn't provided a TLF ID for the regular 900 // handle, switch over to using the i-team handle (which 901 // currently must have a null TLF ID). Before the caller 902 // creates and uses the TLF, they must generate a TLF ID and 903 // store it in the i-team's sigchain. 904 return iteamHandle, nil 905 } 906 907 if t == tlf.Private { 908 session, err := kbpki.GetCurrentSession(ctx) 909 if err != nil { 910 return nil, err 911 } 912 913 if !h.IsReader(session.UID) { 914 return nil, NewReadAccessError(h, session.Name, h.GetCanonicalPath()) 915 } 916 } 917 918 canonicalName := string(h.GetCanonicalName()) 919 if extensionSuffix != "" { 920 extensionList := tlf.HandleExtensionList(extensions) 921 sort.Sort(extensionList) 922 var canonExtensionString string 923 // If this resolve is being done in a "quick" way that doesn't 924 // check for implicit teams, we might not know if this handle 925 // is backed by a team or not. But (mostly for tests) we need 926 // to make sure the suffix reflects the right format, 927 // otherwise we'll get too many levels of non-canonical 928 // redirects. So if the user explicitly specified a "#1" in 929 // their suffix, let's keep it there and assume the user knows 930 // what they're doing. 931 if h.IsBackedByTeam() || strings.Contains(extensionSuffix, "#1") { 932 canonExtensionString = extensionList.SuffixForTeamHandle() 933 } else { 934 canonExtensionString = extensionList.Suffix() 935 } 936 _, _, currExtensionSuffix, _ := 937 idutil.SplitAndNormalizeTLFName(canonicalName, t) 938 canonicalName = strings.Replace( 939 canonicalName, tlf.HandleExtensionSep+currExtensionSuffix, 940 canonExtensionString, 1) 941 h.name = tlf.CanonicalName(canonicalName) 942 if canonExtensionString != tlf.HandleExtensionSep+extensionSuffix { 943 return nil, errors.WithStack( 944 idutil.TlfNameNotCanonical{ 945 Name: name, 946 NameToTry: canonicalName, 947 }) 948 } 949 } 950 951 select { 952 case <-changesCh: 953 default: 954 // No changes were performed because of resolver. 955 return h, nil 956 } 957 958 // Otherwise, identify before returning the canonical name. 959 err = IdentifyHandle(ctx, kbpki, kbpki, osg, h) 960 if err != nil { 961 return nil, err 962 } 963 964 // In this case return both the handle and the error, 965 // ParseHandlePreferred uses this to make the redirection 966 // better. 967 return h, errors.WithStack(idutil.TlfNameNotCanonical{ 968 Name: name, 969 NameToTry: canonicalName, 970 }) 971 } 972 973 // ParseHandle parses a Handle from an encoded string. See 974 // Handle.GetCanonicalName() for the opposite direction. 975 // 976 // Some errors that may be returned and can be specially handled: 977 // 978 // idutil.TlfNameNotCanonical: Returned when the given name is not 979 // canonical -- another name to try (which itself may not be 980 // canonical) is in the error. Usually, you want to treat this as a 981 // symlink to the name to try. 982 // 983 // idutil.NoSuchNameError: Returned when public is set and the given 984 // folder has no public folder. 985 // 986 // TODO In future perhaps all code should switch over to preferred handles, 987 // and rename TlfNameNotCanonical to TlfNameNotPreferred. 988 func ParseHandle( 989 ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter, 990 osg idutil.OfflineStatusGetter, name string, t tlf.Type) ( 991 *Handle, error) { 992 h, err := parseHandleLoose(ctx, kbpki, idGetter, osg, name, t) 993 if err != nil { 994 return nil, err 995 } 996 if name != string(h.GetCanonicalName()) { 997 return nil, errors.WithStack( 998 idutil.TlfNameNotCanonical{ 999 Name: name, 1000 NameToTry: string(h.GetCanonicalName()), 1001 }) 1002 } 1003 return h, nil 1004 } 1005 1006 // ParseHandlePreferred returns idutil.TlfNameNotCanonical if not 1007 // in the preferred format. 1008 // Preferred format means that the users own username (from kbpki) 1009 // as a writer is put before other usernames in the tlf name. 1010 // i.e. 1011 // Canon Preferred 1012 // myname,other myname,other 1013 // another,myname myname,another 1014 // This function also can return idutil.NoSuchNameError or 1015 // idutil.TlfNameNotCanonical. idutil.TlfNameNotCanonical is 1016 // returned from this function when the name is not the *preferred* 1017 // name. 1018 func ParseHandlePreferred( 1019 ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter, 1020 osg idutil.OfflineStatusGetter, name string, t tlf.Type) ( 1021 *Handle, error) { 1022 h, err := parseHandleLoose(ctx, kbpki, idGetter, osg, name, t) 1023 // Return an early if there is an error, except in the case 1024 // where both h is not nil and it is a TlfNameNotCanonicalError. 1025 // In that case continue and return TlfNameNotCanonical later 1026 // with the right symlink target. 1027 if err != nil && (h == nil || !isTlfNameNotCanonical(err)) { 1028 return nil, err 1029 } 1030 session, err := idutil.GetCurrentSessionIfPossible( 1031 ctx, kbpki, h.Type() == tlf.Public) 1032 if err != nil { 1033 return nil, err 1034 } 1035 pref := h.GetPreferredFormat(session.Name) 1036 if string(pref) != name { 1037 return nil, errors.WithStack( 1038 idutil.TlfNameNotCanonical{ 1039 Name: name, 1040 NameToTry: string(pref), 1041 }) 1042 } 1043 return h, nil 1044 } 1045 1046 func isTlfNameNotCanonical(err error) bool { 1047 _, ok := errors.Cause(err).(idutil.TlfNameNotCanonical) 1048 return ok 1049 } 1050 1051 type noImplicitTeamKBPKI struct { 1052 idutil.KBPKI 1053 } 1054 1055 // ResolveImplicitTeam implements the KBPKI interface for noImplicitTeamKBPKI. 1056 func (nitk noImplicitTeamKBPKI) ResolveImplicitTeam( 1057 _ context.Context, _, _ string, _ tlf.Type, 1058 _ keybase1.OfflineAvailability) (idutil.ImplicitTeamInfo, error) { 1059 return idutil.ImplicitTeamInfo{}, 1060 errors.New("Skipping implicit team lookup for quick handle parsing") 1061 } 1062 1063 // ParseHandlePreferredQuick parses a handle from a name, without 1064 // doing this time consuming checks needed for implicit-team checking 1065 // or TLF-ID-fetching. 1066 func ParseHandlePreferredQuick( 1067 ctx context.Context, kbpki idutil.KBPKI, osg idutil.OfflineStatusGetter, 1068 name string, ty tlf.Type) (handle *Handle, err error) { 1069 // Override the KBPKI with one that doesn't try to resolve 1070 // implicit teams. 1071 kbpki = noImplicitTeamKBPKI{kbpki} 1072 return ParseHandlePreferred(ctx, kbpki, nil, osg, name, ty) 1073 }