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
5package tlfhandle
6
7// This file has the type for TlfHandles and offline functionality.
8
9import (
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.
28type 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.
53func 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.
69func (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.
76func (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.
87func (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.
97func (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.
103func (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.
114func (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.
127func (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
139func (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.
152func (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.
161func (h Handle) FirstResolvedWriter() keybase1.UserOrTeamID {
162	return h.ResolvedWriters()[0]
163}
164
165func (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.
178func (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.
186func (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.
197func (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.
207func (h Handle) ConflictInfo() *tlf.HandleExtension {
208	if h.conflictInfo == nil {
209		return nil
210	}
211	conflictInfoCopy := *h.conflictInfo
212	return &conflictInfoCopy
213}
214
215func (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.
232func (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.
261func (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
272func (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.
284func (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.
290func (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.
297func (h *Handle) ClearResolvedReaders() {
298	h.resolvedReaders = nil
299}
300
301// SetTlfID sets the TLF ID associated with this handle.
302func (h *Handle) SetTlfID(id tlf.ID) {
303	h.tlfID = id
304}
305
306// Extensions returns a list of extensions for the given handle.
307func (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
317func 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.
327func (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.
368func (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.
383func (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.
402func (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`.
411func (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.
436func (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.
445func (h *Handle) GetCanonicalPath() string {
446	return BuildCanonicalPathForTlfName(h.Type(), h.GetCanonicalName())
447}
448
449// GetProtocolPath returns the `keybase1.Path` representing this
450// handle.
451func (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.
457func (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.
466func (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.
483func (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
491func 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.
502func 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.
510func (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.
516func (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.
523func (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.
533func (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}
541