1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"fmt"
8	"sort"
9	"strings"
10
11	"github.com/keybase/client/go/gregor"
12	keybase1 "github.com/keybase/client/go/protocol/keybase1"
13	jsonw "github.com/keybase/go-jsonw"
14)
15
16type IdentifyOutcome struct {
17	Contextified
18	Username              NormalizedUsername
19	UID                   keybase1.UID
20	EldestSeqno           keybase1.Seqno
21	Error                 error
22	KeyDiffs              []TrackDiff
23	Revoked               []TrackDiff
24	RevokedDetails        []keybase1.RevokedProof
25	ProofChecks           []*LinkCheckResult
26	Warnings              []Warning
27	TrackUsed             *TrackLookup
28	TrackEqual            bool // Whether the track statement was equal to what we saw
29	TrackOptions          keybase1.TrackOptions
30	Reason                keybase1.IdentifyReason
31	ResponsibleGregorItem gregor.Item
32}
33
34func NewIdentifyOutcome(g *GlobalContext, username NormalizedUsername, uid keybase1.UID, eldestSeqno keybase1.Seqno) *IdentifyOutcome {
35	return &IdentifyOutcome{
36		Contextified: NewContextified(g),
37		Username:     username,
38		UID:          uid,
39		EldestSeqno:  eldestSeqno,
40	}
41}
42
43func (i *IdentifyOutcome) remoteProofLinks() *RemoteProofLinks {
44	rpl := NewRemoteProofLinks(i.G())
45	for _, p := range i.ProofChecks {
46		rpl.Insert(p.link, p.err)
47	}
48	return rpl
49}
50
51func (i *IdentifyOutcome) GetRemoteCheckResultFor(service string, username string) ProofError {
52	cieq := strings.EqualFold
53	for _, pc := range i.ProofChecks {
54		k, v := pc.GetLink().ToKeyValuePair()
55		if cieq(k, service) && cieq(v, username) {
56			return pc.GetProofError()
57		}
58	}
59	return NewProofError(keybase1.ProofStatus_NO_HINT, "no proof checked for %s@%s", username, service)
60}
61
62func (i *IdentifyOutcome) ActiveProofs() []RemoteProofChainLink {
63	return i.remoteProofLinks().Active()
64}
65
66func (i *IdentifyOutcome) AddProofsToSet(existing *ProofSet, okStates []keybase1.ProofState) {
67	i.remoteProofLinks().AddProofsToSet(existing, okStates)
68}
69
70func (i *IdentifyOutcome) TrackSet() *TrackSet {
71	return i.remoteProofLinks().TrackSet()
72}
73
74func (i *IdentifyOutcome) ProofChecksSorted(mctx MetaContext) []*LinkCheckResult {
75	// Sort by display priority
76	pc := make([]*LinkCheckResult, len(i.ProofChecks))
77	copy(pc, i.ProofChecks)
78	proofServices := i.G().GetProofServices()
79	serviceTypes := map[string]int{}
80	for _, lcr := range pc {
81		key := lcr.link.DisplayPriorityKey()
82		if _, ok := serviceTypes[key]; !ok {
83			st := proofServices.GetServiceType(mctx.Ctx(), key)
84			displayPriority := 0
85			if st != nil {
86				displayPriority = st.DisplayPriority()
87			}
88			serviceTypes[key] = displayPriority
89		}
90	}
91	sort.Slice(pc, func(a, b int) bool {
92		keyA := pc[a].link.DisplayPriorityKey()
93		keyB := pc[b].link.DisplayPriorityKey()
94		return serviceTypes[keyA] > serviceTypes[keyB]
95	})
96	return pc
97}
98
99// Revoked proofs are those we used to look for but are gone!
100func (i IdentifyOutcome) NumRevoked() int {
101	return len(i.Revoked)
102}
103
104// The number of proofs that failed.
105func (i IdentifyOutcome) NumProofFailures() int {
106	nfails := 0
107	for _, c := range i.ProofChecks {
108		if c.err != nil {
109			nfails++
110		}
111	}
112	return nfails
113}
114
115// The number of proofs that actually worked
116func (i IdentifyOutcome) NumProofSuccesses() int {
117	nsucc := 0
118	for _, c := range i.ProofChecks {
119		if c.err == nil {
120			nsucc++
121		}
122	}
123	return nsucc
124}
125
126// A "Track Failure" is when we previously tracked this user, and
127// some aspect of their proof changed.  Like their key changed, or
128// they changed Twitter names
129func (i IdentifyOutcome) NumTrackFailures() int {
130	ntf := 0
131	check := func(d TrackDiff) bool {
132		return d != nil && d.BreaksTracking()
133	}
134	for _, c := range i.ProofChecks {
135		if check(c.diff) || check(c.remoteDiff) {
136			ntf++
137		}
138	}
139
140	for _, k := range i.KeyDiffs {
141		if check(k) {
142			ntf++
143		}
144	}
145
146	return ntf
147}
148
149// A "Track Change" isn't necessary a failure, maybe they upgraded
150// a proof from HTTP to HTTPS.  But we still should retrack if we can.
151func (i IdentifyOutcome) NumTrackChanges() int {
152	ntc := 0
153	check := func(d TrackDiff) bool {
154		return d != nil && !d.IsSameAsTracked()
155	}
156	for _, c := range i.ProofChecks {
157		if check(c.diff) || check(c.remoteDiff) {
158			ntc++
159		}
160	}
161	for _, k := range i.KeyDiffs {
162		if check(k) {
163			ntc++
164		}
165	}
166	return ntc
167}
168
169func (i IdentifyOutcome) TrackStatus() keybase1.TrackStatus {
170	if i.NumRevoked() > 0 {
171		return keybase1.TrackStatus_UPDATE_BROKEN_REVOKED
172	}
173	if i.NumTrackFailures() > 0 {
174		return keybase1.TrackStatus_UPDATE_BROKEN_FAILED_PROOFS
175	}
176	if i.TrackUsed != nil {
177		if i.NumTrackChanges() > 0 {
178			return keybase1.TrackStatus_UPDATE_NEW_PROOFS
179		}
180		if i.NumTrackChanges() == 0 {
181			return keybase1.TrackStatus_UPDATE_OK
182		}
183	}
184	if i.NumProofSuccesses() == 0 {
185		return keybase1.TrackStatus_NEW_ZERO_PROOFS
186	}
187	if i.NumProofFailures() > 0 {
188		return keybase1.TrackStatus_NEW_FAIL_PROOFS
189	}
190	return keybase1.TrackStatus_NEW_OK
191}
192
193func (i IdentifyOutcome) IsOK() bool {
194	switch i.TrackStatus() {
195	case keybase1.TrackStatus_UPDATE_NEW_PROOFS:
196		return true
197	case keybase1.TrackStatus_UPDATE_OK:
198		return true
199	case keybase1.TrackStatus_NEW_ZERO_PROOFS:
200		return true
201	case keybase1.TrackStatus_NEW_OK:
202		return true
203	default:
204		return false
205	}
206}
207
208func (i IdentifyOutcome) TrackingStatement() *jsonw.Wrapper {
209	return i.remoteProofLinks().TrackingStatement()
210}
211
212func (i IdentifyOutcome) GetErrorAndWarnings(strict bool) (warnings Warnings, err error) {
213
214	if i.Error != nil {
215		err = i.Error
216		return warnings, err
217	}
218
219	var probs []string
220
221	softErr := func(s string) {
222		if strict {
223			probs = append(probs, s)
224		} else {
225			warnings.Push(StringWarning(s))
226		}
227	}
228
229	// For revoked proofs, we almost always want to return an error. The one exception
230	// is a snoozed tracker proof in non-strict mode.
231	for _, revoked := range i.Revoked {
232		errString := revoked.ToDisplayString()
233		isSnoozed := false
234		if _, ok := revoked.(TrackDiffSnoozedRevoked); ok {
235			isSnoozed = true
236		}
237
238		if !strict && isSnoozed {
239			warnings.Push(StringWarning(errString))
240		} else {
241			probs = append(probs, errString)
242		}
243	}
244
245	if nfails := i.NumProofFailures(); nfails > 0 {
246		p := fmt.Sprintf("PROBLEM: %d proof%s failed remote checks", nfails, GiveMeAnS(nfails))
247		softErr(p)
248	}
249
250	if ntf := i.NumTrackFailures(); ntf > 0 {
251		probs = append(probs,
252			fmt.Sprintf("%d followed proof%s failed",
253				ntf, GiveMeAnS(ntf)))
254	}
255
256	if len(probs) > 0 {
257		err = IdentifySummaryError{i.Username, probs}
258	}
259
260	return warnings, err
261}
262
263func (i IdentifyOutcome) GetError() error {
264	_, e := i.GetErrorAndWarnings(true /*strict */)
265	return e
266}
267
268func (i IdentifyOutcome) GetErrorLax() (Warnings, error) {
269	return i.GetErrorAndWarnings(false /*strict */)
270}
271