-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathapi.go
322 lines (273 loc) · 9.95 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package api
//go:generate $GOPATH/bin/mockgen -source=api.go -destination ../mocks/api.go -package mocks -self_package github.com/snyk/go-application-framework/pkg/api/
import (
"encoding/json"
"fmt"
"github.com/snyk/go-application-framework/pkg/common"
"io"
"net/http"
"net/url"
"github.com/snyk/go-application-framework/internal/api/contract"
"github.com/snyk/go-application-framework/internal/constants"
)
type ApiClient interface {
GetDefaultOrgId() (orgID string, err error)
GetOrgIdFromSlug(slugName string) (string, error)
GetSlugFromOrgId(orgID string) (string, error)
Init(url string, client *http.Client)
GetFeatureFlag(flagname string, origId string) (bool, error)
GetUserMe() (string, error)
GetSelf() (contract.SelfResponse, error)
GetSastSettings(orgId string) (common.SastResponse, error)
}
var _ ApiClient = (*snykApiClient)(nil)
type snykApiClient struct {
url string
client *http.Client
}
// GetSlugFromOrgId retrieves the organization slug associated with a given Snyk organization ID.
//
// Parameters:
// - orgID (string): The UUID of the organization.
//
// Returns:
// - The organization slug as a string.
// - An error object (if the organization is not found, or if API request or response
// parsing errors occur).
func (a *snykApiClient) GetSlugFromOrgId(orgID string) (string, error) {
endpoint := "/rest/orgs/" + orgID
version := "2024-03-12"
body, err := clientGet(a, endpoint, &version)
if err != nil {
return "", err
}
var response contract.GetOrganizationResponse
err = json.Unmarshal(body, &response)
if err != nil {
return "", err
}
return response.Data.Attributes.Slug, nil
}
// GetOrgIdFromSlug retrieves the organization ID associated with a given Snyk organization slug.
//
// Parameters:
// - slugName (string): The unique slug identifier of the organization.
//
// Returns:
// - The organization ID as a string.
// - An error object (if the organization is not found, or if API request or response
// parsing errors occur).
func (a *snykApiClient) GetOrgIdFromSlug(slugName string) (string, error) {
endpoint := "/rest/orgs"
version := "2024-03-12"
body, err := clientGet(a, endpoint, &version, "slug", slugName)
if err != nil {
return "", err
}
var response contract.OrganizationsResponse
err = json.Unmarshal(body, &response)
if err != nil {
return "", err
}
organizations := response.Organizations
for _, organization := range organizations {
if organization.Attributes.Slug == slugName {
return organization.Id, nil
}
}
return "", fmt.Errorf("org ID not found for slug %v", slugName)
}
// GetDefaultOrgId retrieves the default organization ID associated with the authenticated user.
//
// Returns:
// - The user's default organization ID as a string.
// - An error object (if an error occurred while fetching user data).
func (a *snykApiClient) GetDefaultOrgId() (string, error) {
selfData, err := a.GetSelf()
if err != nil {
return "", fmt.Errorf("unable to retrieve org ID: %w", err)
}
return selfData.Data.Attributes.DefaultOrgContext, nil
}
// GetUserMe retrieves the username for the authenticated user from the Snyk API.
//
// Returns:
// - The authenticated user's username as a string.
// - An error object (if an error occurred while fetching user data or extracting the username).
func (a *snykApiClient) GetUserMe() (string, error) {
selfData, err := a.GetSelf()
if err != nil {
return "", fmt.Errorf("error while fetching self data: %w", err) // Prioritize error
}
// according to API spec for get /self
// username is not a mandatory field, only name and email
// service accounts contain only name (name of the service account token) and org_context uuid
// spec: https://apidocs.snyk.io/?version=2024-04-29#get-/self
if selfData.Data.Attributes.Username != "" {
return selfData.Data.Attributes.Username, nil
}
if selfData.Data.Attributes.Name != "" {
return selfData.Data.Attributes.Name, nil
}
return "", fmt.Errorf("error while extracting user: missing properties username/name")
}
// GetFeatureFlag determines the state of a feature flag for the specified organization.
//
// Parameters:
// - flagname (string): The name of the feature flag to check.
// - orgId (string): The ID of the organization associated with the feature flag.
//
// Returns:
// - A boolean indicating if the feature flag is enabled (true) or disabled (false).
// - An error object (if an error occurred during the API request, response parsing,
// or if the organization ID is invalid).
func (a *snykApiClient) GetFeatureFlag(flagname string, orgId string) (bool, error) {
const defaultResult = false
u := a.url + "/v1/cli-config/feature-flags/" + flagname + "?org=" + orgId
if len(orgId) <= 0 {
return defaultResult, fmt.Errorf("failed to lookup feature flag with orgiId not set")
}
res, err := a.client.Get(u)
if err != nil {
return defaultResult, fmt.Errorf("unable to retrieve feature flag: %w", err)
}
//goland:noinspection GoUnhandledErrorResult
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return defaultResult, fmt.Errorf("unable to retrieve feature flag: %w", err)
}
var flag contract.OrgFeatureFlagResponse
flag.Ok = defaultResult
if err = json.Unmarshal(body, &flag); err != nil {
return defaultResult, fmt.Errorf("unable to retrieve feature flag (status: %d): %w", res.StatusCode, err)
}
if res.StatusCode != http.StatusOK || flag.Code == http.StatusUnauthorized || flag.Code == http.StatusForbidden {
return defaultResult, err
}
return flag.Ok, nil
}
// GetSelf retrieves the authenticated user's information from the Snyk API.
//
// Returns:
// - A `contract.SelfResponse` struct containing the user's data.
// - An error object (if an error occurred during the API request or response parsing).
func (a *snykApiClient) GetSelf() (contract.SelfResponse, error) {
endpoint := "/rest/self"
var selfData contract.SelfResponse
body, err := clientGet(a, endpoint, nil)
if err != nil {
return selfData, err
}
if err = json.Unmarshal(body, &selfData); err != nil {
return selfData, fmt.Errorf("unable to retrieve self data: %w", err)
}
return selfData, nil
}
func (a *snykApiClient) GetSastSettings(orgId string) (common.SastResponse, error) {
var response common.SastResponse
var defaultResult common.SastResponse
endpoint := a.url + "/v1/cli-config/settings/sast?org=" + url.QueryEscape(orgId)
res, err := a.client.Get(endpoint)
if err != nil {
return defaultResult, fmt.Errorf("unable to retrieve settings: %w", err)
}
//goland:noinspection GoUnhandledErrorResult
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return defaultResult, fmt.Errorf("unable to retrieve settings: %w", err)
}
if err = json.Unmarshal(body, &response); err != nil {
return defaultResult, fmt.Errorf("unable to retrieve settings (status: %d): %w", res.StatusCode, err)
}
return response, err
}
// clientGet performs an HTTP GET request to the Snyk API, handling query parameters,
// API versioning, and basic error checking.
//
// Parameters:
// - a (snykApiClient): A reference to the Snyk API client object.
// - endpoint (string): The endpoint path to be appended to the API base URL.
// - version (*string): An optional pointer to a string specifying the desired API version.
// If nil or an empty string, the default API version is used.
// - queryParams (...string): A variable number of string arguments representing key-value
// pairs for additional query parameters. Parameters are expected
// in the format "key1", "value1", "key2", "value2", etc.
//
// Returns:
// - The raw response body as a byte slice ([]byte).
// - An error object (if an error occurred during the request or response handling).
//
// Example:
// apiVersion := "2022-01-12"
// response, err := clientGet(myApiClient, "/organizations", &apiVersion, "limit", "50")
func clientGet(a *snykApiClient, endpoint string, version *string, queryParams ...string) ([]byte, error) {
var apiVersion string = constants.SNYK_DEFAULT_API_VERSION
if version != nil && *version != "" {
apiVersion = *version
}
queryParams = append(queryParams, "version", apiVersion)
url, err := BuildUrl(a, endpoint, queryParams...)
if err != nil {
return nil, err
}
res, err := a.client.Get(url.String())
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed (status: %d)", res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
defer res.Body.Close()
return body, nil
}
// BuildUrl constructs a URL for the Snyk API, appending query parameters from a provided slice.
//
// Parameters:
// - a (snykApiClient): A reference to the Snyk API client object.
// - endpoint (string): The endpoint path to be appended to the API base URL.
// - queryParams (...string): A variable number of string arguments representing key-value pairs for the query parameters.
// Parameters are expected in the format "key1", "value1", "key2", "value2", etc.
//
// Returns:
// - A constructed url.URL object.
// - An error object (if an error occurred during URL construction or parsing).
//
// Example:
//
// url, err := BuildUrl(myApiClient, "/users", "filter", "active", "limit", "10")
// if err != nil {
// // Handle error
// }
// // Use the constructed url object (e.g., to make an API call)
func BuildUrl(a *snykApiClient, endpoint string, queryParams ...string) (*url.URL, error) {
u, err := url.Parse(a.url + endpoint)
if err != nil {
return nil, err
}
q := u.Query()
for i := 0; i < len(queryParams); i += 2 {
key := queryParams[i]
value := queryParams[i+1]
q.Set(key, value)
}
u.RawQuery = q.Encode()
return u, nil
}
func (a *snykApiClient) Init(url string, client *http.Client) {
a.url = url
a.client = client
}
func NewApi(url string, httpClient *http.Client) ApiClient {
client := NewApiInstance()
client.Init(url, httpClient)
return client
}
func NewApiInstance() ApiClient {
return &snykApiClient{}
}