github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/tlfhandle/handle.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 type for TlfHandles and offline functionality. 8 9 import ( 10 "fmt" 11 "reflect" 12 "sort" 13 "strings" 14 15 "github.com/keybase/client/go/kbfs/favorites" 16 "github.com/keybase/client/go/kbfs/idutil" 17 "github.com/keybase/client/go/kbfs/kbfscodec" 18 "github.com/keybase/client/go/kbfs/tlf" 19 kbname "github.com/keybase/client/go/kbun" 20 "github.com/keybase/client/go/protocol/keybase1" 21 "github.com/pkg/errors" 22 "golang.org/x/net/context" 23 ) 24 25 // Handle contains all the info in a tlf.Handle as well as 26 // additional info. This doesn't embed tlf.Handle to avoid having to 27 // keep track of data in multiple places. 28 type Handle struct { 29 // If this is not Private, resolvedReaders and unresolvedReaders 30 // should both be nil. 31 tlfType tlf.Type 32 resolvedWriters map[keybase1.UserOrTeamID]kbname.NormalizedUsername 33 resolvedReaders map[keybase1.UserOrTeamID]kbname.NormalizedUsername 34 // Both unresolvedWriters and unresolvedReaders are stored in 35 // sorted order. 36 unresolvedWriters []keybase1.SocialAssertion 37 unresolvedReaders []keybase1.SocialAssertion 38 conflictInfo *tlf.HandleExtension 39 finalizedInfo *tlf.HandleExtension 40 // name can be computed from the other fields, but is cached 41 // for speed. 42 name tlf.CanonicalName 43 44 // If we know the TLF ID at the time this handle is constructed 45 // (e.g., because this handle is backed by an implicit team), we 46 // store the TLF ID here so that we can look the TLF up from the 47 // mdserver using the ID, instead of the handle. 48 tlfID tlf.ID 49 } 50 51 // NewHandle returns a simple new Handle based on the given fields. 52 // This is probably most useful for testing. 53 func NewHandle( 54 ty tlf.Type, 55 resolvedWriters map[keybase1.UserOrTeamID]kbname.NormalizedUsername, 56 unresolvedWriters, unresolvedReaders []keybase1.SocialAssertion, 57 name tlf.CanonicalName, tlfID tlf.ID) *Handle { 58 return &Handle{ 59 tlfType: ty, 60 resolvedWriters: resolvedWriters, 61 unresolvedWriters: unresolvedWriters, 62 unresolvedReaders: unresolvedReaders, 63 name: name, 64 tlfID: tlfID, 65 } 66 } 67 68 // Type returns the type of the TLF this TlfHandle represents. 69 func (h Handle) Type() tlf.Type { 70 return h.tlfType 71 } 72 73 // IsBackedByTeam returns true if h represents a TLF backed by a team. It could 74 // be either a SingleTeam TLF or a private/public TLF backed by an implicit 75 // team. 76 func (h Handle) IsBackedByTeam() bool { 77 if len(h.resolvedWriters) != 1 || 78 len(h.resolvedReaders) != 0 || 79 len(h.unresolvedReaders) != 0 || 80 len(h.unresolvedWriters) != 0 { 81 return false 82 } 83 return h.FirstResolvedWriter().IsTeamOrSubteam() 84 } 85 86 // TypeForKeying returns the keying type for the handle h. 87 func (h Handle) TypeForKeying() tlf.KeyingType { 88 if h.IsBackedByTeam() { 89 return tlf.TeamKeying 90 } 91 return h.Type().ToKeyingType() 92 } 93 94 // TlfID returns the TLF ID corresponding to this handle, if it's 95 // known. If it's wasn't known at the time the handle was 96 // constructed, tlf.NullID is returned. 97 func (h Handle) TlfID() tlf.ID { 98 return h.tlfID 99 } 100 101 // IsWriter returns whether or not the given user is a writer for the 102 // top-level folder represented by this TlfHandle. 103 func (h Handle) IsWriter(user keybase1.UID) bool { 104 // TODO(KBFS-2185) relax this? 105 if h.TypeForKeying() == tlf.TeamKeying { 106 panic("Can't check whether a user is a writer on a team TLF") 107 } 108 _, ok := h.resolvedWriters[user.AsUserOrTeam()] 109 return ok 110 } 111 112 // IsReader returns whether or not the given user is a reader for the 113 // top-level folder represented by this TlfHandle. 114 func (h Handle) IsReader(user keybase1.UID) bool { 115 // TODO(KBFS-2185) relax this? 116 if h.TypeForKeying() == tlf.TeamKeying { 117 panic("Can't check whether a user is a reader on a team TLF") 118 } 119 if h.TypeForKeying() == tlf.PublicKeying || h.IsWriter(user) { 120 return true 121 } 122 _, ok := h.resolvedReaders[user.AsUserOrTeam()] 123 return ok 124 } 125 126 // ResolvedUsersMap returns a map of resolved users from uid to usernames. 127 func (h Handle) ResolvedUsersMap() map[keybase1.UserOrTeamID]kbname.NormalizedUsername { 128 m := make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, 129 len(h.resolvedReaders)+len(h.resolvedWriters)) 130 for k, v := range h.resolvedReaders { 131 m[k] = v 132 } 133 for k, v := range h.resolvedWriters { 134 m[k] = v 135 } 136 return m 137 } 138 139 func (h Handle) unsortedResolvedWriters() []keybase1.UserOrTeamID { 140 if len(h.resolvedWriters) == 0 { 141 return nil 142 } 143 writers := make([]keybase1.UserOrTeamID, 0, len(h.resolvedWriters)) 144 for r := range h.resolvedWriters { 145 writers = append(writers, r) 146 } 147 return writers 148 } 149 150 // ResolvedWriters returns the handle's resolved writer IDs in sorted 151 // order. 152 func (h Handle) ResolvedWriters() []keybase1.UserOrTeamID { 153 writers := h.unsortedResolvedWriters() 154 sort.Sort(tlf.UIDList(writers)) 155 return writers 156 } 157 158 // FirstResolvedWriter returns the handle's first resolved writer ID 159 // (when sorted). For SingleTeam handles, this returns the team to 160 // which the TLF belongs. 161 func (h Handle) FirstResolvedWriter() keybase1.UserOrTeamID { 162 return h.ResolvedWriters()[0] 163 } 164 165 func (h Handle) unsortedResolvedReaders() []keybase1.UserOrTeamID { 166 if len(h.resolvedReaders) == 0 { 167 return nil 168 } 169 readers := make([]keybase1.UserOrTeamID, 0, len(h.resolvedReaders)) 170 for r := range h.resolvedReaders { 171 readers = append(readers, r) 172 } 173 return readers 174 } 175 176 // ResolvedReaders returns the handle's resolved reader IDs in sorted 177 // order. If the handle is public, nil will be returned. 178 func (h Handle) ResolvedReaders() []keybase1.UserOrTeamID { 179 readers := h.unsortedResolvedReaders() 180 sort.Sort(tlf.UIDList(readers)) 181 return readers 182 } 183 184 // UnresolvedWriters returns the handle's unresolved writers in sorted 185 // order. 186 func (h Handle) UnresolvedWriters() []keybase1.SocialAssertion { 187 if len(h.unresolvedWriters) == 0 { 188 return nil 189 } 190 unresolvedWriters := make([]keybase1.SocialAssertion, len(h.unresolvedWriters)) 191 copy(unresolvedWriters, h.unresolvedWriters) 192 return unresolvedWriters 193 } 194 195 // UnresolvedReaders returns the handle's unresolved readers in sorted 196 // order. If the handle is public, nil will be returned. 197 func (h Handle) UnresolvedReaders() []keybase1.SocialAssertion { 198 if len(h.unresolvedReaders) == 0 { 199 return nil 200 } 201 unresolvedReaders := make([]keybase1.SocialAssertion, len(h.unresolvedReaders)) 202 copy(unresolvedReaders, h.unresolvedReaders) 203 return unresolvedReaders 204 } 205 206 // ConflictInfo returns the handle's conflict info, if any. 207 func (h Handle) ConflictInfo() *tlf.HandleExtension { 208 if h.conflictInfo == nil { 209 return nil 210 } 211 conflictInfoCopy := *h.conflictInfo 212 return &conflictInfoCopy 213 } 214 215 func (h Handle) recomputeNameWithExtensions() tlf.CanonicalName { 216 components := strings.Split(string(h.name), tlf.HandleExtensionSep) 217 newName := components[0] 218 extensionList := tlf.HandleExtensionList(h.Extensions()) 219 sort.Sort(extensionList) 220 if h.IsBackedByTeam() { 221 newName += extensionList.SuffixForTeamHandle() 222 } else { 223 newName += extensionList.Suffix() 224 } 225 return tlf.CanonicalName(newName) 226 } 227 228 // WithUpdatedConflictInfo returns a new handle with the conflict info set to 229 // the given one, if the existing one is nil. (In this case, the given one may 230 // also be nil.) Otherwise, the given conflict info must match the existing 231 // one. 232 func (h Handle) WithUpdatedConflictInfo( 233 codec kbfscodec.Codec, info *tlf.HandleExtension) (*Handle, error) { 234 newHandle := h.DeepCopy() 235 if newHandle.conflictInfo == nil { 236 if info == nil { 237 // Nothing to do. 238 return newHandle, nil 239 } 240 conflictInfoCopy := *info 241 newHandle.conflictInfo = &conflictInfoCopy 242 newHandle.name = newHandle.recomputeNameWithExtensions() 243 return newHandle, nil 244 } 245 // Make sure conflict info is the same; the conflict info for 246 // a TLF, once set, is immutable and should never change. 247 equal, err := kbfscodec.Equal(codec, newHandle.conflictInfo, info) 248 if err != nil { 249 return newHandle, err 250 } 251 if !equal { 252 return newHandle, tlf.HandleExtensionMismatchError{ 253 Expected: *newHandle.ConflictInfo(), 254 Actual: info, 255 } 256 } 257 return newHandle, nil 258 } 259 260 // FinalizedInfo returns the handle's finalized info, if any. 261 func (h Handle) FinalizedInfo() *tlf.HandleExtension { 262 if h.finalizedInfo == nil { 263 return nil 264 } 265 finalizedInfoCopy := *h.finalizedInfo 266 return &finalizedInfoCopy 267 } 268 269 // SetFinalizedInfo sets the handle's finalized info to the given one, 270 // which may be nil. 271 // TODO: remove this to make TlfHandle fully immutable 272 func (h *Handle) SetFinalizedInfo(info *tlf.HandleExtension) { 273 if info == nil { 274 h.finalizedInfo = nil 275 } else { 276 finalizedInfoCopy := *info 277 h.finalizedInfo = &finalizedInfoCopy 278 } 279 h.name = h.recomputeNameWithExtensions() 280 } 281 282 // SetName sets the TLF name associated with this Handle. Useful for 283 // testing. 284 func (h *Handle) SetName(name tlf.CanonicalName) { 285 h.name = name 286 } 287 288 // SetResolvedWriter resolves the given `id` to the given `name`. 289 // Useful for testing. 290 func (h *Handle) SetResolvedWriter( 291 id keybase1.UserOrTeamID, name kbname.NormalizedUsername) { 292 h.resolvedWriters[id] = name 293 } 294 295 // ClearResolvedReaders forgets all the resolved reader. Useful for 296 // testing. 297 func (h *Handle) ClearResolvedReaders() { 298 h.resolvedReaders = nil 299 } 300 301 // SetTlfID sets the TLF ID associated with this handle. 302 func (h *Handle) SetTlfID(id tlf.ID) { 303 h.tlfID = id 304 } 305 306 // Extensions returns a list of extensions for the given handle. 307 func (h Handle) Extensions() (extensions []tlf.HandleExtension) { 308 if h.ConflictInfo() != nil { 309 extensions = append(extensions, *h.ConflictInfo()) 310 } 311 if h.FinalizedInfo() != nil { 312 extensions = append(extensions, *h.FinalizedInfo()) 313 } 314 return extensions 315 } 316 317 func init() { 318 if reflect.ValueOf(Handle{}).NumField() != 9 { 319 panic(errors.New( 320 "Unexpected number of fields in TlfHandle; " + 321 "please update TlfHandle.Equals() for your " + 322 "new or removed field")) 323 } 324 } 325 326 // EqualsIgnoreName returns whether h and other contain the same info ignoring the name. 327 func (h Handle) EqualsIgnoreName( 328 codec kbfscodec.Codec, other Handle) (bool, error) { 329 if h.tlfType != other.tlfType { 330 return false, nil 331 } 332 if h.tlfID != other.tlfID { 333 return false, nil 334 } 335 336 if !reflect.DeepEqual(h.resolvedWriters, other.resolvedWriters) { 337 return false, nil 338 } 339 340 if !reflect.DeepEqual(h.resolvedReaders, other.resolvedReaders) { 341 return false, nil 342 } 343 344 if !reflect.DeepEqual(h.unresolvedWriters, other.unresolvedWriters) { 345 return false, nil 346 } 347 348 if !reflect.DeepEqual(h.unresolvedReaders, other.unresolvedReaders) { 349 return false, nil 350 } 351 352 eq, err := kbfscodec.Equal(codec, h.conflictInfo, other.conflictInfo) 353 if err != nil { 354 return false, err 355 } 356 if !eq { 357 return false, nil 358 } 359 360 eq, err = kbfscodec.Equal(codec, h.finalizedInfo, other.finalizedInfo) 361 if err != nil { 362 return false, err 363 } 364 return eq, nil 365 } 366 367 // Equals returns whether h and other contain the same info. 368 func (h Handle) Equals( 369 codec kbfscodec.Codec, other Handle) (bool, error) { 370 eq, err := h.EqualsIgnoreName(codec, other) 371 if err != nil { 372 return false, err 373 } 374 375 if eq && h.name != other.name { 376 return false, nil 377 } 378 379 return eq, nil 380 } 381 382 // ToBareHandle returns a tlf.Handle corresponding to this handle. 383 func (h Handle) ToBareHandle() (tlf.Handle, error) { 384 var readers []keybase1.UserOrTeamID 385 switch h.TypeForKeying() { 386 case tlf.PublicKeying: 387 readers = []keybase1.UserOrTeamID{ 388 keybase1.UserOrTeamID(keybase1.PUBLIC_UID)} 389 case tlf.TeamKeying: 390 // Leave readers blank. 391 default: 392 readers = h.unsortedResolvedReaders() 393 } 394 return tlf.MakeHandle( 395 h.unsortedResolvedWriters(), readers, 396 h.unresolvedWriters, h.unresolvedReaders, 397 h.Extensions()) 398 } 399 400 // ToBareHandleOrBust returns a tlf.Handle corresponding to this 401 // handle, and panics if there's an error. Used by tests. 402 func (h Handle) ToBareHandleOrBust() tlf.Handle { 403 bh, err := h.ToBareHandle() 404 if err != nil { 405 panic(err) 406 } 407 return bh 408 } 409 410 // DeepCopy makes a deep copy of this `Handle`. 411 func (h Handle) DeepCopy() *Handle { 412 hCopy := Handle{ 413 tlfType: h.tlfType, 414 name: h.name, 415 unresolvedWriters: h.UnresolvedWriters(), 416 unresolvedReaders: h.UnresolvedReaders(), 417 conflictInfo: h.ConflictInfo(), 418 finalizedInfo: h.FinalizedInfo(), 419 tlfID: h.tlfID, 420 } 421 422 hCopy.resolvedWriters = make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(h.resolvedWriters)) 423 for k, v := range h.resolvedWriters { 424 hCopy.resolvedWriters[k] = v 425 } 426 427 hCopy.resolvedReaders = make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(h.resolvedReaders)) 428 for k, v := range h.resolvedReaders { 429 hCopy.resolvedReaders[k] = v 430 } 431 432 return &hCopy 433 } 434 435 // GetCanonicalName returns the canonical name of this TLF. 436 func (h *Handle) GetCanonicalName() tlf.CanonicalName { 437 if h.name == "" { 438 panic(fmt.Sprintf("TlfHandle %v with no name", h)) 439 } 440 441 return h.name 442 } 443 444 // GetCanonicalPath returns the full canonical path of this TLF. 445 func (h *Handle) GetCanonicalPath() string { 446 return BuildCanonicalPathForTlfName(h.Type(), h.GetCanonicalName()) 447 } 448 449 // GetProtocolPath returns the `keybase1.Path` representing this 450 // handle. 451 func (h *Handle) GetProtocolPath() keybase1.Path { 452 return BuildProtocolPathForTlfName(h.Type(), h.GetCanonicalName()) 453 } 454 455 // ToFavorite converts a TlfHandle into a Favorite, suitable for 456 // Favorites calls. 457 func (h *Handle) ToFavorite() favorites.Folder { 458 return favorites.Folder{ 459 Name: string(h.GetCanonicalName()), 460 Type: h.Type(), 461 } 462 } 463 464 // FavoriteData converts a TlfHandle into FavoriteData, suitable for 465 // Favorites calls. 466 func (h *Handle) FavoriteData() favorites.Data { 467 fd := favorites.Data{ 468 Name: string(h.GetCanonicalName()), 469 FolderType: h.Type().FolderType(), 470 Private: h.Type() != tlf.Public, 471 ResetMembers: []keybase1.User{}, 472 } 473 if h.IsBackedByTeam() { 474 teamID := h.FirstResolvedWriter().AsTeamOrBust() 475 fd.TeamID = &teamID 476 } 477 return fd 478 } 479 480 // ToFavToAdd converts a TlfHandle into a Favorite to be added, and 481 // sets internal state about whether the corresponding folder was just 482 // created or not. 483 func (h *Handle) ToFavToAdd(created bool) favorites.ToAdd { 484 return favorites.ToAdd{ 485 Folder: h.ToFavorite(), 486 Data: h.FavoriteData(), 487 Created: created, 488 } 489 } 490 491 func getSortedUnresolved(unresolved map[keybase1.SocialAssertion]bool) []keybase1.SocialAssertion { 492 var assertions []keybase1.SocialAssertion 493 for sa := range unresolved { 494 assertions = append(assertions, sa) 495 } 496 sort.Sort(tlf.SocialAssertionList(assertions)) 497 return assertions 498 } 499 500 // CheckHandleOffline does light checks whether a TLF handle looks ok, 501 // it avoids all network calls. 502 func CheckHandleOffline( 503 ctx context.Context, name string, t tlf.Type) error { 504 _, _, _, err := idutil.SplitAndNormalizeTLFName(name, t) 505 return err 506 } 507 508 // IsFinal returns whether or not this TlfHandle represents a finalized 509 // top-level folder. 510 func (h Handle) IsFinal() bool { 511 return h.finalizedInfo != nil 512 } 513 514 // IsConflict returns whether or not this TlfHandle represents a conflicted 515 // top-level folder. 516 func (h Handle) IsConflict() bool { 517 return h.conflictInfo != nil && 518 h.conflictInfo.Type == tlf.HandleExtensionConflict 519 } 520 521 // IsLocalConflict returns whether or not this TlfHandle represents a 522 // locally conflict branch for a top-level folder. 523 func (h Handle) IsLocalConflict() bool { 524 return h.conflictInfo != nil && 525 h.conflictInfo.Type == tlf.HandleExtensionLocalConflict 526 } 527 528 // GetPreferredFormat returns a TLF name formatted with the username given 529 // as the parameter first. 530 // This calls tlf.CanonicalToPreferredName with the canonical 531 // tlf name which will be reordered into the preferred format. 532 // An empty username is allowed here and results in the canonical ordering. 533 func (h Handle) GetPreferredFormat( 534 username kbname.NormalizedUsername) tlf.PreferredName { 535 s, err := tlf.CanonicalToPreferredName(username, h.GetCanonicalName()) 536 if err != nil { 537 panic("TlfHandle.GetPreferredFormat: Parsing canonical username failed!") 538 } 539 return s 540 }