1package leaf
2
3import (
4	"context"
5	"fmt"
6	"path/filepath"
7	"sort"
8	"strings"
9
10	"github.com/gopasspw/gopass/internal/backend"
11	"github.com/gopasspw/gopass/pkg/debug"
12)
13
14// Store is password store
15type Store struct {
16	alias   string
17	path    string
18	crypto  backend.Crypto
19	storage backend.Storage
20}
21
22// Init initializes this sub store
23func Init(ctx context.Context, alias, path string) (*Store, error) {
24	debug.Log("Initializing %s at %s", alias, path)
25	s := &Store{
26		alias: alias,
27		path:  path,
28	}
29
30	st, err := backend.InitStorage(ctx, backend.GetStorageBackend(ctx), path)
31	if err != nil {
32		return nil, err
33	}
34	s.storage = st
35	debug.Log("Storage initialized")
36
37	crypto, err := backend.NewCrypto(ctx, backend.GetCryptoBackend(ctx))
38	if err != nil {
39		return nil, err
40	}
41	s.crypto = crypto
42	debug.Log("Crypto initialized")
43
44	return s, nil
45}
46
47// New creates a new store
48func New(ctx context.Context, alias, path string) (*Store, error) {
49	debug.Log("Instantiating %s at %s", alias, path)
50
51	s := &Store{
52		alias: alias,
53		path:  path,
54	}
55
56	// init storage and rcs backend
57	if err := s.initStorageBackend(ctx); err != nil {
58		return nil, fmt.Errorf("failed to init storage backend: %w", err)
59	}
60	debug.Log("Storage initialized")
61
62	// init crypto backend
63	if err := s.initCryptoBackend(ctx); err != nil {
64		return nil, fmt.Errorf("failed to init crypto backend: %w", err)
65	}
66	debug.Log("Crypto initialized")
67
68	debug.Log("Instantiated %s at %s - storage: %+#v - crypto: %+#v", alias, path, s.storage, s.crypto)
69	return s, nil
70}
71
72// idFile returns the path to the recipient list for this store
73// it walks up from the given filename until it finds a directory containing
74// a gpg id file or it leaves the scope of storage.
75func (s *Store) idFile(ctx context.Context, name string) string {
76	if s.crypto == nil {
77		return ""
78	}
79	fn := name
80	var cnt uint8
81	for {
82		cnt++
83		if cnt > 100 {
84			break
85		}
86		if fn == "" || fn == Sep {
87			break
88		}
89		gfn := filepath.Join(fn, s.crypto.IDFile())
90		if s.storage.Exists(ctx, gfn) {
91			return gfn
92		}
93		fn = filepath.Dir(fn)
94	}
95	return s.crypto.IDFile()
96}
97
98// idFiles returns the path to all id files in this store.
99func (s *Store) idFiles(ctx context.Context) []string {
100	if s.crypto == nil {
101		return nil
102	}
103	files, err := s.Storage().List(ctx, "")
104	if err != nil {
105		return nil
106	}
107	fileSet := make(map[string]struct{}, len(files))
108	for _, file := range files {
109		if strings.HasPrefix(filepath.Base(file), ".") {
110			continue
111		}
112		idf := s.idFile(ctx, file)
113		if s.storage.Exists(ctx, idf) {
114			fileSet[idf] = struct{}{}
115		}
116	}
117	out := make([]string, 0, len(fileSet))
118	for file := range fileSet {
119		out = append(out, file)
120	}
121	sort.Strings(out)
122	return out
123}
124
125// Equals returns true if this.storage has the same on-disk path as the other
126func (s *Store) Equals(other *Store) bool {
127	if other == nil {
128		return false
129	}
130	return s.Path() == other.Path()
131}
132
133// IsDir returns true if the entry is folder inside the store
134func (s *Store) IsDir(ctx context.Context, name string) bool {
135	return s.storage.IsDir(ctx, name)
136}
137
138// Exists checks the existence of a single entry
139func (s *Store) Exists(ctx context.Context, name string) bool {
140	return s.storage.Exists(ctx, s.passfile(name))
141}
142
143func (s *Store) useableKeys(ctx context.Context, name string) ([]string, error) {
144	rs, err := s.GetRecipients(ctx, name)
145	if err != nil {
146		return nil, fmt.Errorf("failed to get recipients: %w", err)
147	}
148
149	if !IsCheckRecipients(ctx) {
150		return rs, nil
151	}
152
153	kl, err := s.crypto.FindRecipients(ctx, rs...)
154	if err != nil {
155		return rs, err
156	}
157
158	return kl, nil
159}
160
161// passfile returns the name of gpg file on disk, for the given key/name
162func (s *Store) passfile(name string) string {
163	return strings.TrimPrefix(name+"."+s.crypto.Ext(), "/")
164}
165
166// String implement fmt.Stringer
167func (s *Store) String() string {
168	return fmt.Sprintf("Store(Alias: %s, Path: %s)", s.alias, s.path)
169}
170
171// Path returns the value of path
172func (s *Store) Path() string {
173	return s.path
174}
175
176// Alias returns the value of alias
177func (s *Store) Alias() string {
178	return s.alias
179}
180
181// Storage returns the storage backend used by this.storage
182func (s *Store) Storage() backend.Storage {
183	return s.storage
184}
185
186// Valid returns true if this store is not nil
187func (s *Store) Valid() bool {
188	return s != nil
189}
190