go-auth0/auth0

View on GitHub
management/management.go

Summary

Maintainability
A
3 hrs
Test Coverage
B
83%
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"
    "gopkg.in/auth0.v5/internal/client"
)

type ManagementOption func(*Management)

// WithDebug configures the management client to dump http requests and
// responses to stdout.
func WithDebug(d bool) ManagementOption {
    return func(m *Management) {
        m.debug = d
    }
}

// WitContext configures the management client to use the provided context
// instead of the provided one.
func WithContext(ctx context.Context) ManagementOption {
    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) ManagementOption {
    return func(m *Management) {
        m.userAgent = userAgent
    }
}

// WithClientCredentials configures management to authenticate using the client
// credentials authentication flow.
func WithClientCredentials(clientID, clientSecret string) ManagementOption {
    return func(m *Management) {
        m.tokenSource = client.ClientCredentials(m.ctx, m.url.String(), clientID, clientSecret)
    }
}

// WithStaticToken configures management to authenticate using a static
// authentication token.
func WithStaticToken(token string) ManagementOption {
    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 options is available for testing purposes and should not be used in
// production.
func WithInsecure() ManagementOption {
    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) ManagementOption {
    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

    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 ...ManagementOption) (*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)

    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 {
    return (&url.URL{
        Scheme: m.url.Scheme,
        Host:   m.url.Host,
        Path:   m.basePath + "/" + strings.Join(path, "/"),
    }).String()
}

// 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
}

func (m *managementError) Error() string {
    return fmt.Sprintf("%d %s: %s", m.StatusCode, m.Err, m.Message)
}

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"`
}

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)
    })
}

// WithFields configures a request to include the desired fields.
//
// Deprecated: use IncludeFields instead.
func WithFields(fields ...string) RequestOption {
    return IncludeFields(fields...)
}

// WithoutFields configures a request to exclude the desired fields.
//
// Deprecated: use ExcludeFields instead.
func WithoutFields(fields ...string) RequestOption {
    return ExcludeFields(fields...)
}

// 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:"alice@example.com"`))
//   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)
}