GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( d93ecb...867cf6 )
by Victor Hugo
01:22 queued 13s
created

mollie.*Client.addRequestHeaders   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 9
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 4
rs 9.95
c 0
b 0
f 0
nop 1
1
package mollie
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/json"
7
	"errors"
8
	"fmt"
9
	"io"
10
	"net/http"
11
	"net/url"
12
	"os"
13
	"regexp"
14
	"runtime"
15
	"strings"
16
17
	"github.com/VictorAvelar/mollie-api-go/v3/mollie/tools/idempotency"
18
	"github.com/google/go-querystring/query"
19
)
20
21
// Constants holding values for client initialization and request instantiation.
22
const (
23
	BaseURL              string = "https://api.mollie.com/"
24
	AuthHeader           string = "Authorization"
25
	TokenType            string = "Bearer"
26
	APITokenEnv          string = "MOLLIE_API_TOKEN"
27
	OrgTokenEnv          string = "MOLLIE_ORG_TOKEN"
28
	RequestContentType   string = "application/json"
29
	IdempotencyKeyHeader string = "Idempotency-Key"
30
)
31
32
var (
33
	accessTokenExpr = regexp.MustCompile(`(?m)^access_`)
34
	errEmptyAuthKey = errors.New("you must provide a non-empty authentication key")
35
	errBadBaseURL   = errors.New("malformed base url, it must contain a trailing slash")
36
)
37
38
// Client manages communication with Mollie's API.
39
type Client struct {
40
	BaseURL        *url.URL
41
	authentication string
42
	userAgent      string
43
	client         *http.Client
44
	common         service // Reuse a single struct instead of allocating one for each service on the heap.
45
	config         *Config
46
	// Tools
47
	idempotencyKeyProvider idempotency.KeyGenerator
48
	// Services
49
	Payments       *PaymentsService
50
	Chargebacks    *ChargebacksService
51
	PaymentMethods *PaymentMethodsService
52
	Invoices       *InvoicesService
53
	Organizations  *OrganizationsService
54
	Profiles       *ProfilesService
55
	Refunds        *RefundsService
56
	Shipments      *ShipmentsService
57
	Orders         *OrdersService
58
	Settlements    *SettlementsService
59
	Captures       *CapturesService
60
	Subscriptions  *SubscriptionsService
61
	Customers      *CustomersService
62
	Miscellaneous  *MiscellaneousService
63
	Mandates       *MandatesService
64
	Permissions    *PermissionsService
65
	Onboarding     *OnboardingService
66
	PaymentLinks   *PaymentLinksService
67
	Partners       *PartnerService
68
	Balances       *BalancesService
69
}
70
71
type service struct {
72
	client *Client
73
}
74
75
func (c *Client) get(ctx context.Context, uri string, options interface{}) (res *Response, err error) {
76 1
	if options != nil {
77 1
		v, _ := query.Values(options)
78 1
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
79
	}
80
81 1
	req, err := c.NewAPIRequest(ctx, http.MethodGet, uri, nil)
82 1
	if err != nil {
83 1
		return
84
	}
85
86 1
	return c.Do(req)
87
}
88
89
func (c *Client) post(ctx context.Context, uri string, body interface{}, options interface{}) (
90
	res *Response,
91
	err error,
92
) {
93 1
	if options != nil {
94 1
		v, _ := query.Values(options)
95 1
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
96
	}
97
98 1
	req, err := c.NewAPIRequest(ctx, http.MethodPost, uri, body)
99 1
	if err != nil {
100 1
		return
101
	}
102
103 1
	return c.Do(req)
104
}
105
106
func (c *Client) patch(ctx context.Context, uri string, body interface{}, options interface{}) (
107
	res *Response,
108
	err error,
109
) {
110 1
	if options != nil {
111
		v, _ := query.Values(options)
112
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
113
	}
114
115 1
	req, err := c.NewAPIRequest(ctx, http.MethodPatch, uri, body)
116 1
	if err != nil {
117 1
		return
118
	}
119
120 1
	return c.Do(req)
121
}
122
123
func (c *Client) delete(ctx context.Context, uri string, options interface{}) (res *Response, err error) {
124 1
	if options != nil {
125
		v, _ := query.Values(options)
126
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
127
	}
128
129 1
	req, err := c.NewAPIRequest(ctx, http.MethodDelete, uri, nil)
130 1
	if err != nil {
131 1
		return
132
	}
133
134 1
	return c.Do(req)
135
}
136
137
// WithAuthenticationValue offers a convenient setter for any of the valid authentication
138
// tokens provided by Mollie.
139
//
140
// Ideally your API key will be provided from and environment variable or
141
// a secret management engine.
142
// This should only be used when environment variables are "impossible" to be used.
143
func (c *Client) WithAuthenticationValue(k string) error {
144 1
	if k == "" {
145 1
		return errEmptyAuthKey
146
	}
147
148 1
	c.authentication = strings.TrimSpace(k)
149
150 1
	return nil
151
}
152
153
// HasAccessToken will return true when the provided authentication token
154
// complies with the access token REGEXP match check.
155
// This will enable TestMode inside the request body.
156
//
157
// See: https://github.com/VictorAvelar/mollie-api-go/issues/123
158
func (c *Client) HasAccessToken() bool {
159 1
	return accessTokenExpr.Match([]byte(c.authentication))
160
}
161
162
// SetIdempotencyKeyGenerator allows you to pass your own idempotency
163
// key generator.
164
func (c *Client) SetIdempotencyKeyGenerator(kg idempotency.KeyGenerator) {
165 1
	c.idempotencyKeyProvider = kg
166
}
167
168
// NewAPIRequest is a wrapper around the http.NewRequest function.
169
//
170
// It will setup the authentication headers/parameters according to the client config.
171
func (c *Client) NewAPIRequest(ctx context.Context, method string, uri string, body interface{}) (
172
	req *http.Request,
173
	err error,
174
) {
175
	//nolint: contextcheck
176 1
	if !strings.HasSuffix(c.BaseURL.Path, "/") {
177 1
		return nil, errBadBaseURL
178
	}
179
180 1
	url, err := c.BaseURL.Parse(uri)
181 1
	if err != nil {
182 1
		return nil, fmt.Errorf("url_parsing_error: %w", err)
183
	}
184
185 1
	if c.config.testing && c.HasAccessToken() {
186 1
		qp := url.Query()
187 1
		qp.Add("testmode", "true")
188 1
		url.RawQuery = qp.Encode()
189
	}
190
191 1
	var buf io.ReadWriter
192 1
	if body != nil {
193 1
		buf = new(bytes.Buffer)
194 1
		enc := json.NewEncoder(buf)
195 1
		enc.SetEscapeHTML(false)
196
197 1
		err := enc.Encode(body)
198 1
		if err != nil {
199 1
			return nil, fmt.Errorf("encoding_error: %w", err)
200
		}
201
	}
202
203 1
	if ctx == nil {
204 1
		ctx = context.Background()
205
	}
206
207 1
	req, err = http.NewRequestWithContext(ctx, method, url.String(), buf)
208 1
	if err != nil {
209 1
		return
210
	}
211
212 1
	c.addRequestHeaders(req)
213
214 1
	return req, nil
215
}
216
217
func (c *Client) addRequestHeaders(req *http.Request) {
218 1
	req.Header.Add(AuthHeader, strings.Join([]string{TokenType, c.authentication}, " "))
219 1
	req.Header.Set("Content-Type", RequestContentType)
220 1
	req.Header.Set("Accept", RequestContentType)
221 1
	req.Header.Set("User-Agent", c.userAgent)
222
223 1
	if c.config.reqIdempotency &&
224
		c.idempotencyKeyProvider != nil &&
225
		req.Method == http.MethodPost {
226 1
		req.Header.Set(IdempotencyKeyHeader, c.idempotencyKeyProvider.Generate())
227
	}
228
}
229
230
// Do sends an API request and returns the API response or returned as an
231
// error if an API error has occurred.
232
func (c *Client) Do(req *http.Request) (*Response, error) {
233 1
	resp, err := c.client.Do(req)
234 1
	if err != nil {
235 1
		return nil, fmt.Errorf("http_error: %w", err)
236
	}
237 1
	defer resp.Body.Close()
238
239 1
	response, err := newResponse(resp)
240 1
	if err != nil {
241
		return response, err
242
	}
243
244 1
	err = CheckResponse(response)
245 1
	if err != nil {
246 1
		return response, err
247
	}
248
249 1
	return response, nil
250
}
251
252
// NewClient returns a new Mollie HTTP API client.
253
// You can pass a previously build http client, if none is provided then
254
// http.DefaultClient will be used.
255
//
256
// NewClient will lookup the environment for values to assign to the
257
// API token (`MOLLIE_API_TOKEN`) and the Organization token (`MOLLIE_ORG_TOKEN`)
258
// according to the provided Config object.
259
//
260
// You can also set the token values programmatically by using the Client
261
// WithAPIKey and WithOrganizationKey functions.
262
func NewClient(baseClient *http.Client, conf *Config) (mollie *Client, err error) {
263 1
	if baseClient == nil {
264 1
		baseClient = http.DefaultClient
265
	}
266
267 1
	uri, _ := url.Parse(BaseURL)
268
269 1
	mollie = &Client{
270
		BaseURL:                uri,
271
		client:                 baseClient,
272
		config:                 conf,
273
		idempotencyKeyProvider: nil,
274
	}
275
276 1
	mollie.common.client = mollie
277
278 1
	if mollie.config.reqIdempotency {
279 1
		mollie.common.client.idempotencyKeyProvider = idempotency.NewStdGenerator()
280
	}
281
282
	// services for resources
283 1
	mollie.Payments = (*PaymentsService)(&mollie.common)
284 1
	mollie.Chargebacks = (*ChargebacksService)(&mollie.common)
285 1
	mollie.PaymentMethods = (*PaymentMethodsService)(&mollie.common)
286 1
	mollie.Invoices = (*InvoicesService)(&mollie.common)
287 1
	mollie.Organizations = (*OrganizationsService)(&mollie.common)
288 1
	mollie.Profiles = (*ProfilesService)(&mollie.common)
289 1
	mollie.Refunds = (*RefundsService)(&mollie.common)
290 1
	mollie.Shipments = (*ShipmentsService)(&mollie.common)
291 1
	mollie.Orders = (*OrdersService)(&mollie.common)
292 1
	mollie.Captures = (*CapturesService)(&mollie.common)
293 1
	mollie.Settlements = (*SettlementsService)(&mollie.common)
294 1
	mollie.Subscriptions = (*SubscriptionsService)(&mollie.common)
295 1
	mollie.Customers = (*CustomersService)(&mollie.common)
296 1
	mollie.Miscellaneous = (*MiscellaneousService)(&mollie.common)
297 1
	mollie.Mandates = (*MandatesService)(&mollie.common)
298 1
	mollie.Permissions = (*PermissionsService)(&mollie.common)
299 1
	mollie.Onboarding = (*OnboardingService)(&mollie.common)
300 1
	mollie.PaymentLinks = (*PaymentLinksService)(&mollie.common)
301 1
	mollie.Partners = (*PartnerService)(&mollie.common)
302 1
	mollie.Balances = (*BalancesService)(&mollie.common)
303
304 1
	mollie.userAgent = strings.Join([]string{
305
		runtime.GOOS,
306
		runtime.GOARCH,
307
		runtime.Version(),
308
	}, ";")
309
310
	// Parse authorization from specified environment variable
311 1
	tkn, ok := os.LookupEnv(mollie.config.auth)
312 1
	if ok {
313 1
		mollie.authentication = tkn
314
	}
315
316 1
	return mollie, nil
317
}
318
319
/*
320
Constructor for Error.
321
*/
322
func newError(rsp *Response) error {
323 1
	baseErr := &BaseError{}
324
325 1
	if rsp.ContentLength > 0 {
326 1
		err := json.Unmarshal(rsp.content, baseErr)
327 1
		if err != nil {
328
			return err
329
		}
330
	} else {
331 1
		baseErr.Status = rsp.StatusCode
332 1
		baseErr.Title = rsp.Status
333 1
		baseErr.Detail = string(rsp.content)
334
	}
335
336 1
	return baseErr
337
}
338
339
// Response is a Mollie API response. This wraps the standard http.Response
340
// returned from Mollie and provides convenient access to things like
341
// pagination links.
342
type Response struct {
343
	*http.Response
344
	content []byte
345
}
346
347
func newResponse(rsp *http.Response) (*Response, error) {
348 1
	res := Response{Response: rsp}
349
350 1
	data, err := io.ReadAll(rsp.Body)
351 1
	if err != nil {
352
		return &res, err
353
	}
354
355 1
	res.content = data
356
357 1
	rsp.Body = io.NopCloser(bytes.NewBuffer(data))
358 1
	res.Response = rsp
359
360 1
	return &res, nil
361
}
362
363
// CheckResponse checks the API response for errors, and returns them if
364
// present. A response is considered an error if it has a status code outside
365
// the 200 range.
366
// API error responses are expected to have either no response
367
// body, or a JSON response body.
368
func CheckResponse(r *Response) error {
369 1
	if r.StatusCode >= http.StatusMultipleChoices {
370 1
		return newError(r)
371
	}
372
373 1
	return nil
374
}
375