Skip to content

Commit 4d7d906

Browse files
Create resource wrapper
1 parent cb52308 commit 4d7d906

File tree

5 files changed

+674
-19
lines changed

5 files changed

+674
-19
lines changed

api/auth.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func (c *Client) CheckSession(ctx context.Context) bool {
2828

2929
// Login gets a Session and CSRF Token from Passbolt and Stores them in the Clients Cookie Jar
3030
func (c *Client) Login(ctx context.Context) error {
31+
// Clear any cached data from previous sessions
32+
c.ClearCache()
3133
c.csrfToken = http.Cookie{}
3234

3335
data := Login{&GPGAuth{KeyID: c.userPrivateKey.GetFingerprint()}}
@@ -129,5 +131,6 @@ func (c *Client) Logout(ctx context.Context) error {
129131
}
130132
c.sessionToken = http.Cookie{}
131133
c.csrfToken = http.Cookie{}
134+
c.ClearCache()
132135
return nil
133136
}

api/client.go

Lines changed: 191 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ type Client struct {
5959

6060
// Enable Debug Logging
6161
Debug bool
62+
63+
// Cache for resource types (rarely change)
64+
resourceTypesCache []ResourceType
65+
66+
// Cache for metadata keys (includes decrypted private keys)
67+
metadataKeysCache []MetadataKey
68+
// Cache for decrypted metadata private keys, keyed by metadata key ID
69+
decryptedMetadataKeysCache map[string]*crypto.Key
70+
71+
// Cache for session keys used for metadata decryption, keyed by metadata key ID
72+
sessionKeyCache map[string]*crypto.SessionKey
6273
}
6374

6475
// PublicKeyReponse the Body of a Public Key Api Request
@@ -98,11 +109,13 @@ func NewClient(httpClient *http.Client, UserAgent, BaseURL, UserPrivateKey, User
98109

99110
// Create Client Object
100111
c := &Client{
101-
httpClient: httpClient,
102-
baseURL: u,
103-
userAgent: UserAgent,
104-
userPrivateKey: unlockedKey,
105-
pgp: pgp,
112+
httpClient: httpClient,
113+
baseURL: u,
114+
userAgent: UserAgent,
115+
userPrivateKey: unlockedKey,
116+
pgp: pgp,
117+
decryptedMetadataKeysCache: make(map[string]*crypto.Key),
118+
sessionKeyCache: make(map[string]*crypto.SessionKey),
106119
}
107120
return c, err
108121
}
@@ -277,3 +290,176 @@ func (c *Client) GetPGPHandle() *crypto.PGPHandle {
277290
func (c *Client) GetPasswordExpirySettings() PasswordExpirySettings {
278291
return c.passwordExpirySettings
279292
}
293+
294+
// ClearCache clears all cached data
295+
func (c *Client) ClearCache() {
296+
c.ClearResourceTypesCache()
297+
c.ClearMetadataKeysCache()
298+
c.ClearSessionKeyCache()
299+
}
300+
301+
// ClearResourceTypesCache clears the resource types cache
302+
func (c *Client) ClearResourceTypesCache() {
303+
c.resourceTypesCache = nil
304+
}
305+
306+
// ClearMetadataKeysCache clears the metadata keys cache
307+
func (c *Client) ClearMetadataKeysCache() {
308+
c.metadataKeysCache = nil
309+
c.decryptedMetadataKeysCache = make(map[string]*crypto.Key)
310+
}
311+
312+
// ClearSessionKeyCache clears the session key cache used for metadata decryption
313+
func (c *Client) ClearSessionKeyCache() {
314+
c.sessionKeyCache = make(map[string]*crypto.SessionKey)
315+
}
316+
317+
// GetResourceTypesCached returns cached resource types, fetching from API if cache is empty
318+
func (c *Client) GetResourceTypesCached(ctx context.Context) ([]ResourceType, error) {
319+
if c.resourceTypesCache != nil {
320+
return c.resourceTypesCache, nil
321+
}
322+
323+
types, err := c.GetResourceTypes(ctx, nil)
324+
if err != nil {
325+
return nil, err
326+
}
327+
328+
c.resourceTypesCache = types
329+
return types, nil
330+
}
331+
332+
// GetResourceTypeCached returns a cached resource type by ID, fetching from API if not in cache
333+
func (c *Client) GetResourceTypeCached(ctx context.Context, typeID string) (*ResourceType, error) {
334+
// First check the cache
335+
if c.resourceTypesCache != nil {
336+
for i := range c.resourceTypesCache {
337+
if c.resourceTypesCache[i].ID == typeID {
338+
return &c.resourceTypesCache[i], nil
339+
}
340+
}
341+
}
342+
343+
// Populate cache and search again
344+
types, err := c.GetResourceTypesCached(ctx)
345+
if err != nil {
346+
return nil, err
347+
}
348+
349+
for i := range types {
350+
if types[i].ID == typeID {
351+
return &types[i], nil
352+
}
353+
}
354+
355+
return nil, fmt.Errorf("resource type not found: %v", typeID)
356+
}
357+
358+
// GetResourceTypeBySlugCached returns a cached resource type by slug
359+
func (c *Client) GetResourceTypeBySlugCached(ctx context.Context, slug string) (*ResourceType, error) {
360+
types, err := c.GetResourceTypesCached(ctx)
361+
if err != nil {
362+
return nil, err
363+
}
364+
365+
for i := range types {
366+
if types[i].Slug == slug {
367+
return &types[i], nil
368+
}
369+
}
370+
371+
return nil, fmt.Errorf("resource type not found: %v", slug)
372+
}
373+
374+
// GetMetadataKeysCached returns cached metadata keys, fetching from API if cache is empty
375+
func (c *Client) GetMetadataKeysCached(ctx context.Context) ([]MetadataKey, error) {
376+
if c.metadataKeysCache != nil {
377+
return c.metadataKeysCache, nil
378+
}
379+
380+
keys, err := c.GetMetadataKeys(ctx, &GetMetadataKeysOptions{
381+
ContainMetadataPrivateKeys: true,
382+
})
383+
if err != nil {
384+
return nil, err
385+
}
386+
387+
c.metadataKeysCache = keys
388+
return keys, nil
389+
}
390+
391+
// GetDecryptedMetadataKeyCached returns a cached decrypted metadata key by ID
392+
// If not in cache, it will fetch and decrypt the key
393+
func (c *Client) GetDecryptedMetadataKeyCached(ctx context.Context, id string) (*crypto.Key, error) {
394+
// Check decrypted key cache first
395+
if key, ok := c.decryptedMetadataKeysCache[id]; ok {
396+
return key, nil
397+
}
398+
399+
// Get metadata keys (from cache or API)
400+
keys, err := c.GetMetadataKeysCached(ctx)
401+
if err != nil {
402+
return nil, fmt.Errorf("Get Metadata Keys: %w", err)
403+
}
404+
405+
// Find the key with matching ID
406+
var metadataKey *MetadataKey
407+
for i := range keys {
408+
if keys[i].ID == id {
409+
metadataKey = &keys[i]
410+
break
411+
}
412+
}
413+
414+
if metadataKey == nil {
415+
return nil, fmt.Errorf("Metadata key not found: %v", id)
416+
}
417+
418+
if len(metadataKey.MetadataPrivateKeys) == 0 {
419+
return nil, fmt.Errorf("No Metadata Private key for our user")
420+
}
421+
422+
// Find our user's private key
423+
var privMetadata *MetadataPrivateKey
424+
for i := range metadataKey.MetadataPrivateKeys {
425+
if metadataKey.MetadataPrivateKeys[i].UserID != nil && *metadataKey.MetadataPrivateKeys[i].UserID == c.userID {
426+
privMetadata = &metadataKey.MetadataPrivateKeys[i]
427+
break
428+
}
429+
}
430+
431+
if privMetadata == nil {
432+
return nil, fmt.Errorf("No Metadata Private key for our user id: %v", c.userID)
433+
}
434+
435+
decPrivMetadatakey, err := c.DecryptMessage(privMetadata.Data)
436+
if err != nil {
437+
return nil, fmt.Errorf("Decrypt Metadata Private Key Data: %w", err)
438+
}
439+
440+
var data MetadataPrivateKeyData
441+
err = json.Unmarshal([]byte(decPrivMetadatakey), &data)
442+
if err != nil {
443+
return nil, fmt.Errorf("Parse Metadata Private Key Data: %w", err)
444+
}
445+
446+
metadataPrivateKeyObj, err := GetPrivateKeyFromArmor(data.ArmoredKey, []byte(data.Passphrase))
447+
if err != nil {
448+
return nil, fmt.Errorf("Get Metadata Private Key: %w", err)
449+
}
450+
451+
// Cache the decrypted key
452+
c.decryptedMetadataKeysCache[id] = metadataPrivateKeyObj
453+
454+
return metadataPrivateKeyObj, nil
455+
}
456+
457+
// GetSessionKey retrieves a cached session key for a metadata key ID
458+
func (c *Client) GetSessionKey(metadataKeyID string) *crypto.SessionKey {
459+
return c.sessionKeyCache[metadataKeyID]
460+
}
461+
462+
// SetSessionKey stores a session key in the cache for a metadata key ID
463+
func (c *Client) SetSessionKey(metadataKeyID string, sessionKey *crypto.SessionKey) {
464+
c.sessionKeyCache[metadataKeyID] = sessionKey
465+
}

api/metadata.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,38 @@ type ResourceMetadataTypeV5TOTPStandalone struct {
4848
Description string `json:"description,omitempty"`
4949
}
5050

51+
// DecryptMetadata decrypts metadata using the provided key.
52+
// For session key caching, use DecryptMetadataWithKeyID instead.
5153
func (c *Client) DecryptMetadata(metadataKey *crypto.Key, armoredCiphertext string) (string, error) {
52-
// TODO Get SessionKey from Cache
53-
var sessionKey *crypto.SessionKey = nil
54+
return c.DecryptMetadataWithKeyID("", metadataKey, armoredCiphertext)
55+
}
5456

55-
if sessionKey != nil {
56-
message, err := c.DecryptMessageWithSessionKey(sessionKey, armoredCiphertext)
57-
// If Decrypt was successfull
58-
if err == nil {
59-
return message, nil
57+
// DecryptMetadataWithKeyID decrypts metadata using the provided key and caches the session key.
58+
// The metadataKeyID is used as the cache key for session key caching.
59+
// If metadataKeyID is empty, session key caching is disabled.
60+
func (c *Client) DecryptMetadataWithKeyID(metadataKeyID string, metadataKey *crypto.Key, armoredCiphertext string) (string, error) {
61+
// Try to get session key from cache
62+
if metadataKeyID != "" {
63+
if sessionKey := c.GetSessionKey(metadataKeyID); sessionKey != nil {
64+
message, err := c.DecryptMessageWithSessionKey(sessionKey, armoredCiphertext)
65+
// If decrypt was successful, return immediately
66+
if err == nil {
67+
return message, nil
68+
}
69+
// If failed, fall through to full decryption
70+
c.log("Session key cache miss for metadata key %v, falling back to full decryption", metadataKeyID)
6071
}
61-
// if this failed, fall through
6272
}
6373

6474
metadata, newSessionKey, err := c.DecryptMessageWithPrivateKeyAndReturnSessionKey(metadataKey, armoredCiphertext)
6575
if err != nil {
6676
return "", fmt.Errorf("Decrypting Metadata: %w", err)
6777
}
6878

69-
// TODO Save newSessionKey to cache
70-
_ = newSessionKey
79+
// Cache the session key for future use
80+
if metadataKeyID != "" && newSessionKey != nil {
81+
c.SetSessionKey(metadataKeyID, newSessionKey)
82+
}
7183

7284
return metadata, nil
7385
}
@@ -78,7 +90,5 @@ func (c *Client) EncryptMetadata(metadataKey *crypto.Key, data string) (string,
7890
return "", fmt.Errorf("Encrypting Metadata: %w", err)
7991
}
8092

81-
// TODO save Session Key to cache
82-
8393
return armoredCiphertext, nil
8494
}

helper/metadata.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,27 @@ import (
1212

1313
func GetResourceMetadata(ctx context.Context, c *api.Client, resource *api.Resource, rType *api.ResourceType) (string, error) {
1414
var metadatakey *crypto.Key
15+
var metadataKeyID string
16+
1517
if resource.MetadataKeyType == api.MetadataKeyTypeUserKey {
1618
tmp, err := c.GetUserPrivateKeyCopy()
1719
if err != nil {
1820
return "", fmt.Errorf("Get Private Key Copy: %w", err)
1921
}
2022
metadatakey = tmp
23+
// For user keys, we don't use session key caching (metadataKeyID stays empty)
2124
} else {
22-
key, err := c.GetMetadataKeyById(ctx, resource.MetadataKeyID)
25+
// Use cached decrypted metadata key
26+
metadataKeyID = resource.MetadataKeyID
27+
key, err := c.GetDecryptedMetadataKeyCached(ctx, metadataKeyID)
2328
if err != nil {
2429
return "", fmt.Errorf("Get Metadata Key by ID: %w", err)
2530
}
2631
metadatakey = key
2732
}
2833

29-
decMetadata, err := c.DecryptMetadata(metadatakey, resource.Metadata)
34+
// Use the version that caches session keys
35+
decMetadata, err := c.DecryptMetadataWithKeyID(metadataKeyID, metadatakey, resource.Metadata)
3036
if err != nil {
3137
return "", fmt.Errorf("Decrypt Metadata: %w", err)
3238
}

0 commit comments

Comments
 (0)