From 819912aa3537f21f4c29b02e60a8930c118a8681 Mon Sep 17 00:00:00 2001 From: Ilja <47740286+iljaSL@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:29:10 +0200 Subject: [PATCH] fix: refactor authorizer v2 (#162) * fix: refactor authorizer v2 * feat(authorizer): support handling of custom param structs with url tags * revert(auth/authorizer): rename ID * fix(response): rename struct field to ID * fix(authorizer): remove not requried handlers, fix naming in structs --- api/auth/client.go | 52 +-- api/auth/model.go | 19 +- api/authorizer/client.go | 652 +++++++++++++++++++---------------- api/authorizer/model.go | 342 +++++++++--------- api/filters/filters.go | 113 +++--- api/filters/urlquery.go | 268 ++++++++++++++ api/filters/urlquery_test.go | 178 ++++++++++ api/response/response.go | 2 +- restapi/client.go | 10 + 9 files changed, 1108 insertions(+), 528 deletions(-) create mode 100644 api/filters/urlquery.go create mode 100644 api/filters/urlquery_test.go diff --git a/api/auth/client.go b/api/auth/client.go index 8cb9a81..ec24a7c 100644 --- a/api/auth/client.go +++ b/api/auth/client.go @@ -7,6 +7,8 @@ package auth import ( + "net/url" + "github.com/SSHcom/privx-sdk-go/api/filters" "github.com/SSHcom/privx-sdk-go/api/response" "github.com/SSHcom/privx-sdk-go/restapi" @@ -47,29 +49,29 @@ func (c *Auth) CreateIdpClient(idpClient *IdpClient) (response.Identifier, error } // UpdateIdpClient updates existing identity provider client configuration definition. -func (c *Auth) UpdateIdpClient(idpClient *IdpClient, idpId string) error { +func (c *Auth) UpdateIdpClient(idpClient *IdpClient, idpID string) error { _, err := c.api. - URL("/auth/api/v1/idp/clients/%s", idpId). + URL("/auth/api/v1/idp/clients/%s", idpID). Put(&idpClient) return err } // GetIdpClient get existing identity provider client configuration. -func (c *Auth) GetIdpClient(idpId string) (*IdpClient, error) { +func (c *Auth) GetIdpClient(idpID string) (*IdpClient, error) { idpClient := &IdpClient{} _, err := c.api. - URL("/auth/api/v1/idp/clients/%s", idpId). + URL("/auth/api/v1/idp/clients/%s", idpID). Get(&idpClient) return idpClient, err } -// DeleteIdpClient delete identity provider client configuration by Id. -func (c *Auth) DeleteIdpClient(idpId string) error { +// DeleteIdpClient delete identity provider client configuration by id. +func (c *Auth) DeleteIdpClient(idpID string) error { _, err := c.api. - URL("/auth/api/v1/idp/clients/%s", idpId). + URL("/auth/api/v1/idp/clients/%s", idpID). Delete() return err @@ -77,11 +79,11 @@ func (c *Auth) DeleteIdpClient(idpId string) error { // RegenerateIdpClientConfig regenerates client_id and client_secret // for OIDC identity provider client configuration. -func (c *Auth) RegenerateIdpClientConfig(idpId string) (*IdpClientConfig, error) { +func (c *Auth) RegenerateIdpClientConfig(idpID string) (*IdpClientConfig, error) { clientConfig := &IdpClientConfig{} _, err := c.api. - URL("/auth/api/v1/idp/clients/%s/regenerate", idpId). + URL("/auth/api/v1/idp/clients/%s/regenerate", idpID). Post(nil, &clientConfig) return clientConfig, err @@ -89,16 +91,16 @@ func (c *Auth) RegenerateIdpClientConfig(idpId string) (*IdpClientConfig, error) // MARK: Session Storage // GetUserSessions get valid sessions by userID. -func (c *Auth) GetUserSessions(userId string, opts ...filters.Option) (*response.ResultSet[Session], error) { +func (c *Auth) GetUserSessions(userID string, opts ...filters.Option) (*response.ResultSet[Session], error) { userSessions := &response.ResultSet[Session]{} - params := filters.Default() + params := url.Values{} for _, opt := range opts { opt(¶ms) } _, err := c.api. - URL("/auth/api/v1/sessionstorage/users/%s/sessions", userId). + URL("/auth/api/v1/sessionstorage/users/%s/sessions", userID). Query(params). Get(&userSessions) @@ -106,16 +108,16 @@ func (c *Auth) GetUserSessions(userId string, opts ...filters.Option) (*response } // GetSourceSessions get valid sessions by sourceID. -func (c *Auth) GetSourceSessions(sourceId string, opts ...filters.Option) (*response.ResultSet[Session], error) { +func (c *Auth) GetSourceSessions(sourceID string, opts ...filters.Option) (*response.ResultSet[Session], error) { sourceSessions := &response.ResultSet[Session]{} - params := filters.Default() + params := url.Values{} for _, opt := range opts { opt(¶ms) } _, err := c.api. - URL("/auth/api/v1/sessionstorage/sources/%s/sessions", sourceId). + URL("/auth/api/v1/sessionstorage/sources/%s/sessions", sourceID). Query(params). Get(&sourceSessions) @@ -125,7 +127,7 @@ func (c *Auth) GetSourceSessions(sourceId string, opts ...filters.Option) (*resp // SearchSessions searches for sessions func (c *Auth) SearchSessions(search *SessionSearch, opts ...filters.Option) (*response.ResultSet[Session], error) { sessions := &response.ResultSet[Session]{} - params := filters.Default() + params := url.Values{} for _, opt := range opts { opt(¶ms) @@ -139,19 +141,19 @@ func (c *Auth) SearchSessions(search *SessionSearch, opts ...filters.Option) (*r return sessions, err } -// TerminateSession terminates single session by Id. -func (c *Auth) TerminateSession(sessionId string) error { +// TerminateSession terminates single session by id. +func (c *Auth) TerminateSession(sessionID string) error { _, err := c.api. - URL("/auth/api/v1/sessionstorage/sessions/%s/terminate", sessionId). + URL("/auth/api/v1/sessionstorage/sessions/%s/terminate", sessionID). Post(nil) return err } // TerminateUserSessions terminates all sessions for a user. -func (store *Auth) TerminateUserSessions(userId string) error { +func (store *Auth) TerminateUserSessions(userID string) error { _, err := store.api. - URL("/auth/api/v1/sessionstorage/users/%s/sessions/terminate", userId). + URL("/auth/api/v1/sessionstorage/users/%s/sessions/terminate", userID). Post(nil) return err @@ -169,20 +171,20 @@ func (store *Auth) Logout() error { // MARK: Mobile Gateway // GetUserPairedDevices get users paired devices. -func (store *Auth) GetUserPairedDevices(userId string) (*response.ResultSet[Device], error) { +func (store *Auth) GetUserPairedDevices(userID string) (*response.ResultSet[Device], error) { devices := &response.ResultSet[Device]{} _, err := store.api. - URL("/auth/api/v1/users/%s/devices", userId). + URL("/auth/api/v1/users/%s/devices", userID). Get(devices) return devices, err } // UnpairUserDevice unpair users device. -func (store *Auth) UnpairUserDevice(userId, deviceId string) error { +func (store *Auth) UnpairUserDevice(userID, deviceID string) error { _, err := store.api. - URL("/auth/api/v1/users/%s/devices/%s", userId, deviceId). + URL("/auth/api/v1/users/%s/devices/%s", userID, deviceID). Delete() return err diff --git a/api/auth/model.go b/api/auth/model.go index bef8f90..2b9365c 100644 --- a/api/auth/model.go +++ b/api/auth/model.go @@ -10,14 +10,14 @@ import "time" // IdpClient identity provider client definition. type IdpClient struct { - Id string `json:"id"` + ID string `json:"id"` Name string `json:"name"` Created time.Time `json:"created,omitempty"` Updated time.Time `json:"updated,omitempty"` - IdpType string `json:"idp_type"` + IDPType string `json:"idp_type"` OIDCIssuer string `json:"oidc_issuer,omitempty"` OIDCAudience []string `json:"oidc_audience"` - OIDCClientId string `json:"oidc_client_id,omitempty"` + OIDCClientID string `json:"oidc_client_id,omitempty"` OIDCClientSecret string `json:"oidc_client_secret,omitempty"` OIDCScopesEnabled []string `json:"oidc_scopes_enabled"` OIDCResponseTypesSupported []string `json:"oidc_response_types_supported,omitempty"` @@ -35,6 +35,7 @@ type IdpClient struct { OIDCRefreshTokenValidInMinutes int `json:"oidc_refresh_token_valid_in_minutes,omitempty"` UserFilter string `json:"user_filter,omitempty"` Enabled bool `json:"enabled"` + ContainerRequired bool `json:"container_required,omitempty"` } // IdpClientConfig identity provider client config definition. @@ -45,15 +46,15 @@ type IdpClientConfig struct { // Session session definition type Session struct { - Id string `json:"id"` - UserId string `json:"user_id"` - SourceId string `json:"source_id"` + ID string `json:"id"` + UserID string `json:"user_id"` + SourceID string `json:"source_id"` Domain string `json:"domain"` Username string `json:"username"` RemoteAddr string `json:"remote_addr"` UserAgent string `json:"user_agent"` Type string `json:"type"` - ParentSessionId string `json:"parent_session_id,omitempty"` + ParentSessionID string `json:"parent_session_id,omitempty"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` Expires time.Time `json:"expires"` @@ -77,13 +78,13 @@ type SessionPasswordPolicy struct { // SessionSearch session search request parameter definition. type SessionSearch struct { Keywords string `json:"keywords,omitempty"` - UserId string `json:"user_id,omitempty"` + UserID string `json:"user_id,omitempty"` Type string `json:"type,omitempty"` } // Device paired mobile gateway device definition. type Device struct { - Id string `json:"id"` + ID string `json:"id"` OS string `json:"os"` Name string `json:"name"` Activated string `json:"activated"` diff --git a/api/authorizer/client.go b/api/authorizer/client.go index b8ae298..531d642 100644 --- a/api/authorizer/client.go +++ b/api/authorizer/client.go @@ -9,48 +9,74 @@ package authorizer import ( "net/url" + "github.com/SSHcom/privx-sdk-go/api/filters" + "github.com/SSHcom/privx-sdk-go/api/response" "github.com/SSHcom/privx-sdk-go/restapi" ) -// Client is a authorizer client instance. -type Client struct { +// Authorizer is a authorizer client instance. +type Authorizer struct { api restapi.Connector } -// New creates a new authorizer client instance -func New(api restapi.Connector) *Client { - return &Client{api: api} +// New authorizer client constructor. +func New(api restapi.Connector) *Authorizer { + return &Authorizer{api: api} } -// CACertificates gets authorizer's root certificates -func (auth *Client) CACertificates(accessGroupID string) ([]CA, error) { - ca := []CA{} - filters := Params{ - AccessGroupID: accessGroupID, +// MARK: Status +// Status get authorizer microservice status. +func (c *Authorizer) Status() (*response.ServiceStatus, error) { + status := &response.ServiceStatus{} + + _, err := c.api. + URL("/authorizer/api/v1/status"). + Get(status) + + return status, err +} + +// MARK: CAS +// GetCACertificates get authorizers root certificates. +// Note, the v1 endpoint doesn't return the count as part of the response body, +// this will change with v2. Until then, we will handle it internally within the SDK. +func (c *Authorizer) GetCACertificates(opts ...filters.Option) (*response.ResultSet[CA], error) { + cas := []CA{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/cas"). - Query(&filters). - Get(&ca) + Query(params). + Get(&cas) + + // v1 endpoint does not return count, + // return count internally in sdk until v2 is introduced + certs := &response.ResultSet[CA]{ + Items: cas, + Count: len(cas), + } - return ca, err + return certs, err } -// CACertificate gets authorizer's root certificate -func (auth *Client) CACertificate(caID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/cas/%s", url.PathEscape(caID)). +// DownloadCACertificate fetch authorizers root certificate as a download object. +func (c *Authorizer) DownloadCACertificate(caID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/cas/%s", caID). Download(filename) return err } // CAConfig get authorizers root certificate config by ca type. -func (auth *Client) CAConfig(caType string) (ComponentCaConfig, error) { +func (c *Authorizer) CAConfig(caType string) (ComponentCaConfig, error) { caConf := ComponentCaConfig{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/%s/cas/config", caType). Get(&caConf) @@ -58,401 +84,440 @@ func (auth *Client) CAConfig(caType string) (ComponentCaConfig, error) { } // UpdateCAConfig update authorizers root certificate config by ca type. -func (auth *Client) UpdateCAConfig(caType string, caConf ComponentCaConfig) error { - _, err := auth.api. +func (c *Authorizer) UpdateCAConfig(caType string, caConf ComponentCaConfig) error { + _, err := c.api. URL("/authorizer/api/v1/%s/cas/config", caType). Put(&caConf) return err } -// CertificateRevocationList gets authorizer CA's certificate revocation list. -func (auth *Client) CertificateRevocationList(caID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/cas/%s/crl", url.PathEscape(caID)). +// DownloadCertificateRevocationList fetch authorizer CA certificate revocation list as a download object. +func (c *Authorizer) DownloadCertificateRevocationList(caID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/cas/%s/crl", caID). Download(filename) return err } -// TargetHostCredentials get target host credentials for the user -func (auth *Client) TargetHostCredentials(authorizer *AuthorizationRequest) (*ApiIdentitiesResponse, error) { +// GetTargetHostCredentials get target host credentials for the user. +func (c *Authorizer) GetTargetHostCredentials(request *ApiIdentities) (*ApiIdentitiesResponse, error) { principal := &ApiIdentitiesResponse{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/ca/authorize"). - Post(&authorizer, &principal) + Post(&request, &principal) return principal, err } -// Principals gets defined principals from the authorizer -func (auth *Client) Principals() ([]Principal, error) { - principals := []Principal{} +// MARK: Principals +// GetPrincipals get defined principals. +// Note, the v1 endpoint doesn't return the count as part of the response body, +// this will change with v2. Until then, we will handle it internally within the SDK. +func (c *Authorizer) GetPrincipals() (*response.ResultSet[Principal], error) { + p := []Principal{} - _, err := auth.api. - URL("/authorizer/api/v1/cas"). - Get(&principals) + _, err := c.api. + URL("/authorizer/api/v1/principals"). + Get(&p) + + // v1 endpoint does not return count, + // return count internally in sdk until v2 is introduced + principals := &response.ResultSet[Principal]{ + Count: len(p), + Items: p, + } return principals, err } -// Principal gets the principal key by its group ID -func (auth *Client) Principal(groupID, keyID, filter string) (*Principal, error) { +// GetPrincipal get principal by its group id. +func (c *Authorizer) GetPrincipal(groupID string, opts ...filters.Option) (*Principal, error) { principal := &Principal{} - filters := Params{ - KeyID: keyID, - Filter: filter, + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. - URL("/authorizer/api/v1/principals/%s", url.PathEscape(groupID)). - Query(&filters). + _, err := c.api. + URL("/authorizer/api/v1/principals/%s", groupID). + Query(params). Get(&principal) return principal, err } -// DeletePrincipalKey delete the principal key by its group ID -func (auth *Client) DeletePrincipalKey(groupID, keyID string) error { - filters := Params{ - KeyID: keyID, +// DeletePrincipalKey delete the principal key by its group id. +func (c *Authorizer) DeletePrincipalKey(groupID string, opts ...filters.Option) error { + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. - URL("/authorizer/api/v1/principals/%s", url.PathEscape(groupID)). - Query(filters). + _, err := c.api. + URL("/authorizer/api/v1/principals/%s", groupID). + Query(params). Delete() return err } -// CreatePrincipalKey create a principal key pair -func (auth *Client) CreatePrincipalKey(groupID string) (*Principal, error) { +// CreatePrincipalKey create a principal key pair. +func (c *Authorizer) CreatePrincipalKey(groupID string) (*Principal, error) { principal := &Principal{} - _, err := auth.api. - URL("/authorizer/api/v1/principals/%s/create", url.PathEscape(groupID)). + _, err := c.api. + URL("/authorizer/api/v1/principals/%s/create", groupID). Post(nil, &principal) return principal, err } -// ImportPrincipalKey mport a principal key pair -func (auth *Client) ImportPrincipalKey(groupID string, key *PrincipalKeyImportRequest) (*Principal, error) { +// ImportPrincipalKey import a principal key pair. +func (c *Authorizer) ImportPrincipalKey(groupID string, key *PrincipalKeyImport) (*Principal, error) { principal := &Principal{} - _, err := auth.api. - URL("/authorizer/api/v1/principals/%s/import", url.PathEscape(groupID)). + _, err := c.api. + URL("/authorizer/api/v1/principals/%s/import", groupID). Post(&key, &principal) return principal, err } -// SignPrincipalKey sign a principal key and get a signature -func (auth *Client) SignPrincipalKey(groupID, keyID string, credential *Credential) (*Signature, error) { +// SignPrincipalKey get a principal key signature. +func (c *Authorizer) SignPrincipalKey(groupID string, sign *PrincipalKeySign, opts ...filters.Option) (*Signature, error) { signature := &Signature{} - filters := Params{ - KeyID: keyID, + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. - URL("/authorizer/api/v1/principals/%s/sign", url.PathEscape(groupID)). - Query(&filters). - Post(&credential, &signature) + _, err := c.api. + URL("/authorizer/api/v1/principals/%s/sign", groupID). + Query(params). + Post(&sign, &signature) return signature, err } -// ExtenderCACertificates gets authorizer's extender CA certificates -func (auth *Client) ExtenderCACertificates(accessGroupID string) ([]CA, error) { - certificates := []CA{} - filters := Params{ - AccessGroupID: accessGroupID, +// MARK: Extender +// GetExtenderCACertificates gets authorizers extender CA certificates. +// Note, the v1 endpoint doesn't return the count as part of the response body, +// this will change with v2. Until then, we will handle it internally within the SDK. +func (c *Authorizer) GetExtenderCACertificates(opts ...filters.Option) (*response.ResultSet[CA], error) { + cs := []CA{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/extender/cas"). - Query(&filters). - Get(&certificates) + Query(params). + Get(&cs) + + // v1 endpoint does not return count, + // return count internally in sdk until v2 is introduced + certificates := &response.ResultSet[CA]{ + Count: len(cs), + Items: cs, + } return certificates, err } -// ExtenderCACertificate gets authorizer's extender CA certificate -func (auth *Client) ExtenderCACertificate(id string) (*CA, error) { - certificate := &CA{} - - _, err := auth.api. - URL("/authorizer/api/v1/extender/cas/%s", url.PathEscape(id)). - Get(&certificate) +// DownloadExtenderCACertificate fetch authorizers extender CA certificate by id as a download object. +func (c *Authorizer) DownloadExtenderCACertificate(filename, id string) error { + err := c.api. + URL("/authorizer/api/v1/extender/cas/%s", id). + Download(filename) - return certificate, err + return err } -// DownloadExtenderCertificateCRL gets authorizer CA's certificate revocation list -func (auth *Client) DownloadExtenderCertificateCRL(filename, id string) error { - err := auth.api. - URL("/authorizer/api/v1/extender/cas/%s/crl", url.PathEscape(id)). +// DownloadExtenderCertificateCRL fetch authorizer CA certificate revocation list as a download object. +func (c *Authorizer) DownloadExtenderCertificateCRL(filename, id string) error { + err := c.api. + URL("/authorizer/api/v1/extender/cas/%s/crl", id). Download(filename) return err } -// ExtenderConfigDownloadHandle get a session id -func (auth *Client) ExtenderConfigDownloadHandle(trustedClientID string) (*DownloadHandle, error) { - sessionID := &DownloadHandle{} +// GetExtenderConfigSessions get extenders config session ids. +func (c *Authorizer) GetExtenderConfigSessions(trustedClientID string) (*SessionIDResponse, error) { + sessionIDs := &SessionIDResponse{} - _, err := auth.api. - URL("/authorizer/api/v1/extender/conf/%s", url.PathEscape(trustedClientID)). - Post(nil, &sessionID) + _, err := c.api. + URL("/authorizer/api/v1/extender/conf/%s", trustedClientID). + Post(nil, &sessionIDs) - return sessionID, err + return sessionIDs, err } -// DownloadExtenderConfig gets a pre-configured extender config -func (auth *Client) DownloadExtenderConfig(trustedClientID, sessionID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/extender/conf/%s/%s", url.PathEscape(trustedClientID), url.PathEscape(sessionID)). +// DownloadExtenderConfig fetch a pre-configured extender config as a download object. +func (c *Authorizer) DownloadExtenderConfig(trustedClientID, sessionID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/extender/conf/%s/%s", trustedClientID, sessionID). Download(filename) return err } -// DeployScriptDownloadHandle get a session id for a deployment script -func (auth *Client) DeployScriptDownloadHandle(trustedClientID string) (*DownloadHandle, error) { - sessionID := &DownloadHandle{} +// MARK: Deploy +// GetDeployScriptSessions get deploy script session ids. +func (c *Authorizer) GetDeployScriptSessions(trustedClientID string) (*SessionIDResponse, error) { + sessionIDs := &SessionIDResponse{} - _, err := auth.api. - URL("/authorizer/api/v1/deploy/%s", url.PathEscape(trustedClientID)). - Post(nil, &sessionID) + _, err := c.api. + URL("/authorizer/api/v1/deploy/%s", trustedClientID). + Post(nil, &sessionIDs) - return sessionID, err + return sessionIDs, err } -// DownloadDeployScript gets a pre-configured deployment script -func (auth *Client) DownloadDeployScript(trustedClientID, sessionID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/deploy/%s/%s", url.PathEscape(trustedClientID), url.PathEscape(sessionID)). +// DownloadDeployScript fetch a pre-configured deployment script. +func (c *Authorizer) DownloadDeployScript(trustedClientID, sessionID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/deploy/%s/%s", trustedClientID, sessionID). Download(filename) return err } -// DownloadPrincipalCommandScript gets the principals_command.sh script -func (auth *Client) DownloadPrincipalCommandScript(filename string) error { - err := auth.api. +// DownloadPrincipalCommandScript fetch the principals_command.sh script. +func (c *Authorizer) DownloadPrincipalCommandScript(filename string) error { + err := c.api. URL("/authorizer/api/v1/deploy/principals_command.sh"). Download(filename) return err } -// CarrierConfigDownloadHandle get a session id for a carrier config -func (auth *Client) CarrierConfigDownloadHandle(trustedClientID string) (*DownloadHandle, error) { - sessionID := &DownloadHandle{} +// MARK: Carrier +// // GetCarrierConfigSessions get carrier config session ids. +func (c *Authorizer) GetCarrierConfigSessions(trustedClientID string) (*SessionIDResponse, error) { + sessionIDs := &SessionIDResponse{} - _, err := auth.api. - URL("/authorizer/api/v1/carrier/conf/%s", url.PathEscape(trustedClientID)). - Post(nil, &sessionID) + _, err := c.api. + URL("/authorizer/api/v1/carrier/conf/%s", trustedClientID). + Post(nil, &sessionIDs) - return sessionID, err + return sessionIDs, err } -// DownloadCarrierConfig gets a pre-configured carrier config -func (auth *Client) DownloadCarrierConfig(trustedClientID, sessionID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/carrier/conf/%s/%s", url.PathEscape(trustedClientID), url.PathEscape(sessionID)). +// DownloadCarrierConfig fetch a pre-configured carrier config. +func (c *Authorizer) DownloadCarrierConfig(trustedClientID, sessionID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/carrier/conf/%s/%s", trustedClientID, sessionID). Download(filename) return err } -// WebProxyCACertificates gets authorizer's web proxy CA certificates -func (auth *Client) WebProxyCACertificates(accessGroupID string) ([]CA, error) { - certificates := []CA{} - filters := Params{ - AccessGroupID: accessGroupID, +// MARK: Web-Proxy +// GetWebProxyCACertificates gets authorizer's web proxy CA certificates. +// Note, the v1 endpoint doesn't return the count as part of the response body, +// this will change with v2. Until then, we will handle it internally within the SDK. +func (c *Authorizer) GetWebProxyCACertificates(opts ...filters.Option) (*response.ResultSet[CA], error) { + cs := []CA{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/icap/cas"). - Query(&filters). - Get(&certificates) + Query(params). + Get(&cs) + + // v1 endpoint does not return count, + // return count internally in sdk until v2 is introduced + certificates := &response.ResultSet[CA]{ + Count: len(cs), + Items: cs, + } return certificates, err } -// WebProxyCACertificate gets authorizer's web proxy CA certificate -func (auth *Client) WebProxyCACertificate(trustedClientID string) (*CA, error) { +// GetWebProxyCACertificate gets authorizer's web proxy CA certificate by id. +func (c *Authorizer) GetWebProxyCACertificate(id string) (*CA, error) { certificate := &CA{} - _, err := auth.api. - URL("/authorizer/api/v1/icap/cas/%s", url.PathEscape(trustedClientID)). + _, err := c.api. + URL("/authorizer/api/v1/icap/cas/%s", id). Get(&certificate) return certificate, err } -// DownloadWebProxyCertificateCRL gets authorizer CA's certificate revocation list -func (auth *Client) DownloadWebProxyCertificateCRL(filename, trustedClientID string) error { - err := auth.api. - URL("/authorizer/api/v1/icap/cas/%s/crl", url.PathEscape(trustedClientID)). +// DownloadWebProxyCertificateCRL fetch authorizer CA certificate revocation list as a download object. +func (c *Authorizer) DownloadWebProxyCertificateCRL(filename, id string) error { + err := c.api. + URL("/authorizer/api/v1/icap/cas/%s/crl", id). Download(filename) return err } -// WebProxySessionDownloadHandle get a session id for a web proxy config -func (auth *Client) WebProxySessionDownloadHandle(trustedClientID string) (*DownloadHandle, error) { - sessionID := &DownloadHandle{} +// GetWebProxyConfigSessions get web proxy config session ids. +func (c *Authorizer) GetWebProxyConfigSessions(trustedClientID string) (*SessionIDResponse, error) { + sessionIDs := &SessionIDResponse{} - _, err := auth.api. - URL("/authorizer/api/v1/icap/conf/%s", url.PathEscape(trustedClientID)). - Post(nil, &sessionID) + _, err := c.api. + URL("/authorizer/api/v1/icap/conf/%s", trustedClientID). + Post(nil, &sessionIDs) - return sessionID, err + return sessionIDs, err } -// DownloadWebProxyConfig gets a pre-configured web proxy config -func (auth *Client) DownloadWebProxyConfig(trustedClientID, sessionID, filename string) error { - err := auth.api. - URL("/authorizer/api/v1/icap/conf/%s/%s", url.PathEscape(trustedClientID), url.PathEscape(sessionID)). +// DownloadWebProxyConfig fetch a pre-configured web proxy config as a download object. +func (c *Authorizer) DownloadWebProxyConfig(trustedClientID, sessionID, filename string) error { + err := c.api. + URL("/authorizer/api/v1/icap/conf/%s/%s", trustedClientID, sessionID). Download(filename) return err } -// CertTemplates returns the certificate authentication templates for the service -func (auth *Client) CertTemplates(service string) ([]CertTemplate, error) { - result := templatesResult{} - filters := Params{ - Service: service, +// MARK: Templates +// GetCertTemplates returns the certificate authentication templates. +func (c *Authorizer) GetCertTemplates(opts ...filters.Option) (*response.ResultSet[CertTemplate], error) { + templates := &response.ResultSet[CertTemplate]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/cert/templates"). - Query(&filters). - Get(&result) + Query(params). + Get(&templates) - return result.Items, err + return templates, err } -// SSLTrustAnchor returns the SSL trust anchor (PrivX TLS CA certificate) -func (auth *Client) SSLTrustAnchor() (*TrustAnchor, error) { - anchor := &TrustAnchor{} +// MARK: Trust Anchors +// GetSSLTrustAnchor returns the SSL trust anchor. +func (c *Authorizer) GetSSLTrustAnchor() (*TrustAnchor, error) { + trustAnchor := &TrustAnchor{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/ssl-trust-anchor"). - Get(&anchor) + Get(&trustAnchor) - return anchor, err + return trustAnchor, err } -// ExtenderTrustAnchor returns the extender trust anchor (PrivX TLS CA certificate) -func (auth *Client) ExtenderTrustAnchor() (*TrustAnchor, error) { - anchor := &TrustAnchor{} +// GetExtenderTrustAnchor returns the extender trust anchor. +func (c *Authorizer) GetExtenderTrustAnchor() (*TrustAnchor, error) { + trustAnchor := &TrustAnchor{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/extender-trust-anchor"). - Get(&anchor) + Get(&trustAnchor) - return anchor, err + return trustAnchor, err } // MARK: Access Groups -// AccessGroups lists all access group -func (auth *Client) AccessGroups(offset, limit int, sortkey, sortdir string) ([]AccessGroup, error) { - filters := Params{ - Offset: offset, - Limit: limit, - Sortkey: sortkey, - Sortdir: sortdir, +// GetAccessGroups get all access group. +func (c *Authorizer) GetAccessGroups(opts ...filters.Option) (*response.ResultSet[AccessGroup], error) { + accessGroups := &response.ResultSet[AccessGroup]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := accessGroupResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/accessgroups"). - Query(&filters). - Get(&result) + Query(params). + Get(&accessGroups) - return result.Items, err + return accessGroups, err } -// CreateAccessGroup create a access group -func (auth *Client) CreateAccessGroup(accessGroup *AccessGroup) (string, error) { - var object struct { - ID string `json:"id"` - } +// CreateAccessGroup create access group. +func (c *Authorizer) CreateAccessGroup(accessGroup *AccessGroup) (response.Identifier, error) { + identifier := response.Identifier{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/accessgroups"). - Post(&accessGroup, &object) + Post(&accessGroup, &identifier) - return object.ID, err + return identifier, err } -// SearchAccessGroup search for access groups -func (auth *Client) SearchAccessGroup(offset, limit int, sortkey, sortdir string, search *SearchParams) ([]AccessGroup, error) { - filters := Params{ - Offset: offset, - Limit: limit, - Sortkey: sortkey, - Sortdir: sortdir, +// SearchAccessGroups search for access groups. +func (c *Authorizer) SearchAccessGroups(search *AccessGroupSearch, opts ...filters.Option) (*response.ResultSet[AccessGroup], error) { + accessGroups := &response.ResultSet[AccessGroup]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := accessGroupResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/accessgroups/search"). - Query(&filters). - Post(search, &result) + Query(params). + Post(search, &accessGroups) - return result.Items, err + return accessGroups, err } -// AccessGroup get access group -func (auth *Client) AccessGroup(accessGroupID string) (*AccessGroup, error) { +// GetAccessGroup get access group by id. +func (c *Authorizer) GetAccessGroup(accessGroupID string) (*AccessGroup, error) { accessGroup := &AccessGroup{} - _, err := auth.api. - URL("/authorizer/api/v1/accessgroups/%s", url.PathEscape(accessGroupID)). + _, err := c.api. + URL("/authorizer/api/v1/accessgroups/%s", accessGroupID). Get(&accessGroup) return accessGroup, err } -// UpdateAccessGroup update access group -func (auth *Client) UpdateAccessGroup(accessGroupID string, accessGroup *AccessGroup) error { - _, err := auth.api. - URL("/authorizer/api/v1/accessgroups/%s", url.PathEscape(accessGroupID)). - Put(accessGroup) +// UpdateAccessGroup update access group by id. +func (c *Authorizer) UpdateAccessGroup(accessGroupID string, update *AccessGroup) error { + _, err := c.api. + URL("/authorizer/api/v1/accessgroups/%s", accessGroupID). + Put(update) return err } -// DeleteAccessGroup delete a access group -func (auth *Client) DeleteAccessGroup(accessGroupID string) error { - _, err := auth.api. +// DeleteAccessGroup delete access group by id. +func (c *Authorizer) DeleteAccessGroup(accessGroupID string) error { + _, err := c.api. URL("/authorizer/api/v1/accessgroups/%s", accessGroupID). Delete() return err } -// CreateAccessGroupsIdCas create CA Key to an access group -func (auth *Client) CreateAccessGroupsIdCas(accessGroupID string) (string, error) { - var result string +// RenewAccessGroupCAKey renew access group CA key. +func (c *Authorizer) RenewAccessGroupCAKey(accessGroupID string) (string, error) { + var keyID string - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/accessgroups/%s/cas", accessGroupID). - Post(nil, &result) + Post(nil, &keyID) - return result, err + return keyID, err } -// DeleteAccessGroup delete a CA Key to an access group -func (auth *Client) DeleteAccessGroupsIdCas(accessGroupID string, caID string) error { - _, err := auth.api. +// RevokeAccessGroupCAKey revoke access group CA key. +func (c *Authorizer) RevokeAccessGroupCAKey(accessGroupID string, caID string) error { + _, err := c.api. URL("/authorizer/api/v1/accessgroups/%s/cas/%s", accessGroupID, caID). Delete() @@ -460,124 +525,123 @@ func (auth *Client) DeleteAccessGroupsIdCas(accessGroupID string, caID string) e } // MARK: Certs -// SearchCert search for certificates -func (auth *Client) SearchCert(offset, limit int, sortkey, sortdir string, cert *APICertificateSearch) ([]APICertificate, error) { - filters := Params{ - Offset: offset, - Limit: limit, - Sortkey: sortkey, - Sortdir: sortdir, +// SearchCerts search certificates. +func (c *Authorizer) SearchCerts(search *ApiCertificateSearch, opts ...filters.Option) (*response.ResultSet[ApiCertificate], error) { + certs := &response.ResultSet[ApiCertificate]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := apiCertificateResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/cert/search"). - Query(&filters). - Post(cert, &result) + Query(params). + Post(search, &certs) - return result.Items, err + return certs, err } -// Get all Certificates -func (auth *Client) GetAllCertificates() (apiCertificateResult, error) { - certificates := apiCertificateResult{} +// GetAllCertificates get all certificates. +func (c *Authorizer) GetAllCertificates() (*response.ResultSet[ApiCertificate], error) { + certs := &response.ResultSet[ApiCertificate]{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/cert"). - Get(&certificates) + Get(&certs) - return certificates, err + return certs, err } -// Get Certificate by ID -func (auth *Client) GetCertByID(ID string) (ApiCertificateObject, error) { - cert := ApiCertificateObject{} +// GetCert get certificate by id. +func (c *Authorizer) GetCert(certID string) (*ApiCertificate, error) { + cert := &ApiCertificate{} - _, err := auth.api. - URL("/authorizer/api/v1/cert/%s", url.PathEscape(ID)). + _, err := c.api. + URL("/authorizer/api/v1/cert/%s", certID). Get(&cert) return cert, err } // MARK: Secrets -// AccountSecrets lists all account secrets -func (auth *Client) AccountSecrets(limit int, sortdir string) (AccountSecretsResult, error) { - filters := Params{ - Limit: limit, - Sortdir: sortdir, +// GetAccountSecrets get all account secrets. +func (c *Authorizer) GetAccountSecrets(opts ...filters.Option) (*response.ResultSet[HostAccountSecret], error) { + secrets := &response.ResultSet[HostAccountSecret]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := AccountSecretsResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/secrets"). - Query(&filters). - Get(&result) + Query(params). + Get(&secrets) - return result, err + return secrets, err } -// SearchAccountSecrets search for account secrets -func (auth *Client) SearchAccountSecrets(limit int, sortdir string, search *AccountSecretsSearchRequest) (AccountSecretsResult, error) { - filters := Params{ - Limit: limit, - Sortdir: sortdir, +// SearchAccountSecrets search for account secrets. +func (c *Authorizer) SearchAccountSecrets(search *AccountSecretSearch, opts ...filters.Option) (*response.ResultSet[HostAccountSecret], error) { + secrets := &response.ResultSet[HostAccountSecret]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := AccountSecretsResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/secrets/search"). - Query(&filters). - Post(search, &result) + Query(params). + Post(search, &secrets) - return result, err + return secrets, err } -// CheckoutAccountSecret checkout account secret -func (auth *Client) CheckoutAccountSecret(path string) (CheckoutResult, error) { - checkoutReq := CheckoutRequest{ - Path: path, +// GetSecretCheckouts get secret checkouts. +func (c *Authorizer) GetSecretCheckouts(opts ...filters.Option) (*response.ResultSet[Checkout], error) { + checkouts := &response.ResultSet[Checkout]{} + params := url.Values{} + + for _, opt := range opts { + opt(¶ms) } - result := CheckoutResult{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/secrets/checkouts"). - Post(checkoutReq, &result) + Query(params). + Get(&checkouts) - return result, err + return checkouts, err } -// Checkouts lists secret checkouts -func (auth *Client) Checkouts(limit int, sortdir string) (CheckoutResult, error) { - filters := Params{ - Limit: limit, - Sortdir: sortdir, - } - result := CheckoutResult{} +// CheckoutAccountSecret checkout account secret. +func (c *Authorizer) CheckoutAccountSecret(checkout CheckoutRequest) (*response.ResultSet[Checkout], error) { + checkoutResp := &response.ResultSet[Checkout]{} - _, err := auth.api. + _, err := c.api. URL("/authorizer/api/v1/secrets/checkouts"). - Query(&filters). - Get(&result) + Post(checkout, &checkoutResp) - return result, err + return checkoutResp, err } -// Checkout get checkout by id -func (auth *Client) Checkout(checkoutId string) (*Checkout, error) { +// GetSecretCheckout get secret checkout by id. +func (c *Authorizer) GetSecretCheckout(checkoutID string) (*Checkout, error) { checkout := &Checkout{} - _, err := auth.api. - URL("/authorizer/api/v1/secrets/checkouts/%s", url.PathEscape(checkoutId)). + _, err := c.api. + URL("/authorizer/api/v1/secrets/checkouts/%s", checkoutID). Get(&checkout) return checkout, err } -// ReleaseCheckout release secret checkout -func (auth *Client) ReleaseCheckout(checkoutId string) error { - _, err := auth.api. - URL("/authorizer/api/v1/secrets/checkouts/%s/release", url.PathEscape(checkoutId)). +// ReleaseSecretCheckout release secret checkout. +func (c *Authorizer) ReleaseSecretCheckout(checkoutID string) error { + _, err := c.api. + URL("/authorizer/api/v1/secrets/checkouts/%s/release", checkoutID). Post(nil) return err diff --git a/api/authorizer/model.go b/api/authorizer/model.go index 26cf84e..99e8d1b 100644 --- a/api/authorizer/model.go +++ b/api/authorizer/model.go @@ -6,46 +6,55 @@ package authorizer -import "time" - -// Params query params definition -type Params struct { - ResponseType string `json:"response_type,omitempty"` - ClientID string `json:"client_id,omitempty"` - State string `json:"state,omitempty"` - RedirectURI string `json:"redirect_uri,omitempty"` - UserAgent string `json:"user_agent,omitempty"` - OidcID string `json:"oidc_id,omitempty"` - AccessGroupID string `json:"access_group_id,omitempty"` - KeyID string `json:"key_id,omitempty"` - Filter string `json:"filter,omitempty"` - Service string `json:"service,omitempty"` - Sortkey string `json:"sortkey,omitempty"` - Sortdir string `json:"sortdir,omitempty"` - Offset int `json:"offset,omitempty"` - Limit int `json:"limit,omitempty"` +import ( + "time" + + "github.com/SSHcom/privx-sdk-go/api/secretsmanager" +) + +// CAparams ca query parameter definition. +type CAParams struct { + AccessGroupID string `url:"access_group_id"` } -// SearchParams search params definition -type SearchParams struct { - Keywords string `json:"keywords,omitempty"` +// PrincipalParams principal query parameter definition. +type PrincipalParams struct { + KeyID string `url:"key_id"` } -// APICertificate api certificate definition -type APICertificate struct { - ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - Revoked string `json:"revoked,omitempty"` - RevocationReason string `json:"revocation_reason,omitempty"` - Cert string `json:"cert,omitempty"` - Chain string `json:"chain,omitempty"` +// CertTemplateParams certificate template query parameter definition. +type CertTemplateParams struct { + Service string `url:"service"` } -// APICertificateSearch api certificate search definition -type APICertificateSearch struct { +// ApiCertificate api certificate definition. +type ApiCertificate struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` + Serial string `json:"serial"` + OwnerID string `json:"owner_id,omitempty"` + Revoked string `json:"revoked,omitempty"` + RevocationReason string `json:"revocation_reason,omitempty"` + Cert string `json:"cert,omitempty"` + Chain string `json:"chain,omitempty"` + Issuer string `json:"issuer,omitempty"` + Subject string `json:"subject,omitempty"` + NotBefore string `json:"not_before,omitempty"` + NotAfter string `json:"not_after,omitempty"` + KeyUsage string `json:"key_usage,omitempty"` + BasicConstraints string `json:"basic_constraints,omitempty"` + Extensions string `json:"extensions,omitempty"` + FingerPrintSHA1 string `json:"fingerprint_sha1,omitempty"` + FingerPrintSHA256 string `json:"fingerprint_sha256,omitempty"` + SubjectKeyID string `json:"subject_key_id,omitempty"` + AuthorityKeyID string `json:"authority_key_id,omitempty"` + Status string `json:"status"` +} + +// ApiCertificateSearch api certificate search definition. +type ApiCertificateSearch struct { + Type string `json:"type"` ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` KeyID string `json:"key_id,omitempty"` OwnerID string `json:"owner_id,omitempty"` Subject string `json:"subject,omitempty"` @@ -75,30 +84,35 @@ type CertTemplate struct { Extensions []string `json:"extensions,omitempty"` } -// DownloadHandle download handle definition -type DownloadHandle struct { +// SessionIDResponse session id response definition. +type SessionIDResponse struct { SessionID string `json:"session_id"` } -// Signature signature definition +// Signature principal key signature response definition. type Signature struct { - Signature string `json:"signature"` + Signature string `json:"signature"` + ResponseCode int `json:"response_code,omitempty"` + Message string `json:"message,omitempty"` } -// Credential end user authentication credentials definition -type Credential struct { - Type string `json:"type"` - Data string `json:"data"` +// PrincipalKeySign principal key sign request definition. +type PrincipalKeySign struct { + ID string `json:"id"` + GroupID string `json:"group_id"` + PublicKey string `json:"publicKey"` + Type string `json:"type"` + Data string `json:"data"` } -// PrincipalKeyImportRequest principal key import definition -type PrincipalKeyImportRequest struct { +// PrincipalKeyImport principal key import request definition. +type PrincipalKeyImport struct { Algorithm string `json:"algorithm"` Data string `json:"data"` } -// AuthorizationRequest end user authorization request definition -type AuthorizationRequest struct { +// ApiIdentities end user authorization request definition. +type ApiIdentities struct { PublicKey string `json:"public_key,omitempty"` HostID string `json:"host_id,omitempty"` Hostname string `json:"hostname,omitempty"` @@ -107,100 +121,135 @@ type AuthorizationRequest struct { RoleID string `json:"role_id,omitempty"` } -// Principal principal definition -type Principal struct { - ID string `json:"id"` - GroupID string `json:"group_id,omitempty"` - Type string `json:"type,omitempty"` - Comment string `json:"comment,omitempty"` - PublicKey string `json:"public_key,omitempty"` - PublicKeyString string `json:"public_key_string,omitempty"` - Size int `json:"size,omitempty"` -} - -type ApiSshCertificate struct { - Type string `json:"type"` - Data string `json:"data"` - DataString string `json:"data_string"` - Chain []string `json:"chain"` -} +// ApiIdentitiesResponse api identities response definition. type ApiIdentitiesResponse struct { Certificates []ApiSshCertificate `json:"certificates"` - PrincipalKeys []Principal `json:"principal_keys"` + PrincipalKeys []ApiSshKey `json:"principal_keys"` Passphrase string `json:"passphrase,omitempty"` ResponseCode int `json:"response_code"` Message string `json:"message"` } -// CA is root certificate representation -type CA struct { - ID string `json:"id"` - GroupID string `json:"group_id"` - Type string `json:"type"` - Size int `json:"size"` - PublicKey string `json:"public_key"` - X509 string `json:"x509_certificate"` +// ApiSshCertificate api ssh certificate definition. +type ApiSshCertificate struct { + Type string `json:"type"` + Data string `json:"data"` + DataString string `json:"data_string"` + Chain []string `json:"chain"` } -// AccessGroup access group definition -type AccessGroup struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Comment string `json:"comment,omitempty"` - CAID string `json:"ca_id,omitempty"` - Author string `json:"author,omitempty"` - Created string `json:"created,omitempty"` - Updated string `json:"updated,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Default bool `json:"default,omitempty"` -} -type ApiCertificateSearchResponse struct { - Count int `json:"count"` - Items []ApiCertificateObject `json:"items"` +// ApiSshKey api ssh key definition. +type ApiSshKey struct { + ID string `json:"id"` + GroupID string `json:"group_id,omitempty"` + Type string `json:"type,omitempty"` + Comment string `json:"comment,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PublicKeyString string `json:"public_key_string,omitempty"` + Size int `json:"size,omitempty"` } -type ApiCertificateObject struct { - Type string `json:"type"` +// CA root certificate definition. +type CA struct { ID string `json:"id"` - Serial string `json:"serial"` - OwnerID string `json:"owner_id,omitempty"` - Revoked string `json:"revoked,omitempty"` - RevocationReason string `json:"revocation_reason,omitempty"` - Cert string `json:"cert"` - Chain string `json:"chain"` - Issuer string `json:"issuer,omitempty"` + GroupID string `json:"group_id"` + AccessGroupID string `json:"access_group_id,omitempty"` + Type string `json:"type"` + Size int `json:"size"` + PublicKey string `json:"public_key"` + Comment string `json:"comment,omitempty"` + PublicKeyString string `json:"public_key_string"` + X509Certificate string `json:"x509_certificate,omitempty"` Subject string `json:"subject,omitempty"` + Issuer string `json:"issuer,omitempty"` + SerialNumber string `json:"serial,omitempty"` NotBefore string `json:"not_before,omitempty"` NotAfter string `json:"not_after,omitempty"` - KeyUsage string `json:"key_usage,omitempty"` - BasicConstraints string `json:"basic_constraints,omitempty"` - Extensions string `json:"extensions,omitempty"` FingerPrintSHA1 string `json:"fingerprint_sha1,omitempty"` FingerPrintSHA256 string `json:"fingerprint_sha256,omitempty"` - SubjectKeyID string `json:"subject_key_id,omitempty"` - AuthorityKeyID string `json:"authority_key_id,omitempty"` - ExpiryStatus string `json:"expiry_status,omitempty"` } -type AccountSecrets struct { - Path string `json:"path"` - Type string `json:"type"` - Username string `json:"username"` - Email string `json:"email,omitempty"` - FullName string `json:"full_name,omitempty"` - TargetDomain TargetDomainHandle `json:"target_domain,omitempty"` - Host HostPrincipals `json:"host,omitempty"` - Created string `json:"created,omitempty"` - Updated string `json:"updated,omitempty"` +// Principal principal definition. +type Principal struct { + Type string `json:"type"` + ID string `json:"id"` + GroupID string `json:"group_id"` + Comment string `json:"comment,omitempty"` + PublicKey string `json:"public_key"` + PublicKeyString string `json:"public_key_string"` + Size int `json:"size"` +} + +// CertificateEnroll certificate enroll request definition. +type CertificateEnroll struct { + CAID string `json:"ca_id,omitempty"` + CSR string `json:"csr"` + Owner string `json:"owner"` } +// CertificateEnrollResponse certificate enroll response definition. +type CertificateEnrollResponse struct { + ID string `json:"id"` + Cert string `json:"cert"` + CA string `json:"ca"` +} + +// CertificateRevocation certificate revocation request definition. +type CertificateRevocation struct { + Reason string `json:"reason,omitempty"` + Owner string `json:"owner,omitempty"` + Cert string `json:"cert,omitempty"` +} + +// CertificateRevocationResponse certificate revocation response definition. +type CertificateRevocationResponse struct { + IDS []string `json:"ids"` +} + +// AccessGroup access group definition. +type AccessGroup struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Comment string `json:"comment,omitempty"` + HostCertificateTrustAnchors string `json:"host_certificate_trust_anchors"` + WinRMHostCertificateTrustAnchors string `json:"winrm_host_certificate_trust_anchors"` + DBHostCertificateTrustAnchors string `json:"db_host_certificate_trust_anchors"` + CAID string `json:"ca_id,omitempty"` + PrimaryCAID string `json:"primary_ca_id"` + Author string `json:"author,omitempty"` + Created string `json:"created,omitempty"` + Updated string `json:"updated,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Default bool `json:"default,omitempty"` +} + +// AccessGroupSearch access group request search body definition. +type AccessGroupSearch struct { + Keywords string `json:"keywords,omitempty"` +} + +// HostAccountSecret host account secret definition. +type HostAccountSecret struct { + Path string `json:"path"` + Type string `json:"type"` + Username string `json:"username"` + Email string `json:"email,omitempty"` + FullName string `json:"full_name,omitempty"` + TargetDomain TargetDomainHandle `json:"target_domain,omitempty"` + Host HostPrincipalsHandle `json:"host,omitempty"` + Created string `json:"created,omitempty"` + Updated string `json:"updated,omitempty"` +} + +// TargetDomainHandle target domain handle definition. type TargetDomainHandle struct { ID string `json:"id"` Name string `json:"name,omitempty"` Deleted bool `json:"deleted,omitempty"` } -type HostPrincipals struct { +// HostPrincipalsHandle host principals handle definition. +type HostPrincipalsHandle struct { ID string `json:"id"` Addresses []string `json:"addresses"` CommonName string `json:"common_name,omitempty"` @@ -208,70 +257,43 @@ type HostPrincipals struct { InstanceID string `json:"instance_id,omitempty"` } -type AccountSecretsSearchRequest struct { +// AccountSecretSearch account secret search request definition. +type AccountSecretSearch struct { Keywords string `json:"keywords"` HostID string `json:"host_id,omitempty"` Username string `json:"username,omitempty"` } +// Checkout checkout definition. type Checkout struct { - ID string `json:"id"` - Path string `json:"path"` - Type string `json:"type"` - Expires string `json:"expires"` - Created string `json:"created"` - ExplicitCheckout bool `json:"explicit_checkout"` - Secrets []Secrets `json:"secrets"` - Username string `json:"username"` - Email string `json:"email,omitempty"` - FullName string `json:"full_name,omitempty"` - Host HostPrincipals `json:"host,omitempty"` - TargetDomain TargetDomain `json:"target_domain,omitempty"` - ManagedAccountID string `json:"managed_account_id,omitempty"` - UserID string `json:"user_id"` -} - + ID string `json:"id"` + Path string `json:"path"` + Type string `json:"type"` + Expires string `json:"expires"` + Created string `json:"created"` + ExplicitCheckout bool `json:"explicit_checkout"` + Secrets []Secrets `json:"secrets"` + Username string `json:"username"` + Email string `json:"email,omitempty"` + FullName string `json:"full_name,omitempty"` + Host HostPrincipalsHandle `json:"host,omitempty"` + TargetDomain secretsmanager.TargetDomainHandle `json:"target_domain,omitempty"` + ManagedAccountID string `json:"managed_account_id,omitempty"` + UserID string `json:"user_id"` +} + +// CheckoutRequest checkout request definition. type CheckoutRequest struct { Path string `json:"path"` } +// Secrets secrets definition. type Secrets struct { Version int `json:"version"` Secret string `json:"secret"` Created time.Time `json:"created"` } -type TargetDomain struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Deleted bool `json:"deleted,omitempty"` -} - -type templatesResult struct { - Count int `json:"count"` - Items []CertTemplate `json:"items"` -} - -type accessGroupResult struct { - Count int `json:"count"` - Items []AccessGroup `json:"items"` -} - -type apiCertificateResult struct { - Count int `json:"count"` - Items []APICertificate `json:"items"` -} - -type AccountSecretsResult struct { - Count int `json:"count"` - Items []AccountSecrets `json:"items"` -} - -type CheckoutResult struct { - Count int `json:"count"` - Items []Checkout `json:"items"` -} - // ComponentCaConfig component ca config response definition. type ComponentCaConfig struct { Name string `json:"name"` diff --git a/api/filters/filters.go b/api/filters/filters.go index df3741f..860587b 100644 --- a/api/filters/filters.go +++ b/api/filters/filters.go @@ -1,68 +1,103 @@ package filters +import ( + "fmt" + "net/url" + "strconv" +) + const ( ASC = "ASC" // ASC defines ascending sort direction. DESC = "DESC" // DESC defines descending sort direction. ) -// Params defines optional query parameters. -type Params struct { - Offset int `json:"offset,omitempty"` - Limit int `json:"limit,omitempty"` - SortKey string `json:"sortkey,omitempty"` - SortDir string `json:"sortdir,omitempty"` -} - -// Default constructor for default filters. -func Default() Params { - return Params{ - Offset: 0, - Limit: 50, - SortDir: "ASC", - } -} - -// Option function type modifying Params. -type Option func(p *Params) +// Option function type setting url values. +type Option func(*url.Values) -// Offset sets the offset for Params. +// Offset sets the offset for url values. func Offset(offset int) Option { - return func(p *Params) { p.Offset = offset } + return func(v *url.Values) { v.Set("offset", strconv.Itoa(offset)) } } -// Limit sets the limit for Params. +// Limit sets the limit for url values. func Limit(limit int) Option { - return func(p *Params) { p.Limit = limit } + return func(v *url.Values) { v.Set("limit", strconv.Itoa(limit)) } } -// Paging sets both the offset and limit for Params. +// Paging sets both the offset and limit for url values. func Paging(offset, limit int) Option { - return func(p *Params) { - p.Offset = offset - p.Limit = limit + return func(v *url.Values) { + v.Set("offset", strconv.Itoa(offset)) + v.Set("limit", strconv.Itoa(limit)) } } -// Sort sets the sort key and direction for Params. +// Sort sets the sort key and direction for url values. func Sort(key, dir string) Option { - return func(p *Params) { - p.SortKey = key - p.SortDir = dir + return func(v *url.Values) { + v.Set("sortkey", key) + v.Set("sortdir", dir) } } -// SortAsc sets the sort key with ascending order for Params. +// SortAsc sets the sort key with ascending order for url values. func SortAsc(key string) Option { - return func(p *Params) { - p.SortKey = key - p.SortDir = ASC + return func(v *url.Values) { + v.Set("sortkey", key) + v.Set("sortdir", ASC) } } -// SortDesc sets the sort key with descending order for Params. +// SortDesc sets the sort key with descending order for url values. func SortDesc(key string) Option { - return func(p *Params) { - p.SortKey = key - p.SortDir = DESC + return func(v *url.Values) { + v.Set("sortkey", key) + v.Set("sortdir", DESC) + } +} + +// Filter sets the filter for url values. +func Filter(filter string) Option { + return func(v *url.Values) { v.Set("filter", filter) } +} + +// FuzzyCount sets the fuzzy count for url values. +func FuzzyCount(fuzzycount bool) Option { + return func(v *url.Values) { v.Set("fuzzycount", strconv.FormatBool(fuzzycount)) } +} + +// SetCustomParams set custom key-value parameter pairs freely. +func SetCustomParams(key, value string) Option { + return func(v *url.Values) { v.Set(key, value) } +} + +// SetStructParams convert struct to URL values and return Option from those values. +func SetStructParams(p interface{}) Option { + return func(v *url.Values) { + values, err := Values(p) + if err != nil { + fmt.Printf("failed converting struct into URL values: %v", err) + return + } + + options := urlValuesToOptions(values) + for _, opt := range options { + opt(v) + } + } +} + +// urlValuesToOptions convert URL values into []Option. +func urlValuesToOptions(values url.Values) []Option { + opts := make([]Option, 0) + for key, vals := range values { + for _, val := range vals { + opts = append(opts, func(k, v string) Option { + return func(params *url.Values) { + params.Set(k, v) + } + }(key, val)) + } } + return opts } diff --git a/api/filters/urlquery.go b/api/filters/urlquery.go new file mode 100644 index 0000000..d2bf241 --- /dev/null +++ b/api/filters/urlquery.go @@ -0,0 +1,268 @@ +package filters + +import ( + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var ( + encoderType = reflect.TypeOf(new(Encoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) +) + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} + +// Values convert struct into URL values by reflecting over its fields and tags. +func Values(s interface{}) (url.Values, error) { + v := reflect.ValueOf(s) + values := make(url.Values) + + // Dereference pointers, return empty map if nil. + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return values, nil + } + v = v.Elem() + } + + if s == nil { + return values, nil + } + + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected a struct, got: %T", s) + } + + err := reflectValue(values, v, "") + + return values, err +} + +// reflectValue recursively populates URL values by reflecting over +// fields of a struct, handling embedded structs, slices, arrays, and custom types. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + embedded := []reflect.Value{} + t := val.Type() + + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + // skip unexported fields + if sf.PkgPath != "" && !sf.Anonymous { + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + // skip ignored fields + if tag == "-" { + continue + } + + // split url tag into its base name and options + name, opts := parseTag(tag) + if name == "" { + // check for embedded struct fields + if sf.Anonymous { + // ensure value is the actual struct and not a pointer to it + v := reflect.Indirect(sv) + if v.IsValid() && v.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, v) + continue + } + } + + name = sf.Name + } + + // skip if omitempty is set and passed value is empty + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + // check if value implements Encoder interface for custom encoding + if sv.Type().Implements(encoderType) { + // if sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(encoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + // recursively dereference pointers. break on nil pointers + for sv.Kind() == reflect.Ptr { + // stop if nil to prevent further dereferencing + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + switch sv.Kind() { + case reflect.Slice, reflect.Array: + if sv.Len() == 0 { + // skip if slice or array is empty + continue + } + handleSliceOrArray(values, name, sv, opts, sf) + continue + case reflect.Struct: + // handle recursively nested structs + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + values.Add(name, valueString(sv, opts, sf)) + } + + // Handle embedded structs + for _, f := range embedded { + if err := reflectValue(values, f, scope); err != nil { + return err + } + } + + return nil +} + +// handleSliceOrArray processes slices/arrays in a struct field, either joining them with a delimiter +// or adding each element separately to the URL values map. +func handleSliceOrArray(values url.Values, name string, sv reflect.Value, opts tagOptions, sf reflect.StructField) { + var delimiter string + switch { + case opts.Contains("comma"): + delimiter = "," + case opts.Contains("space"): + delimiter = " " + case opts.Contains("semicolon"): + delimiter = ";" + case opts.Contains("brackets"): + name += "[]" + default: + delimiter = sf.Tag.Get("del") + } + + if delimiter != "" { + var b strings.Builder + for i := 0; i < sv.Len(); i++ { + if i > 0 { + b.WriteString(delimiter) + } + b.WriteString(valueString(sv.Index(i), opts, sf)) + } + values.Add(name, b.String()) + } else { + for i := 0; i < sv.Len(); i++ { + k := name + if opts.Contains("numbered") { + k = fmt.Sprintf("%s%d", name, i) + } + values.Add(k, valueString(sv.Index(i), opts, sf)) + } + } +} + +// valueString returns the string representation of a value based on its type and tag options. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + // get underlying value, return empty string for invalid values + v = reflect.Indirect(v) + if !v.IsValid() { + return "" + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + return strconv.Itoa(convertBool(v.Bool())) + } + + if v.Type() == timeType { + return formatTimeValue(v.Interface().(time.Time), opts, sf) + } + + return fmt.Sprint(v.Interface()) +} + +// parseTag split struct field's "url" tag into its name and optional comma separated options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// convertBool convert a boolean value to 1 (true) or 0 (false). +func convertBool(b bool) int { + if b { + return 1 + } + return 0 +} + +// formatTimeValue formats a time.Time value based on provided tag options or struct field tag. +// Defaults to RFC3339 format if no specific option is provided. +func formatTimeValue(t time.Time, opts tagOptions, sf reflect.StructField) string { + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + if opts.Contains("unixmilli") { + return strconv.FormatInt(t.UnixNano()/1e6, 10) + } + if opts.Contains("unixnano") { + return strconv.FormatInt(t.UnixNano(), 10) + } + if layout := sf.Tag.Get("layout"); layout != "" { + return t.Format(layout) + } + return t.Format(time.RFC3339) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if z, ok := v.Interface().(interface{ IsZero() bool }); ok { + return z.IsZero() + } + + return false +} diff --git a/api/filters/urlquery_test.go b/api/filters/urlquery_test.go new file mode 100644 index 0000000..73fa217 --- /dev/null +++ b/api/filters/urlquery_test.go @@ -0,0 +1,178 @@ +package filters + +import ( + "net/url" + "reflect" + "testing" + "time" +) + +func TestValues(t *testing.T) { + type args struct { + s interface{} + } + tests := []struct { + name string + args args + want url.Values + wantErr bool + }{ + { + name: "Test Struct with omitempty tags no values", + args: args{ + struct { + StringTest string `url:"string_test,omitempty"` + TimeTest string `url:"time_test,omitempty"` + IntTest int `url:"int_test,omitempty"` + BoolTest bool `url:"bool_test,omitempty"` + }{}, + }, + want: url.Values{}, + wantErr: false, + }, + { + name: "Test Struct with omitempty with values", + args: args{ + struct { + StringTest string `url:"string_test,omitempty"` + TimeTest string `url:"time_test,omitempty"` + IntTest int `url:"int_test,omitempty"` + BoolTest bool `url:"bool_test,omitempty"` + }{ + StringTest: "test", + TimeTest: time.Now().Format(time.RFC3339), + BoolTest: true, + IntTest: 42, + }, + }, + want: url.Values{ + "bool_test": {"true"}, + "string_test": {"test"}, + "int_test": {"42"}, + "time_test": {time.Now().Format(time.RFC3339)}, + }, + }, + { + name: "Test Struct with slice and custom tag options", + args: args{ + struct { + StringList []string `url:"string_list,comma"` + }{ + StringList: []string{"item1", "item2", "item3"}, + }, + }, + want: url.Values{ + "string_list": {"item1,item2,item3"}, + }, + wantErr: false, + }, + { + name: "Test Struct with empty slice and omitempty", + args: args{ + struct { + StringList []string `url:"string_list,omitempty"` + }{}, + }, + want: url.Values{}, + wantErr: false, + }, + { + name: "Test Struct with pointer fields", + args: args{ + struct { + IntPtr *int `url:"int_ptr,omitempty"` + }{ + IntPtr: func() *int { v := 100; return &v }(), + }, + }, + want: url.Values{ + "int_ptr": {"100"}, + }, + wantErr: false, + }, + { + name: "Test Struct with nil pointer and omitempty", + args: args{ + struct { + IntPtr *int `url:"int_ptr,omitempty"` + }{}, + }, + want: url.Values{}, + wantErr: false, + }, + { + name: "Invalid type (non-struct input)", + args: args{ + "this_is_not_a_struct", + }, + want: nil, + wantErr: true, + }, + { + name: "Empty Test Struct without omitempty", + args: args{ + struct { + StringTest string `url:"string_test"` + TimeTest string `url:"time_test"` + IntTest int `url:"int_test"` + BoolTest bool `url:"bool_test"` + }{}, + }, + want: url.Values{ + "string_test": {""}, + "time_test": {""}, + "int_test": {"0"}, + "bool_test": {"false"}, + }, + }, + { + name: "Test Struct with embedded struct", + args: args{ + struct { + EmbeddedStruct struct { + SubStringTest string `url:"sub_string_test"` + SubIntTest int `url:"sub_int_test"` + } + StringTest string `url:"string_test"` + TimeTest string `url:"time_test"` + IntTest int `url:"int_test"` + BoolTest bool `url:"bool_test"` + }{ + EmbeddedStruct: struct { + SubStringTest string `url:"sub_string_test"` + SubIntTest int `url:"sub_int_test"` + }{ + SubStringTest: "embedded", + SubIntTest: 101, + }, + StringTest: "parent", + TimeTest: time.Now().Format(time.RFC3339), + BoolTest: true, + IntTest: 42, + }, + }, + want: url.Values{ + "sub_string_test": {"embedded"}, + "sub_int_test": {"101"}, + "bool_test": {"true"}, + "string_test": {"parent"}, + "int_test": {"42"}, + "time_test": {time.Now().Format(time.RFC3339)}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Values(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("Values() error = %v, \nwantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Values() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/api/response/response.go b/api/response/response.go index d1b473d..71c0d20 100644 --- a/api/response/response.go +++ b/api/response/response.go @@ -29,5 +29,5 @@ type ResultSet[T any] struct { // Identifier id response definition. type Identifier struct { - Id string `json:"id"` + ID string `json:"id"` } diff --git a/restapi/client.go b/restapi/client.go index f34827d..8c52077 100644 --- a/restapi/client.go +++ b/restapi/client.go @@ -141,6 +141,16 @@ func (curl *tCURL) Query(data interface{}) CURL { } func (curl *tCURL) encodeURL(query interface{}) (url.Values, error) { + // If interface is of type url.Values, return it. Values have + // processed already in the new filters package. + urlValues, ok := query.(url.Values) + if ok { + return urlValues, nil + } + + // TODO: remove the json marshal and unmarshal operation. + // Those operations still remain inside encodeURL as some packages + // still rely on it e.g. "oauth" package. bin, err := json.Marshal(query) if err != nil { return nil, err