1// Copyright 2019 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	"time"
9
10	"github.com/keybase/client/go/protocol/keybase1"
11)
12
13func randomPassphraseToState(hasRandomPassphrase bool) keybase1.PassphraseState {
14	if hasRandomPassphrase {
15		return keybase1.PassphraseState_RANDOM
16	}
17	return keybase1.PassphraseState_KNOWN
18}
19
20func LoadPassphraseState(mctx MetaContext) (passphraseState keybase1.PassphraseState, err error) {
21	return LoadPassphraseStateWithForceRepoll(mctx)
22}
23
24// forceRepoll only forces repoll when the state is RANDOM, but not when it is KNOWN.
25func LoadPassphraseStateWithForceRepoll(mctx MetaContext) (passphraseState keybase1.PassphraseState, err error) {
26	mctx = mctx.WithLogTag("PPSTATE")
27	defer mctx.Trace(fmt.Sprintf("LoadPassphraseState()"), &err)()
28
29	// If we're in standalone mode, we don't get the gregor msg about
30	// passphrase_state changes. So, force a repoll to the server if the state
31	// isn't currently KNOWN.
32	forceRepoll := mctx.G().GregorListener == nil
33
34	if len(mctx.G().Env.GetUsername().String()) == 0 {
35		mctx.Debug("LoadPassphraseState: user is not logged in")
36		return passphraseState, NewLoginRequiredError("LoadPassphraseState")
37	}
38
39	configState := mctx.G().Env.GetConfig().GetPassphraseState()
40	if configState != nil {
41		mctx.Debug("LoadPassphraseState: state found in config.json: %#v", configState)
42		if !forceRepoll || *configState == keybase1.PassphraseState_KNOWN {
43			return *configState, nil
44		}
45	}
46
47	mctx.Debug("LoadPassphraseState: state not found in config.json; checking legacy leveldb")
48
49	legacyState, err := loadPassphraseStateFromLegacy(mctx)
50	if err == nil {
51		mctx.Debug("LoadPassphraseState: state found in legacy leveldb: %#v", legacyState)
52		MaybeSavePassphraseState(mctx, legacyState)
53		if !forceRepoll || legacyState == keybase1.PassphraseState_KNOWN {
54			return legacyState, nil
55		}
56	}
57	mctx.Debug("LoadPassphraseState: could not find state in legacy leveldb (%s); checking remote", err)
58
59	remoteState, err := LoadPassphraseStateFromRemote(mctx)
60	if err == nil {
61		MaybeSavePassphraseState(mctx, remoteState)
62		return remoteState, nil
63	}
64	return passphraseState, fmt.Errorf("failed to load passphrase state from any path, including remote: %s", err)
65}
66
67func MaybeSavePassphraseState(mctx MetaContext, passphraseState keybase1.PassphraseState) {
68	err := mctx.G().Env.GetConfigWriter().SetPassphraseState(passphraseState)
69	if err == nil {
70		mctx.Debug("Added PassphraseState=%#v to config file", passphraseState)
71	} else {
72		mctx.Warning("Failed to save passphraseState=%#v to config file: %s", passphraseState, err)
73	}
74}
75
76func loadPassphraseStateFromLegacy(mctx MetaContext) (passphraseState keybase1.PassphraseState, err error) {
77	currentUID := mctx.CurrentUID()
78	cacheKey := DbKey{
79		Typ: DBLegacyHasRandomPW,
80		Key: currentUID.String(),
81	}
82	var hasRandomPassphrase bool
83	found, err := mctx.G().GetKVStore().GetInto(&hasRandomPassphrase, cacheKey)
84	if err != nil {
85		return passphraseState, err
86	}
87	if !found {
88		return passphraseState, fmt.Errorf("passphrase state not found in leveldb")
89	}
90	return randomPassphraseToState(hasRandomPassphrase), nil
91}
92
93func LoadPassphraseStateFromRemote(mctx MetaContext) (passphraseState keybase1.PassphraseState, err error) {
94	var ret struct {
95		AppStatusEmbed
96		RandomPassphrase bool `json:"random_pw"`
97	}
98	err = mctx.G().API.GetDecode(mctx, APIArg{
99		Endpoint:       "user/has_random_pw",
100		SessionType:    APISessionTypeREQUIRED,
101		InitialTimeout: 10 * time.Second,
102	}, &ret)
103	if err != nil {
104		return passphraseState, err
105	}
106	return randomPassphraseToState(ret.RandomPassphrase), nil
107}
108