From ad0ef0aae9e0e018d49c9d228fed8a70e68f754e Mon Sep 17 00:00:00 2001 From: Sjur Date: Wed, 8 Jan 2025 09:23:10 +0100 Subject: [PATCH] feat: fetch user credentials in sdk (#78) Co-authored-by: Erik Godding Boye Co-authored-by: Amund Tenstad --- internal/sdk/cloudian/sdk.go | 43 ++++++++++++++++++++++++++++ internal/sdk/cloudian/secret.go | 7 +++++ internal/sdk/cloudian/secret_test.go | 24 ++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 internal/sdk/cloudian/secret.go create mode 100644 internal/sdk/cloudian/secret_test.go diff --git a/internal/sdk/cloudian/sdk.go b/internal/sdk/cloudian/sdk.go index e2fc999..80526f7 100644 --- a/internal/sdk/cloudian/sdk.go +++ b/internal/sdk/cloudian/sdk.go @@ -108,6 +108,12 @@ func toInternalUser(u User) userInternal { } } +// SecurityInfo is the Cloudian API's term for secure credentials +type SecurityInfo struct { + AccessKey Secret `json:"accessKey"` + SecretKey Secret `json:"secretKey"` +} + var ErrNotFound = errors.New("not found") // WithInsecureTLSVerify skips the TLS validation of the server certificate when `insecure` is true. @@ -230,6 +236,43 @@ func (client Client) CreateUser(ctx context.Context, user User) error { return resp.Body.Close() } +// GetUserCredentials fetches all the credentials of a user. +func (client Client) GetUserCredentials(ctx context.Context, user User) ([]SecurityInfo, error) { + url := client.baseURL + "/user/credentials/list?userId=" + user.UserID + "&groupId=" + user.GroupID + + req, err := client.newRequest(ctx, url, http.MethodGet, nil) + if err != nil { + return nil, fmt.Errorf("error creating credentials request: %w", err) + } + + resp, err := client.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing credentials request: %w", err) + } + + defer resp.Body.Close() // nolint:errcheck + + switch resp.StatusCode { + case 200: + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading credentials response: %w", err) + } + + var securityInfo []SecurityInfo + if err := json.Unmarshal(body, &securityInfo); err != nil { + return nil, fmt.Errorf("error parsing credentials response: %w", err) + } + + return securityInfo, nil + case 204: + // Cloudian-API returns 204 if no security credentials found + return nil, ErrNotFound + default: + return nil, fmt.Errorf("error: list credentials unexpected status code: %d", resp.StatusCode) + } +} + // Delete a group and all its members. func (client Client) DeleteGroupRecursive(ctx context.Context, groupId string) error { users, err := client.ListUsers(ctx, groupId, nil) diff --git a/internal/sdk/cloudian/secret.go b/internal/sdk/cloudian/secret.go new file mode 100644 index 0000000..029637f --- /dev/null +++ b/internal/sdk/cloudian/secret.go @@ -0,0 +1,7 @@ +package cloudian + +type Secret string + +func (s Secret) String() string { + return "********" +} diff --git a/internal/sdk/cloudian/secret_test.go b/internal/sdk/cloudian/secret_test.go new file mode 100644 index 0000000..302b0e0 --- /dev/null +++ b/internal/sdk/cloudian/secret_test.go @@ -0,0 +1,24 @@ +package cloudian + +import ( + "encoding/json" + "testing" +) + +func TestSecretUnmarshal(t *testing.T) { + jsonString := `[{"accessKey":"124","secretKey":"x+2","createDate":1735894172440,"active":true}]` + + var secrets []SecurityInfo + err := json.Unmarshal([]byte(jsonString), &secrets) + if err != nil { + t.Errorf("Error deserializing from JSON: %v", err) + } + + if string(secrets[0].AccessKey) != "124" { + t.Errorf("Expected string equality to 124, got %v", secrets[0].AccessKey) + } + + if secrets[0].AccessKey.String() != "********" { + t.Errorf("Expected obfuscated string, got %v", secrets[0].SecretKey) + } +}