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 ( 24340b...dc34c5 )
by Victor Hugo
02:13 queued 13s
created

mollie.*Client.NewAPIRequest   C

Complexity

Conditions 9

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 9

Importance

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