Skip to content

Commit cbce8ba

Browse files
committed
fix(iam): support application Entity Type
1 parent 1167d12 commit cbce8ba

6 files changed

+326
-24
lines changed

internal/namespaces/iam/v1alpha1/custom_iam.go

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
)
1313

1414
type apiKeyResponse struct {
15-
APIKey *iam.APIKey
16-
UserType string `json:"user_type"`
17-
Policies map[string][]string `json:"policies"`
15+
APIKey *iam.APIKey
16+
EntityType string `json:"entity_type"`
17+
Policies map[string][]string `json:"policies"`
1818
}
1919
type iamGetAPIKeyArgs struct {
2020
AccessKey string
@@ -31,6 +31,67 @@ func WithPolicies(withPolicies bool) apiKeyOptions {
3131
}
3232
}
3333

34+
type userEntity struct {
35+
UserID string
36+
}
37+
38+
type applicationEntity struct {
39+
ApplicationID string
40+
}
41+
42+
type entity interface {
43+
entityType(ctx context.Context, api *iam.API) (string, error)
44+
getPolicies(ctx context.Context, api *iam.API) ([]*iam.Policy, error)
45+
}
46+
47+
func (u userEntity) entityType(ctx context.Context, api *iam.API) (string, error) {
48+
user, err := api.GetUser(&iam.GetUserRequest{
49+
UserID: u.UserID,
50+
}, scw.WithContext(ctx))
51+
if err != nil {
52+
return "", err
53+
}
54+
55+
return string(user.Type), nil
56+
}
57+
58+
func (a applicationEntity) entityType(ctx context.Context, api *iam.API) (string, error) {
59+
return "application", nil
60+
}
61+
62+
func buildEntity(apiKey *iam.APIKey) (entity, error) {
63+
if apiKey == nil {
64+
return nil, fmt.Errorf("invalid API key")
65+
}
66+
if apiKey.UserID != nil {
67+
return userEntity{UserID: *apiKey.UserID}, nil
68+
}
69+
if apiKey.ApplicationID != nil {
70+
return applicationEntity{ApplicationID: *apiKey.ApplicationID}, nil
71+
}
72+
return nil, fmt.Errorf("invalid API key")
73+
}
74+
75+
func (u userEntity) getPolicies(ctx context.Context, api *iam.API) ([]*iam.Policy, error) {
76+
policies, err := api.ListPolicies(&iam.ListPoliciesRequest{
77+
UserIDs: []string{u.UserID},
78+
}, scw.WithContext(ctx), scw.WithAllPages())
79+
if err != nil {
80+
return nil, err
81+
}
82+
return policies.Policies, nil
83+
}
84+
85+
func (a applicationEntity) getPolicies(ctx context.Context, api *iam.API) ([]*iam.Policy, error) {
86+
policies, err := api.ListPolicies(&iam.ListPoliciesRequest{
87+
ApplicationIDs: []string{a.ApplicationID},
88+
}, scw.WithContext(ctx), scw.WithAllPages())
89+
if err != nil {
90+
return nil, err
91+
}
92+
return policies.Policies, nil
93+
}
94+
3495
func getApiKey(
3596
ctx context.Context,
3697
api *iam.API,
@@ -45,40 +106,37 @@ func getApiKey(
45106
return response, err
46107
}
47108

48-
user, err := api.GetUser(&iam.GetUserRequest{
49-
UserID: *apiKey.UserID,
50-
}, scw.WithContext(ctx))
109+
entity, err := buildEntity(apiKey)
110+
if err != nil {
111+
return response, err
112+
}
113+
114+
entityType, err := entity.entityType(ctx, api)
51115
if err != nil {
52116
return response, err
53117
}
54118

55119
response.APIKey = apiKey
56-
response.UserType = string(user.Type)
120+
response.EntityType = entityType
57121

58-
if user.Type == iam.UserTypeOwner {
59-
response.UserType = fmt.Sprintf(
122+
if entityType == string(iam.UserTypeOwner) {
123+
response.EntityType = fmt.Sprintf(
60124
"%s (owner has all permissions over the organization)",
61-
user.Type,
125+
entityType,
62126
)
63127

64128
return response, nil
65129
}
66130

67131
if options.WithPolicies {
68-
listPolicyRequest := &iam.ListPoliciesRequest{
69-
UserIDs: []string{*apiKey.UserID},
70-
}
71-
policies, err := api.ListPolicies(
72-
listPolicyRequest,
73-
scw.WithAllPages(),
74-
scw.WithContext(ctx),
75-
)
132+
policies, err := entity.getPolicies(ctx, api)
76133
if err != nil {
77134
return response, err
78135
}
136+
79137
// Build a map of policies -> [rules...]
80138
policyMap := map[string][]string{}
81-
for _, policy := range policies.Policies {
139+
for _, policy := range policies {
82140
rules, err := api.ListRules(
83141
&iam.ListRulesRequest{
84142
PolicyID: policy.ID,
@@ -107,7 +165,7 @@ func apiKeyMarshalerFunc(i any, opt *human.MarshalOpt) (string, error) {
107165

108166
opt.Sections = []*human.MarshalSection{
109167
{
110-
FieldName: "UserType",
168+
FieldName: "EntityType",
111169
},
112170
{
113171
FieldName: "APIKey",

internal/namespaces/iam/v1alpha1/custom_iam_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ func Test_iamAPIKeyGet(t *testing.T) {
5050
return apiKeys[0].AccessKey
5151
}
5252

53+
applicationResulter := func(result any) any {
54+
applications := result.([]*iamSdk.Application)
55+
if applications == nil {
56+
panic("applications is nil")
57+
}
58+
if len(applications) == 0 {
59+
panic("no application found")
60+
}
61+
62+
return applications[0].ID
63+
}
64+
5365
t.Run("GetOwnerAPIKey", core.Test(&core.TestConfig{
5466
Commands: commands,
5567
BeforeFunc: core.BeforeFuncCombine(
@@ -92,4 +104,26 @@ func Test_iamAPIKeyGet(t *testing.T) {
92104
core.TestCheckExitCode(0),
93105
),
94106
}))
107+
108+
t.Run("GetApplicationAPIKey", core.Test(&core.TestConfig{
109+
Commands: commands,
110+
BeforeFunc: core.BeforeFuncCombine(
111+
core.ExecStoreBeforeCmdWithResulter(
112+
"application",
113+
"scw iam application list",
114+
applicationResulter,
115+
),
116+
core.ExecStoreBeforeCmdWithResulter(
117+
"applicationAPIKey",
118+
"scw iam api-key list bearer-id={{ .application }}",
119+
apiKeyResulter,
120+
),
121+
),
122+
Cmd: `scw iam api-key get {{ .applicationAPIKey }}`,
123+
Check: core.TestCheckCombine(
124+
core.TestCheckGolden(),
125+
core.TestCheckExitCode(0),
126+
),
127+
}))
128+
95129
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
version: 1
3+
interactions:
4+
- request:
5+
body: '{"applications":[{"id":"370d4bad-80b5-4602-8def-f2a34b7581de","name":"tf-tests-iam-group-membership-basic-app1","description":"","created_at":"2025-06-02T13:13:41.151439Z","updated_at":"2025-06-02T13:13:41.151439Z","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"nb_api_keys":1,"tags":[]},{"id":"e40e18d2-9d2d-4139-82bd-eabcf776669a","name":"tf-tests-iam-group-membership-basic","description":"","created_at":"2025-06-11T09:06:37.802556Z","updated_at":"2025-06-11T09:06:37.802556Z","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"nb_api_keys":0,"tags":[]}],"total_count":2}'
6+
form: {}
7+
headers:
8+
User-Agent:
9+
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.3; darwin; arm64) cli-e2e-test
10+
url: https://api.scaleway.com/iam/v1alpha1/applications?order_by=created_at_asc&organization_id=11111111-1111-1111-1111-111111111111&page=1
11+
method: GET
12+
response:
13+
body: '{"applications":[{"id":"370d4bad-80b5-4602-8def-f2a34b7581de","name":"tf-tests-iam-group-membership-basic-app1","description":"","created_at":"2025-06-02T13:13:41.151439Z","updated_at":"2025-06-02T13:13:41.151439Z","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"nb_api_keys":1,"tags":[]},{"id":"e40e18d2-9d2d-4139-82bd-eabcf776669a","name":"tf-tests-iam-group-membership-basic","description":"","created_at":"2025-06-11T09:06:37.802556Z","updated_at":"2025-06-11T09:06:37.802556Z","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"nb_api_keys":0,"tags":[]}],"total_count":2}'
14+
headers:
15+
Content-Length:
16+
- "691"
17+
Content-Security-Policy:
18+
- default-src 'none'; frame-ancestors 'none'
19+
Content-Type:
20+
- application/json
21+
Date:
22+
- Fri, 20 Jun 2025 13:53:46 GMT
23+
Server:
24+
- Scaleway API Gateway (fr-par-1;edge03)
25+
Strict-Transport-Security:
26+
- max-age=63072000
27+
X-Content-Type-Options:
28+
- nosniff
29+
X-Frame-Options:
30+
- DENY
31+
X-Request-Id:
32+
- e2ff4071-4b3b-4530-be28-fe94c1f5341c
33+
status: 200 OK
34+
code: 200
35+
duration: ""
36+
- request:
37+
body: '{"api_keys":[{"access_key":"SCW2NSMT7HPTHMJBWP0S","secret_key":null,"description":"","created_at":"2025-06-19T15:14:18.042537Z","updated_at":"2025-06-19T15:14:18.042537Z","expires_at":null,"default_project_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"creation_ip":"51.159.46.153","application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}],"total_count":1}'
38+
form: {}
39+
headers:
40+
User-Agent:
41+
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.3; darwin; arm64) cli-e2e-test
42+
url: https://api.scaleway.com/iam/v1alpha1/api-keys?bearer_id=370d4bad-80b5-4602-8def-f2a34b7581de&bearer_type=unknown_bearer_type&order_by=created_at_asc&organization_id=11111111-1111-1111-1111-111111111111&page=1
43+
method: GET
44+
response:
45+
body: '{"api_keys":[{"access_key":"SCW2NSMT7HPTHMJBWP0S","secret_key":null,"description":"","created_at":"2025-06-19T15:14:18.042537Z","updated_at":"2025-06-19T15:14:18.042537Z","expires_at":null,"default_project_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"creation_ip":"51.159.46.153","application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}],"total_count":1}'
46+
headers:
47+
Content-Length:
48+
- "402"
49+
Content-Security-Policy:
50+
- default-src 'none'; frame-ancestors 'none'
51+
Content-Type:
52+
- application/json
53+
Date:
54+
- Fri, 20 Jun 2025 13:53:46 GMT
55+
Server:
56+
- Scaleway API Gateway (fr-par-1;edge03)
57+
Strict-Transport-Security:
58+
- max-age=63072000
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- DENY
63+
X-Request-Id:
64+
- 1f0fcf2b-0b50-4440-b712-7a7111ac316f
65+
status: 200 OK
66+
code: 200
67+
duration: ""
68+
- request:
69+
body: '{"access_key":"SCW2NSMT7HPTHMJBWP0S","secret_key":null,"description":"","created_at":"2025-06-19T15:14:18.042537Z","updated_at":"2025-06-19T15:14:18.042537Z","expires_at":null,"default_project_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"creation_ip":"51.159.46.153","application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}'
70+
form: {}
71+
headers:
72+
User-Agent:
73+
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.3; darwin; arm64) cli-e2e-test
74+
url: https://api.scaleway.com/iam/v1alpha1/api-keys/SCW2NSMT7HPTHMJBWP0S
75+
method: GET
76+
response:
77+
body: '{"access_key":"SCW2NSMT7HPTHMJBWP0S","secret_key":null,"description":"","created_at":"2025-06-19T15:14:18.042537Z","updated_at":"2025-06-19T15:14:18.042537Z","expires_at":null,"default_project_id":"6867048b-fe12-4e96-835e-41c79a39604b","editable":true,"deletable":true,"managed":false,"creation_ip":"51.159.46.153","application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}'
78+
headers:
79+
Content-Length:
80+
- "371"
81+
Content-Security-Policy:
82+
- default-src 'none'; frame-ancestors 'none'
83+
Content-Type:
84+
- application/json
85+
Date:
86+
- Fri, 20 Jun 2025 13:53:46 GMT
87+
Server:
88+
- Scaleway API Gateway (fr-par-1;edge03)
89+
Strict-Transport-Security:
90+
- max-age=63072000
91+
X-Content-Type-Options:
92+
- nosniff
93+
X-Frame-Options:
94+
- DENY
95+
X-Request-Id:
96+
- aaec1f7c-1c09-4b15-8e9f-f31a3f678d16
97+
status: 200 OK
98+
code: 200
99+
duration: ""
100+
- request:
101+
body: '{"policies":[{"id":"4feb1f44-1726-4373-bf9b-01c828ddb0ca","name":"Copy
102+
of Editors","description":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","created_at":"2025-05-09T10:39:40.931178Z","updated_at":"2025-05-09T10:39:40.931178Z","editable":true,"deletable":true,"managed":false,"nb_rules":2,"nb_scopes":2,"nb_permission_sets":4,"tags":[],"application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}],"total_count":1}'
103+
form: {}
104+
headers:
105+
User-Agent:
106+
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.3; darwin; arm64) cli-e2e-test
107+
url: https://api.scaleway.com/iam/v1alpha1/policies?application_ids=370d4bad-80b5-4602-8def-f2a34b7581de&order_by=policy_name_asc&organization_id=11111111-1111-1111-1111-111111111111&page=1
108+
method: GET
109+
response:
110+
body: '{"policies":[{"id":"4feb1f44-1726-4373-bf9b-01c828ddb0ca","name":"Copy
111+
of Editors","description":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b","created_at":"2025-05-09T10:39:40.931178Z","updated_at":"2025-05-09T10:39:40.931178Z","editable":true,"deletable":true,"managed":false,"nb_rules":2,"nb_scopes":2,"nb_permission_sets":4,"tags":[],"application_id":"370d4bad-80b5-4602-8def-f2a34b7581de"}],"total_count":1}'
112+
headers:
113+
Content-Length:
114+
- "426"
115+
Content-Security-Policy:
116+
- default-src 'none'; frame-ancestors 'none'
117+
Content-Type:
118+
- application/json
119+
Date:
120+
- Fri, 20 Jun 2025 13:53:46 GMT
121+
Server:
122+
- Scaleway API Gateway (fr-par-1;edge03)
123+
Strict-Transport-Security:
124+
- max-age=63072000
125+
X-Content-Type-Options:
126+
- nosniff
127+
X-Frame-Options:
128+
- DENY
129+
X-Request-Id:
130+
- 98cd9ade-ee35-4fbf-ae9e-df865bc734c0
131+
status: 200 OK
132+
code: 200
133+
duration: ""
134+
- request:
135+
body: '{"rules":[{"id":"a7d5a179-d818-4cf2-a280-3d9445381cd2","permission_set_names":["OrganizationReadOnly","ProjectManager","SupportTicketReadOnly"],"permission_sets_scope_type":"organization","condition":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b"},{"id":"5b5c2ab4-48e3-45a5-a9a3-ac3ea382f773","permission_set_names":["AllProductsFullAccess"],"permission_sets_scope_type":"projects","condition":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b"}],"total_count":2}'
136+
form: {}
137+
headers:
138+
User-Agent:
139+
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.3; darwin; arm64) cli-e2e-test
140+
url: https://api.scaleway.com/iam/v1alpha1/rules?policy_id=4feb1f44-1726-4373-bf9b-01c828ddb0ca
141+
method: GET
142+
response:
143+
body: '{"rules":[{"id":"a7d5a179-d818-4cf2-a280-3d9445381cd2","permission_set_names":["OrganizationReadOnly","ProjectManager","SupportTicketReadOnly"],"permission_sets_scope_type":"organization","condition":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b"},{"id":"5b5c2ab4-48e3-45a5-a9a3-ac3ea382f773","permission_set_names":["AllProductsFullAccess"],"permission_sets_scope_type":"projects","condition":"","organization_id":"6867048b-fe12-4e96-835e-41c79a39604b"}],"total_count":2}'
144+
headers:
145+
Content-Length:
146+
- "485"
147+
Content-Security-Policy:
148+
- default-src 'none'; frame-ancestors 'none'
149+
Content-Type:
150+
- application/json
151+
Date:
152+
- Fri, 20 Jun 2025 13:53:46 GMT
153+
Server:
154+
- Scaleway API Gateway (fr-par-1;edge03)
155+
Strict-Transport-Security:
156+
- max-age=63072000
157+
X-Content-Type-Options:
158+
- nosniff
159+
X-Frame-Options:
160+
- DENY
161+
X-Request-Id:
162+
- f676dfd8-c93d-4cec-9394-78fdb6d157dc
163+
status: 200 OK
164+
code: 200
165+
duration: ""

0 commit comments

Comments
 (0)