From 4d896321d707c1afc96f47caaacd183b30a57585 Mon Sep 17 00:00:00 2001 From: John ODonnell Date: Wed, 25 Jan 2023 13:34:42 -0500 Subject: [PATCH] Account specifier now optional on resource IDs When an account is not specified, the configured account is used. --- CHANGELOG.md | 5 ++++ conjurapi/client.go | 51 ++++++++++++++++++++++---------------- conjurapi/resource_test.go | 8 ++++++ 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74604fa..260f1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Remove all usage of Conjur v4 [cyberark/conjur-api-go#139](https://github.com/cyberark/conjur-api-go/pull/139) +### Changed +- Resource IDs can now be partially-qualified, adhering to the form + [:]:. + [cyberark/conjur-api-go#153](https://github.com/cyberark/conjur-api-go/pull/153) + ## [0.10.2] - 2022-11-14 ### Fixed diff --git a/conjurapi/client.go b/conjurapi/client.go index 756d6aa..18921d4 100644 --- a/conjurapi/client.go +++ b/conjurapi/client.go @@ -306,13 +306,10 @@ func (c *Client) OidcAuthenticateRequest(code, nonce, code_verifier string) (*ht } func (c *Client) RotateAPIKeyRequest(roleID string) (*http.Request, error) { - account, _, _, err := parseID(roleID) + _, _, _, err := c.parseID(roleID) if err != nil { return nil, err } - if account != c.config.Account { - return nil, fmt.Errorf("Account of '%s' must match the configured account '%s'", roleID, c.config.Account) - } rotateURL := makeRouterURL(c.authnURL(), "api_key").withFormattedQuery("role=%s", roleID).String() @@ -345,7 +342,7 @@ func (c *Client) ChangeUserPasswordRequest(username string, password string, new // CheckPermissionRequest crafts an HTTP request to Conjur's /resource endpoint // to check if the authenticated user has the given privilege on the given resourceID. func (c *Client) CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) + account, kind, id, err := c.parseID(resourceID) if err != nil { return nil, err } @@ -364,12 +361,12 @@ func (c *Client) CheckPermissionRequest(resourceID, privilege string) (*http.Req // CheckPermissionForRoleRequest crafts an HTTP request to Conjur's /resource endpoint // to check if a given role has the given privilege on the given resourceID. func (c *Client) CheckPermissionForRoleRequest(resourceID, roleID, privilege string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) + account, kind, id, err := c.parseID(resourceID) if err != nil { return nil, err } - roleAccount, roleKind, roleIdentifier, err := parseID(roleID) + roleAccount, roleKind, roleIdentifier, err := c.parseID(roleID) if err != nil { return nil, err } @@ -387,7 +384,7 @@ func (c *Client) CheckPermissionForRoleRequest(resourceID, roleID, privilege str } func (c *Client) ResourceRequest(resourceID string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) + account, kind, id, err := c.parseID(resourceID) if err != nil { return nil, err } @@ -431,7 +428,7 @@ func (c *Client) ResourcesRequest(filter *ResourceFilter) (*http.Request, error) } func (c *Client) PermittedRolesRequest(resourceID string, privilege string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) + account, kind, id, err := c.parseID(resourceID) if err != nil { return nil, err } @@ -445,7 +442,7 @@ func (c *Client) PermittedRolesRequest(resourceID string, privilege string) (*ht } func (c *Client) RoleRequest(roleID string) (*http.Request, error) { - account, kind, id, err := parseID(roleID) + account, kind, id, err := c.parseID(roleID) if err != nil { return nil, err } @@ -459,7 +456,7 @@ func (c *Client) RoleRequest(roleID string) (*http.Request, error) { } func (c *Client) RoleMembersRequest(roleID string) (*http.Request, error) { - account, kind, id, err := parseID(roleID) + account, kind, id, err := c.parseID(roleID) if err != nil { return nil, err } @@ -473,7 +470,7 @@ func (c *Client) RoleMembersRequest(roleID string) (*http.Request, error) { } func (c *Client) RoleMembershipsRequest(roleID string) (*http.Request, error) { - account, kind, id, err := parseID(roleID) + account, kind, id, err := c.parseID(roleID) if err != nil { return nil, err } @@ -489,7 +486,7 @@ func (c *Client) RoleMembershipsRequest(roleID string) (*http.Request, error) { func (c *Client) LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) { fullPolicyID := makeFullId(c.config.Account, "policy", policyID) - account, kind, id, err := parseID(fullPolicyID) + account, kind, id, err := c.parseID(fullPolicyID) if err != nil { return nil, err } @@ -650,7 +647,7 @@ func (c *Client) createHostURL() string { } func (c *Client) variableURL(variableID string) (string, error) { - account, kind, id, err := parseID(variableID) + account, kind, id, err := c.parseID(variableID) if err != nil { return "", err } @@ -658,7 +655,7 @@ func (c *Client) variableURL(variableID string) (string, error) { } func (c *Client) variableWithVersionURL(variableID string, version int) (string, error) { - account, kind, id, err := parseID(variableID) + account, kind, id, err := c.parseID(variableID) if err != nil { return "", err } @@ -717,13 +714,25 @@ func makeFullId(account, kind, id string) string { return strings.Join(tokens, ":") } -func parseID(fullID string) (account, kind, id string, err error) { - tokens := strings.SplitN(fullID, ":", 3) - if len(tokens) != 3 { - err = fmt.Errorf("Id '%s' must be fully qualified", fullID) - return +// parseID accepts as argument a resource ID and returns its components - account, +// resource kind, and identifier. The provided ID can either be fully- or +// partially-qualified. If the ID is only partially-qualified, the configured +// account will be returned. +// +// Examples: +// c.parseID("dev:user:alice") => "dev", "user", "alice", nil +// c.parseID("user:alice") => "dev", "user", "alice", nil +// c.parseID("prod:user:alice") => "prod", "user", "alice", nil +// c.parseID("malformed") => "", "", "". error +func (c *Client) parseID(id string) (account, kind, identifier string, err error) { + tokens := strings.SplitN(id, ":", 3) + if len(tokens) == 3 { + return tokens[0], tokens[1], tokens[2], nil + } else if len(tokens) == 2 { + return c.config.Account, tokens[0], tokens[1], nil + } else { + return "", "", "", fmt.Errorf("Malformed ID '%s': must be fully- or partially-qualified, of form [:]:", id) } - return tokens[0], tokens[1], tokens[2], nil } func NewClient(config Config) (*Client, error) { diff --git a/conjurapi/resource_test.go b/conjurapi/resource_test.go index c646917..08dbf45 100644 --- a/conjurapi/resource_test.go +++ b/conjurapi/resource_test.go @@ -54,6 +54,10 @@ func TestClient_CheckPermission(t *testing.T) { "Check a permission on a non-existent resource", checkAndAssert(conjur, assertFailure, "cucumber:variable:foobar"), ) + t.Run( + "Check a permission on account-less resource", + checkAndAssert(conjur, assertSuccess, "variable:db-password"), + ) } func TestClient_CheckPermissionForRole(t *testing.T) { @@ -76,6 +80,10 @@ func TestClient_CheckPermissionForRole(t *testing.T) { "Check a permission with empty role", checkAndAssert(conjur, assertError, "cucumber:variable:db-password", ""), ) + t.Run( + "Check a permission for account-less role", + checkAndAssert(conjur, assertSuccess, "variable:db-password", "user:alice"), + ) } func TestClient_ResourceExists(t *testing.T) {