From 8733d8f6fc1c3fd575463bced83039f65e5829ad Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 12 Mar 2025 18:14:59 +0100 Subject: [PATCH 1/8] Add Metadata Key Structs, Types and Getter --- api/metadatakey.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 api/metadatakey.go diff --git a/api/metadatakey.go b/api/metadatakey.go new file mode 100644 index 0000000..482c864 --- /dev/null +++ b/api/metadatakey.go @@ -0,0 +1,86 @@ +package api + +import ( + "context" + "encoding/json" +) + +type MetadataKeyType string + +const ( + MetadataKeyTypeUserKey MetadataKeyType = "user_key" + MetadataKeyTypeSharedKey = "shared_key" +) + +func (s MetadataKeyType) IsValid() bool { + switch s { + case MetadataKeyTypeUserKey, MetadataKeyTypeSharedKey: + return true + } + return false +} + +// MetadataKey is a MetadataKey +type MetadataKey struct { + ID string `json:"id,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + ArmoredKey string `json:"armored_key,omitempty"` + Created Time `json:"created,omitempty"` + Modified Time `json:"modified,omitempty"` + + // These are always null? Used for Key Rotation? + //"expired": null, + //"deleted": null, + + CreatedBy *string `json:"created_by,omitempty"` + ModifiedBy *string `json:"modified_by,omitempty"` + + MetadataPrivateKeys []MetadataPrivateKey `json:"metadata_private_keys,omitempty"` +} + +// MetadataPrivateKey is a MetadataPrivateKey +type MetadataPrivateKey struct { + ID string `json:"id,omitempty"` + MetadataKeyID string `json:"metadata_key_id,omitempty"` + UserID *string `json:"user_id,omitempty"` // TODO, is this nullable. The Docs says yes and no + Data string `json:"data,omitempty"` + Created Time `json:"created,omitempty"` + Modified Time `json:"modified,omitempty"` + CreatedBy *string `json:"created_by,omitempty"` + ModifiedBy *string `json:"modified_by,omitempty"` +} + +// MetadataPrivateKeyData is a MetadataPrivateKeyData +type MetadataPrivateKeyData struct { + // ObjectType Must always be PASSBOLT_METADATA_PRIVATE_KEY + ObjectType string `json:"object_type,omitempty"` + // Domain Must be the Passbolt Server URL + Domain string `json:"domain,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + ArmoredKey string `json:"armored_key,omitempty"` + // Passphrase must be Empty for Server Keys + Passphrase string `json:"passphrase,omitempty"` +} + +// GetMetadataKeysOptions are all available query parameters +type GetMetadataKeysOptions struct { + FilterDeleted bool `url:"filter[deleted,omitempty"` + FilterExpired bool `url:"filter[expired,omitempty"` + + ContainMetadataPrivateKeys bool `url:"contain[metadata_private_keys],omitempty"` +} + +// GetMetadataKeys gets all Passbolt GetMetadataKeys +func (c *Client) GetMetadataKeys(ctx context.Context, opts *GetMetadataKeysOptions) ([]MetadataKey, error) { + msg, err := c.DoCustomRequest(ctx, "GET", "/metadata/keys.json", "v2", nil, opts) + if err != nil { + return nil, err + } + + var metadataKeys []MetadataKey + err = json.Unmarshal(msg.Body, &metadataKeys) + if err != nil { + return nil, err + } + return metadataKeys, nil +} From 862201be89597e6c8c2c8fec648c4f29521e4f2a Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 12 Mar 2025 18:16:05 +0100 Subject: [PATCH 2/8] Add Metadata Related Field to Resource and GetResourcesOptions --- api/resources.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/api/resources.go b/api/resources.go index c25ddaf..2215686 100644 --- a/api/resources.go +++ b/api/resources.go @@ -9,6 +9,7 @@ import ( // Resource is a Resource. // Warning: Since Passbolt v3 some fields here may not be populated as they may be in the Secret depending on the ResourceType, // for now the only Field like that is the Description. +// With Passbolt v5 it is now Possible that all Unencrypted User Supplied Fields are empty, and need to be decrypted from the Metadata Message type Resource struct { ID string `json:"id,omitempty"` Created *Time `json:"created,omitempty"` @@ -27,8 +28,13 @@ type Resource struct { FolderParentID string `json:"folder_parent_id,omitempty"` ResourceTypeID string `json:"resource_type_id,omitempty"` ResourceType ResourceType `json:"resource_type,omitempty"` - Secrets []Secret `json:"secrets,omitempty"` - Tags []Tag `json:"tags,omitempty"` + + MetadataKeyID string `json:"metadata_key_id,omitempty"` + MetadataKeyType MetadataKeyType `json:"metadata_key_type,omitempty"` + Metadata string `json:"metadata,omitempty"` + + Secrets []Secret `json:"secrets,omitempty"` + Tags []Tag `json:"tags,omitempty"` } // Tag is a Passbolt Password Tag @@ -48,6 +54,8 @@ type GetResourcesOptions struct { // Parent Folder id FilterHasParent []string `url:"filter[has-parent][],omitempty"` FilterHasTag string `url:"filter[has-tag],omitempty"` + // TODO Are undescores correct heare? + MetadataKeyType MetadataKeyType `url:"filter[metadata_key_type],omitempty"` ContainCreator bool `url:"contain[creator],omitempty"` ContainFavorites bool `url:"contain[favorite],omitempty"` From 53b7d7504a465d5d81170ec0f544fbbfc919461b Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 12 Mar 2025 18:21:40 +0100 Subject: [PATCH 3/8] Add Foreign Model Enum for Session Keys --- api/foreign_model.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 api/foreign_model.go diff --git a/api/foreign_model.go b/api/foreign_model.go new file mode 100644 index 0000000..ca87e2f --- /dev/null +++ b/api/foreign_model.go @@ -0,0 +1,19 @@ +package api + +type ForeignModelTypes string + +const ( + ForeignModelTypesResource ForeignModelTypes = "Resource" + ForeignModelTypesSecret ForeignModelTypes = "Secret" + ForeignModelTypesFolder = "Folder" + ForeignModelTypesComment = "Comment" + ForeignModelTypesTag = "Tag" +) + +func (s ForeignModelTypes) IsValid() bool { + switch s { + case ForeignModelTypesResource, ForeignModelTypesSecret, ForeignModelTypesFolder, ForeignModelTypesComment, ForeignModelTypesTag: + return true + } + return false +} From 02e63472ddf1cf6e2029a8b0afde4376bf19fd19 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 12 Mar 2025 18:26:02 +0100 Subject: [PATCH 4/8] Add Missing MetadataKeys and Get Option to Users, fix User Me --- api/users.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/api/users.go b/api/users.go index 9ce240b..a53e068 100644 --- a/api/users.go +++ b/api/users.go @@ -24,6 +24,9 @@ type User struct { GPGKey *GPGKey `json:"gpgKey,omitempty"` LastLoggedIn string `json:"last_logged_in,omitempty"` Locale string `json:"locale,omitempty"` + + // Admin only, needs contains + MissingMetadataKeyIDs []string `json:"missing_metadata_key_ids,omitempty"` } // Profile is a Profile @@ -42,6 +45,8 @@ type GetUsersOptions struct { FilterHasGroup []string `url:"filter[has-group][],omitempty"` FilterHasAccess []string `url:"filter[has-access][],omitempty"` FilterIsAdmin bool `url:"filter[is-admin],omitempty"` + // Admin only, TODO are underscores correct? + MissingMetadataKeyIDs bool `url:"filter[missing_metadata_key_ids],omitempty"` ContainLastLoggedIn bool `url:"contain[LastLoggedIn],omitempty"` } @@ -82,9 +87,11 @@ func (c *Client) GetMe(ctx context.Context) (*User, error) { // GetUser gets a Passbolt User func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) { - err := checkUUIDFormat(userID) - if err != nil { - return nil, fmt.Errorf("Checking ID format: %w", err) + if userID != "me" { + err := checkUUIDFormat(userID) + if err != nil { + return nil, fmt.Errorf("Checking ID format: %w", err) + } } msg, err := c.DoCustomRequest(ctx, "GET", "/users/"+userID+".json", "v2", nil, nil) if err != nil { @@ -101,9 +108,11 @@ func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) { // UpdateUser Updates a existing Passbolt User func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*User, error) { - err := checkUUIDFormat(userID) - if err != nil { - return nil, fmt.Errorf("Checking ID format: %w", err) + if userID != "me" { + err := checkUUIDFormat(userID) + if err != nil { + return nil, fmt.Errorf("Checking ID format: %w", err) + } } msg, err := c.DoCustomRequest(ctx, "PUT", "/users/"+userID+".json", "v2", user, nil) if err != nil { @@ -119,11 +128,13 @@ func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*Use // DeleteUser Deletes a Passbolt User func (c *Client) DeleteUser(ctx context.Context, userID string) error { - err := checkUUIDFormat(userID) - if err != nil { - return fmt.Errorf("Checking ID format: %w", err) + if userID != "me" { + err := checkUUIDFormat(userID) + if err != nil { + return fmt.Errorf("Checking ID format: %w", err) + } } - _, err = c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+".json", "v2", nil, nil) + _, err := c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+".json", "v2", nil, nil) if err != nil { return err } From 9348a96af53a157b2699aaa3b74e2af70e0cb6b7 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 12 Mar 2025 18:32:12 +0100 Subject: [PATCH 5/8] Add Basic Metadata Session Key Structs and functions --- api/metadata_sessionkey.go | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 api/metadata_sessionkey.go diff --git a/api/metadata_sessionkey.go b/api/metadata_sessionkey.go new file mode 100644 index 0000000..cf18abe --- /dev/null +++ b/api/metadata_sessionkey.go @@ -0,0 +1,61 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" +) + +// MetadataSessionKey is a MetadataSessionKey +type MetadataSessionKey struct { + ID string `json:"id,omitempty"` + UserID string `json:"user_id,omitempty"` + Data string `json:"data,omitempty"` + Created Time `json:"created,omitempty"` + Modified Time `json:"modified,omitempty"` +} + +// MetadataSessionKeyData is a MetadataSessionKeyData +type MetadataSessionKeyData struct { + // ObjectType Must always be PASSBOLT_SESSION_KEYS + ObjectType string `json:"object_type,omitempty"` + SessionKeys []MetadataSessionKeyDataElement `json:"session_keys,omitempty"` +} + +// MetadataSessionKeyData is a MetadataSessionKeyData +type MetadataSessionKeyDataElement struct { + ForeignModel ForeignModelTypes `json:"foreign_model"` + ForeignID string `json:"foreign_id"` + SessionKey string `json:"session_key"` + Modified Time `json:"modified"` +} + +// GetMetadataTypeSettings gets the Servers Settings about which Types to use +func (c *Client) GetMetadataSessionKeys(ctx context.Context) ([]MetadataSessionKey, error) { + msg, err := c.DoCustomRequest(ctx, "GET", "/metadata/session-keys.json", "v2", nil, nil) + if err != nil { + return nil, err + } + + var metadataSessionKeys []MetadataSessionKey + err = json.Unmarshal(msg.Body, &metadataSessionKeys) + if err != nil { + return nil, err + } + return metadataSessionKeys, nil +} + +// TODO add Create and Update + +// DeleteSessionKey Deletes a Passbolt SessionKey +func (c *Client) DeleteSessionKey(ctx context.Context, sessionKeyID string) error { + err := checkUUIDFormat(sessionKeyID) + if err != nil { + return fmt.Errorf("Checking ID format: %w", err) + } + _, err = c.DoCustomRequest(ctx, "DELETE", "/metadata/session-keys/"+sessionKeyID+".json", "v2", nil, nil) + if err != nil { + return err + } + return nil +} From 2afac57e2955756e4b271ff2d299eabcf5faf5cc Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 13 Mar 2025 17:45:42 +0100 Subject: [PATCH 6/8] Fix MetadataKey Filter --- api/metadatakey.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/metadatakey.go b/api/metadatakey.go index 482c864..b968258 100644 --- a/api/metadatakey.go +++ b/api/metadatakey.go @@ -64,8 +64,8 @@ type MetadataPrivateKeyData struct { // GetMetadataKeysOptions are all available query parameters type GetMetadataKeysOptions struct { - FilterDeleted bool `url:"filter[deleted,omitempty"` - FilterExpired bool `url:"filter[expired,omitempty"` + FilterDeleted bool `url:"filter[deleted],omitempty"` + FilterExpired bool `url:"filter[expired],omitempty"` ContainMetadataPrivateKeys bool `url:"contain[metadata_private_keys],omitempty"` } From 7bc894763cf1df1906b6aed4ad4cc06b3ed5f5f6 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Fri, 14 Mar 2025 13:28:27 +0100 Subject: [PATCH 7/8] Add DoCustomRequestV5 --- api/api.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/api.go b/api/api.go index 8f94d7e..22d70f2 100644 --- a/api/api.go +++ b/api/api.go @@ -26,12 +26,22 @@ type APIHeader struct { } // DoCustomRequest Executes a Custom Request and returns a APIResponse +// +// Deprecated: DoCustomRequest is deprecated. Use DoCustomRequestV5 instead func (c *Client) DoCustomRequest(ctx context.Context, method, path, version string, body interface{}, opts interface{}) (*APIResponse, error) { _, response, err := c.DoCustomRequestAndReturnRawResponse(ctx, method, path, version, body, opts) return response, err } +// DoCustomRequestV5 Executes a Custom Request and returns a APIResponse +func (c *Client) DoCustomRequestV5(ctx context.Context, method, path string, body interface{}, opts interface{}) (*APIResponse, error) { + _, response, err := c.DoCustomRequestAndReturnRawResponseV5(ctx, method, path, body, opts) + return response, err +} + // DoCustomRequestAndReturnRawResponse Executes a Custom Request and returns a APIResponse and the Raw HTTP Response +// +// Deprecated: DoCustomRequestAndReturnRawResponse is deprecated. Use DoCustomRequestAndReturnRawResponseV5 instead func (c *Client) DoCustomRequestAndReturnRawResponse(ctx context.Context, method, path, version string, body interface{}, opts interface{}) (*http.Response, *APIResponse, error) { // version is no longer used and is ignored. return c.DoCustomRequestAndReturnRawResponseV5(ctx, method, path, body, opts) From 057d2e5f5953d422d813f9e897463057dad2c2cc Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Fri, 14 Mar 2025 13:29:07 +0100 Subject: [PATCH 8/8] Migrate new things to DoCustomRequestV5, fix Comments --- api/metadata_sessionkey.go | 8 ++++---- api/metadata_settings.go | 4 ++-- api/metadatakey.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/metadata_sessionkey.go b/api/metadata_sessionkey.go index cf18abe..7cada9d 100644 --- a/api/metadata_sessionkey.go +++ b/api/metadata_sessionkey.go @@ -22,7 +22,7 @@ type MetadataSessionKeyData struct { SessionKeys []MetadataSessionKeyDataElement `json:"session_keys,omitempty"` } -// MetadataSessionKeyData is a MetadataSessionKeyData +// MetadataSessionKeyDataElement is a MetadataSessionKeyDataElement type MetadataSessionKeyDataElement struct { ForeignModel ForeignModelTypes `json:"foreign_model"` ForeignID string `json:"foreign_id"` @@ -30,9 +30,9 @@ type MetadataSessionKeyDataElement struct { Modified Time `json:"modified"` } -// GetMetadataTypeSettings gets the Servers Settings about which Types to use +// GetMetadataSessionKeys gets the Metadata Session Keys func (c *Client) GetMetadataSessionKeys(ctx context.Context) ([]MetadataSessionKey, error) { - msg, err := c.DoCustomRequest(ctx, "GET", "/metadata/session-keys.json", "v2", nil, nil) + msg, err := c.DoCustomRequestV5(ctx, "GET", "/metadata/session-keys.json", nil, nil) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func (c *Client) DeleteSessionKey(ctx context.Context, sessionKeyID string) erro if err != nil { return fmt.Errorf("Checking ID format: %w", err) } - _, err = c.DoCustomRequest(ctx, "DELETE", "/metadata/session-keys/"+sessionKeyID+".json", "v2", nil, nil) + _, err = c.DoCustomRequestV5(ctx, "DELETE", "/metadata/session-keys/"+sessionKeyID+".json", nil, nil) if err != nil { return err } diff --git a/api/metadata_settings.go b/api/metadata_settings.go index b54158f..ae80715 100644 --- a/api/metadata_settings.go +++ b/api/metadata_settings.go @@ -9,7 +9,7 @@ type PassboltAPIVersionType string const ( PassboltAPIVersionTypeV4 PassboltAPIVersionType = "v4" - PassboltAPIVersionTypeV5 = "v5" + PassboltAPIVersionTypeV5 PassboltAPIVersionType = "v5" ) func (s PassboltAPIVersionType) IsValid() bool { @@ -59,7 +59,7 @@ func getV4DefaultMetadataTypeSettings() MetadataTypeSettings { // GetMetadataTypeSettings gets the Servers Settings about which Types to use func (c *Client) GetMetadataTypeSettings(ctx context.Context) (*MetadataTypeSettings, error) { - msg, err := c.DoCustomRequest(ctx, "GET", "/metadata/types/settings.json", "v3", nil, nil) + msg, err := c.DoCustomRequestV5(ctx, "GET", "/metadata/types/settings.json", nil, nil) if err != nil { return nil, err } diff --git a/api/metadatakey.go b/api/metadatakey.go index b968258..49db61c 100644 --- a/api/metadatakey.go +++ b/api/metadatakey.go @@ -9,7 +9,7 @@ type MetadataKeyType string const ( MetadataKeyTypeUserKey MetadataKeyType = "user_key" - MetadataKeyTypeSharedKey = "shared_key" + MetadataKeyTypeSharedKey MetadataKeyType = "shared_key" ) func (s MetadataKeyType) IsValid() bool { @@ -72,7 +72,7 @@ type GetMetadataKeysOptions struct { // GetMetadataKeys gets all Passbolt GetMetadataKeys func (c *Client) GetMetadataKeys(ctx context.Context, opts *GetMetadataKeysOptions) ([]MetadataKey, error) { - msg, err := c.DoCustomRequest(ctx, "GET", "/metadata/keys.json", "v2", nil, opts) + msg, err := c.DoCustomRequestV5(ctx, "GET", "/metadata/keys.json", nil, opts) if err != nil { return nil, err }