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
9	keybase1 "github.com/keybase/client/go/protocol/keybase1"
10	jsonw "github.com/keybase/go-jsonw"
11)
12
13type SigHint struct {
14	sigID     keybase1.SigID
15	remoteID  string
16	apiURL    string
17	humanURL  string
18	checkText string
19	// `isVerified` indicates if the client generated the values or they were
20	// received from the server and are trusted but not verified.
21	isVerified bool
22}
23
24func (sh SigHint) GetHumanURL() string  { return sh.humanURL }
25func (sh SigHint) GetAPIURL() string    { return sh.apiURL }
26func (sh SigHint) GetCheckText() string { return sh.checkText }
27
28func NewSigHint(jw *jsonw.Wrapper) (sh *SigHint, err error) {
29	if jw == nil || !jw.IsOk() {
30		return nil, nil
31	}
32	sh = &SigHint{}
33	sh.sigID, err = GetSigID(jw.AtKey("sig_id"))
34	sh.remoteID, _ = jw.AtKey("remote_id").GetString()
35	sh.apiURL, _ = jw.AtKey("api_url").GetString()
36	sh.humanURL, _ = jw.AtKey("human_url").GetString()
37	sh.checkText, _ = jw.AtKey("proof_text_check").GetString()
38	sh.isVerified, _ = jw.AtKey("isVerified").GetBool()
39	return sh, err
40}
41
42func NewVerifiedSigHint(sigID keybase1.SigID, remoteID, apiURL, humanURL, checkText string) *SigHint {
43	return &SigHint{
44		sigID:      sigID,
45		remoteID:   remoteID,
46		apiURL:     apiURL,
47		humanURL:   humanURL,
48		checkText:  checkText,
49		isVerified: true,
50	}
51}
52
53func (sh SigHint) MarshalToJSON() *jsonw.Wrapper {
54	ret := jsonw.NewDictionary()
55	_ = ret.SetKey("sig_id", jsonw.NewString(sh.sigID.String()))
56	_ = ret.SetKey("remote_id", jsonw.NewString(sh.remoteID))
57	_ = ret.SetKey("api_url", jsonw.NewString(sh.apiURL))
58	_ = ret.SetKey("human_url", jsonw.NewString(sh.humanURL))
59	_ = ret.SetKey("proof_text_check", jsonw.NewString(sh.checkText))
60	_ = ret.SetKey("is_verified", jsonw.NewBool(sh.isVerified))
61	return ret
62}
63
64type SigHints struct {
65	Contextified
66	uid     keybase1.UID
67	version int
68	hints   map[keybase1.SigIDMapKey]*SigHint
69	dirty   bool
70}
71
72func NewSigHints(jw *jsonw.Wrapper, uid keybase1.UID, dirty bool, g *GlobalContext) (sh *SigHints, err error) {
73	sh = &SigHints{
74		uid:          uid,
75		dirty:        dirty,
76		version:      0,
77		Contextified: NewContextified(g),
78	}
79	err = sh.PopulateWith(jw)
80	if err != nil {
81		sh = nil
82	}
83	return
84}
85
86func (sh SigHints) Lookup(i keybase1.SigID) *SigHint {
87	obj := sh.hints[i.ToMapKey()]
88	return obj
89}
90
91func (sh *SigHints) PopulateWith(jw *jsonw.Wrapper) (err error) {
92
93	if jw == nil || jw.IsNil() {
94		return
95	}
96
97	jw.AtKey("version").GetIntVoid(&sh.version, &err)
98	if err != nil {
99		return
100	}
101
102	sh.hints = make(map[keybase1.SigIDMapKey]*SigHint)
103	var n int
104	n, err = jw.AtKey("hints").Len()
105	if err != nil {
106		return
107	}
108
109	for i := 0; i < n; i++ {
110		hint, tmpe := NewSigHint(jw.AtKey("hints").AtIndex(i))
111		if tmpe != nil {
112			sh.G().Log.Warning("Bad SigHint Loaded: %s", tmpe)
113		} else {
114			sh.hints[hint.sigID.ToMapKey()] = hint
115		}
116	}
117	return
118}
119
120func (sh SigHints) MarshalToJSON() *jsonw.Wrapper {
121	ret := jsonw.NewDictionary()
122	_ = ret.SetKey("version", jsonw.NewInt(sh.version))
123	_ = ret.SetKey("hints", jsonw.NewArray(len(sh.hints)))
124	i := 0
125	for _, v := range sh.hints {
126		_ = ret.AtKey("hints").SetIndex(i, v.MarshalToJSON())
127		i++
128	}
129	return ret
130}
131
132func (sh *SigHints) Store(m MetaContext) (err error) {
133	m.Debug("+ SigHints.Store() for uid=%s", sh.uid)
134	if sh.dirty {
135		err = sh.G().LocalDb.Put(DbKeyUID(DBSigHints, sh.uid), []DbKey{}, sh.MarshalToJSON())
136		sh.dirty = false
137	} else {
138		m.Debug("| SigHints.Store() skipped; wasn't dirty")
139	}
140	m.Debug("- SigHints.Store() for uid=%s -> %v", sh.uid, ErrToOk(err))
141	return err
142}
143
144func LoadSigHints(m MetaContext, uid keybase1.UID) (sh *SigHints, err error) {
145	defer m.Trace(fmt.Sprintf("+ LoadSigHints(%s)", uid), &err)()
146	var jw *jsonw.Wrapper
147	jw, err = m.G().LocalDb.Get(DbKeyUID(DBSigHints, uid))
148	if err != nil {
149		jw = nil
150		m.Debug("| SigHints failed to access local storage: %s", err)
151	}
152	// jw might be nil here, but that's allowed.
153	sh, err = NewSigHints(jw, uid, false, m.G())
154	if err == nil {
155		m.Debug("| SigHints loaded @v%d", sh.version)
156	}
157	m.Debug("- LoadSigHints(%s)", uid)
158	return
159}
160
161func (sh *SigHints) Refresh(m MetaContext) (err error) {
162	defer m.Trace(fmt.Sprintf("Refresh SigHints for uid=%s", sh.uid), &err)()
163	res, err := m.G().API.Get(m, APIArg{
164		Endpoint:    "sig/hints",
165		SessionType: APISessionTypeNONE,
166		Args: HTTPArgs{
167			"uid": UIDArg(sh.uid),
168			"low": I{sh.version},
169		},
170	})
171	if err != nil {
172		return err
173	}
174
175	return sh.RefreshWith(m, res.Body)
176}
177
178func (sh *SigHints) RefreshWith(m MetaContext, jw *jsonw.Wrapper) (err error) {
179	defer m.Trace("RefreshWith", &err)()
180
181	n, err := jw.AtKey("hints").Len()
182	if err != nil {
183		return err
184	}
185	if n == 0 {
186		m.Debug("| No changes; version %d was up-to-date", sh.version)
187	} else if err = sh.PopulateWith(jw); err != nil {
188		return err
189	} else {
190		sh.dirty = true
191	}
192	return nil
193}
194
195func LoadAndRefreshSigHints(m MetaContext, uid keybase1.UID) (*SigHints, error) {
196	sh, err := LoadSigHints(m, uid)
197	if err != nil {
198		return nil, err
199	}
200	if err = sh.Refresh(m); err != nil {
201		return nil, err
202	}
203	return sh, nil
204}
205