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
Pull Request — master (#207)
by Victor Hugo
01:19
created

mollie.*Client.WithAuthenticationValue   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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