-
Notifications
You must be signed in to change notification settings - Fork 60
/
management.go
524 lines (438 loc) · 13.4 KB
/
management.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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
package management
//go:generate go run gen-methods.go
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"golang.org/x/oauth2"
"github.com/auth0/go-auth0/internal/client"
)
// Option is used for passing options to the Management client.
type Option func(*Management)
// WithDebug configures the management client to dump http requests and
// responses to stdout.
func WithDebug(d bool) Option {
return func(m *Management) {
m.debug = d
}
}
// WithContext configures the management client to use the provided context
// instead of the provided one.
func WithContext(ctx context.Context) Option {
return func(m *Management) {
m.ctx = ctx
}
}
// WithUserAgent configures the management client to use the provided user agent
// string instead of the default one.
func WithUserAgent(userAgent string) Option {
return func(m *Management) {
m.userAgent = userAgent
}
}
// WithClientCredentials configures management to authenticate using the client
// credentials authentication flow.
func WithClientCredentials(clientID, clientSecret string) Option {
return func(m *Management) {
m.tokenSource = client.OAuth2ClientCredentials(m.ctx, m.url.String(), clientID, clientSecret)
}
}
// WithStaticToken configures management to authenticate using a static
// authentication token.
func WithStaticToken(token string) Option {
return func(m *Management) {
m.tokenSource = client.StaticToken(token)
}
}
// WithInsecure configures management to not use an authentication token and
// use HTTP instead of HTTPS.
//
// This option is available for testing purposes and should not be used in
// production.
func WithInsecure() Option {
return func(m *Management) {
m.tokenSource = client.StaticToken("insecure")
m.url.Scheme = "http"
}
}
// WithClient configures management to use the provided client.
func WithClient(client *http.Client) Option {
return func(m *Management) {
m.http = client
}
}
// Management is an Auth0 management client used to interact with the Auth0
// Management API v2.
type Management struct {
// Client manages Auth0 Client (also known as Application) resources.
Client *ClientManager
// ClientGrant manages Auth0 ClientGrant resources.
ClientGrant *ClientGrantManager
// ResourceServer manages Auth0 Resource Server (also known as API)
// resources.
ResourceServer *ResourceServerManager
// Connection manages Auth0 Connection resources.
Connection *ConnectionManager
// CustomDomain manages Auth0 Custom Domains.
CustomDomain *CustomDomainManager
// Grant manages Auth0 Grants.
Grant *GrantManager
// Log reads Auth0 Logs.
Log *LogManager
// LogStream reads Auth0 Logs.
LogStream *LogStreamManager
// RoleManager manages Auth0 Roles.
Role *RoleManager
// RuleManager manages Auth0 Rules.
Rule *RuleManager
// HookManager manages Auth0 Hooks
Hook *HookManager
// RuleManager manages Auth0 Rule Configurations.
RuleConfig *RuleConfigManager
// Email manages Auth0 Email Providers.
Email *EmailManager
// EmailTemplate manages Auth0 Email Templates.
EmailTemplate *EmailTemplateManager
// User manages Auth0 User resources.
User *UserManager
// Job manages Auth0 jobs.
Job *JobManager
// Tenant manages your Auth0 Tenant.
Tenant *TenantManager
// Ticket creates verify email or change password tickets.
Ticket *TicketManager
// Stat is used to retrieve usage statistics.
Stat *StatManager
// Branding settings such as company logo or primary color.
Branding *BrandingManager
// Guardian manages your Auth0 Guardian settings
Guardian *GuardianManager
// Prompt manages your prompt settings.
Prompt *PromptManager
// Blacklist manages the auth0 blacklists
Blacklist *BlacklistManager
// SigningKey manages Auth0 Application Signing Keys.
SigningKey *SigningKeyManager
// Anomaly manages the IP blocks
Anomaly *AnomalyManager
// Actions manages Actions extensibility
Action *ActionManager
// Organization manages Auth0 Organizations.
Organization *OrganizationManager
// AttackProtection manages Auth0 Attack Protection.
AttackProtection *AttackProtectionManager
url *url.URL
basePath string
userAgent string
debug bool
ctx context.Context
tokenSource oauth2.TokenSource
http *http.Client
}
// New creates a new Auth0 Management client by authenticating using the
// supplied client id and secret.
func New(domain string, options ...Option) (*Management, error) {
// Ignore the scheme if it was defined in the domain variable. Then prefix
// with https as its the only scheme supported by the Auth0 API.
if i := strings.Index(domain, "//"); i != -1 {
domain = domain[i+2:]
}
domain = "https://" + domain
u, err := url.Parse(domain)
if err != nil {
return nil, err
}
m := &Management{
url: u,
basePath: "api/v2",
userAgent: client.UserAgent,
debug: false,
ctx: context.Background(),
http: http.DefaultClient,
}
for _, option := range options {
option(m)
}
m.http = client.Wrap(m.http, m.tokenSource,
client.WithDebug(m.debug),
client.WithUserAgent(m.userAgent),
client.WithRateLimit())
m.Client = newClientManager(m)
m.ClientGrant = newClientGrantManager(m)
m.Connection = newConnectionManager(m)
m.CustomDomain = newCustomDomainManager(m)
m.Grant = newGrantManager(m)
m.LogStream = newLogStreamManager(m)
m.Log = newLogManager(m)
m.ResourceServer = newResourceServerManager(m)
m.Role = newRoleManager(m)
m.Rule = newRuleManager(m)
m.Hook = newHookManager(m)
m.RuleConfig = newRuleConfigManager(m)
m.EmailTemplate = newEmailTemplateManager(m)
m.Email = newEmailManager(m)
m.User = newUserManager(m)
m.Job = newJobManager(m)
m.Tenant = newTenantManager(m)
m.Ticket = newTicketManager(m)
m.Stat = newStatManager(m)
m.Branding = newBrandingManager(m)
m.Guardian = newGuardianManager(m)
m.Prompt = newPromptManager(m)
m.Blacklist = newBlacklistManager(m)
m.SigningKey = newSigningKeyManager(m)
m.Anomaly = newAnomalyManager(m)
m.Action = newActionManager(m)
m.Organization = newOrganizationManager(m)
m.AttackProtection = newAttackProtectionManager(m)
return m, nil
}
// URI returns the absolute URL of the Management API with any path segments
// appended to the end.
func (m *Management) URI(path ...string) string {
baseURL := &url.URL{
Scheme: m.url.Scheme,
Host: m.url.Host,
Path: m.basePath + "/",
}
const escapedForwardSlash = "%2F"
var escapedPath []string
for _, unescapedPath := range path {
// Go's url.PathEscape will not escape "/", but some user IDs do have a valid "/" in them.
// See https://github.com/golang/go/blob/b55a2fb3b0d67b346bac871737b862f16e5a6447/src/net/url/url.go#L141.
defaultPathEscaped := url.PathEscape(unescapedPath)
escapedPath = append(
escapedPath,
strings.Replace(defaultPathEscaped, "/", escapedForwardSlash, -1),
)
}
return baseURL.String() + strings.Join(escapedPath, "/")
}
// NewRequest returns a new HTTP request. If the payload is not nil it will be
// encoded as JSON.
func (m *Management) NewRequest(method, uri string, payload interface{}, options ...RequestOption) (r *http.Request, err error) {
var buf bytes.Buffer
if payload != nil {
err := json.NewEncoder(&buf).Encode(payload)
if err != nil {
return nil, fmt.Errorf("encoding request payload failed: %w", err)
}
}
r, err = http.NewRequest(method, uri, &buf)
if err != nil {
return nil, err
}
r.Header.Add("Content-Type", "application/json")
for _, option := range options {
option.apply(r)
}
return
}
// Do sends an HTTP request and returns an HTTP response, handling any context
// cancellations or timeouts.
func (m *Management) Do(req *http.Request) (*http.Response, error) {
ctx := req.Context()
res, err := m.http.Do(req)
if err != nil {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
return nil, err
}
}
return res, nil
}
// Request combines NewRequest and Do, while also handling decoding of response payload.
func (m *Management) Request(method, uri string, v interface{}, options ...RequestOption) error {
req, err := m.NewRequest(method, uri, v, options...)
if err != nil {
return err
}
res, err := m.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
return newError(res.Body)
}
if res.StatusCode != http.StatusNoContent && res.StatusCode != http.StatusAccepted {
err := json.NewDecoder(res.Body).Decode(v)
if err != nil {
return fmt.Errorf("decoding response payload failed: %w", err)
}
return res.Body.Close()
}
return nil
}
// Error is an interface describing any error which could be returned by the
// Auth0 Management API.
type Error interface {
// Status returns the status code returned by the server together with the
// present error.
Status() int
error
}
type managementError struct {
StatusCode int `json:"statusCode"`
Err string `json:"error"`
Message string `json:"message"`
}
func newError(r io.Reader) error {
m := &managementError{}
err := json.NewDecoder(r).Decode(m)
if err != nil {
return err
}
return m
}
// Error formats the error into a string representation.
func (m *managementError) Error() string {
return fmt.Sprintf("%d %s: %s", m.StatusCode, m.Err, m.Message)
}
// Status returns the status code of the error.
func (m *managementError) Status() int {
return m.StatusCode
}
// List is an envelope which is typically used when calling List() or Search()
// methods.
//
// It holds metadata such as the total result count, starting offset and limit.
//
// Specific implementations embed this struct, therefore its direct use is not
// useful. Rather it has been made public in order to aid documentation.
type List struct {
Start int `json:"start"`
Limit int `json:"limit"`
Length int `json:"length"`
Total int `json:"total"`
}
// HasNext returns true if the list has more results.
func (l List) HasNext() bool {
return l.Total > l.Start+l.Limit
}
// RequestOption configures a call (typically to retrieve a resource) to Auth0 with
// query parameters.
type RequestOption interface {
apply(*http.Request)
}
func newRequestOption(fn func(r *http.Request)) *requestOption {
return &requestOption{applyFn: fn}
}
type requestOption struct {
applyFn func(r *http.Request)
}
func (o *requestOption) apply(r *http.Request) {
o.applyFn(r)
}
func applyListDefaults(options []RequestOption) RequestOption {
return newRequestOption(func(r *http.Request) {
PerPage(50).apply(r)
for _, option := range options {
option.apply(r)
}
IncludeTotals(true).apply(r)
})
}
// Context configures a request to use the specified context.
func Context(ctx context.Context) RequestOption {
return newRequestOption(func(r *http.Request) {
*r = *r.WithContext(ctx)
})
}
// IncludeFields configures a request to include the desired fields.
func IncludeFields(fields ...string) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("fields", strings.Join(fields, ","))
q.Set("include_fields", "true")
r.URL.RawQuery = q.Encode()
})
}
// ExcludeFields configures a request to exclude the desired fields.
func ExcludeFields(fields ...string) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("fields", strings.Join(fields, ","))
q.Set("include_fields", "false")
r.URL.RawQuery = q.Encode()
})
}
// Page configures a request to receive a specific page, if the results where
// concatenated.
func Page(page int) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("page", strconv.FormatInt(int64(page), 10))
r.URL.RawQuery = q.Encode()
})
}
// PerPage configures a request to limit the amount of items in the result.
func PerPage(items int) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("per_page", strconv.FormatInt(int64(items), 10))
r.URL.RawQuery = q.Encode()
})
}
// IncludeTotals configures a request to include totals.
func IncludeTotals(include bool) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("include_totals", strconv.FormatBool(include))
r.URL.RawQuery = q.Encode()
})
}
// Query configures a request to search on specific query parameters.
//
// For example:
// List(Query(`email:"[email protected]"`))
// List(Query(`name:"jane smith"`))
// List(Query(`logins_count:[100 TO 200}`))
// List(Query(`logins_count:{100 TO *]`))
//
// See: https://auth0.com/docs/users/search/v3/query-syntax
func Query(s string) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set("search_engine", "v3")
q.Set("q", s)
r.URL.RawQuery = q.Encode()
})
}
// Parameter configures a request to add arbitrary query parameters to requests
// made to Auth0.
func Parameter(key, value string) RequestOption {
return newRequestOption(func(r *http.Request) {
q := r.URL.Query()
q.Set(key, value)
r.URL.RawQuery = q.Encode()
})
}
// Header configures a request to add HTTP headers to requests made to Auth0.
func Header(key, value string) RequestOption {
return newRequestOption(func(r *http.Request) {
r.Header.Set(key, value)
})
}
// Body configures a requests body.
func Body(b []byte) RequestOption {
return newRequestOption(func(r *http.Request) {
r.Body = ioutil.NopCloser(bytes.NewReader(b))
})
}
// Stringify returns a string representation of the value passed as an argument.
func Stringify(v interface{}) string {
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
panic(err)
}
return string(b)
}