// Copyright 2010 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Package oauth is consumer interface for OAuth 1.0, OAuth 1.0a and RFC 5849.
//
// Redirection-based Authorization
//
// This section outlines how to use the oauth package in redirection-based
// authorization (http://tools.ietf.org/html/rfc5849#section-2).
//
// Step 1: Create a Client using credentials and URIs provided by the server.
// The Client can be initialized once at application startup and stored in a
// package-level variable.
//
// Step 2: Request temporary credentials using the Client
// RequestTemporaryCredentials method. The callbackURL parameter is the URL of
// the callback handler in step 4. Save the returned credential secret so that
// it can be later found using credential token as a key. The secret can be
// stored in a database keyed by the token. Another option is to store the
// token and secret in session storage or a cookie.
//
// Step 3: Redirect the user to URL returned from AuthorizationURL method. The
// AuthorizationURL method uses the temporary credentials from step 2 and other
// parameters as specified by the server.
//
// Step 4: The server redirects back to the callback URL specified in step 2
// with the temporary token and a verifier. Use the temporary token to find the
// temporary secret saved in step 2. Using the temporary token, temporary
// secret and verifier, request token credentials using the client RequestToken
// method. Save the returned credentials for later use in the application.
//
// Signing Requests
//
// The Client type has two low-level methods for signing requests, SignForm and
// SetAuthorizationHeader.
//
// The SignForm method adds an OAuth signature to a form. The application makes
// an authenticated request by encoding the modified form to the query string
// or request body.
//
// The SetAuthorizationHeader method adds an OAuth siganture to a request
// header. The SetAuthorizationHeader method is the only way to correctly sign
// a request if the application sets the URL Opaque field when making a
// request.
//
// The Get, Put, Post and Delete methods sign and invoke a request using the
// supplied net/http Client. These methods are easy to use, but not as flexible
// as constructing a request using one of the low-level methods.
//
// Context With HTTP Client
//
// A context-enabled method can include a custom HTTP client in the
// context and execute an HTTP request using the included HTTP client.
//
//     hc := &http.Client{Timeout: 2 * time.Second}
//     ctx := context.WithValue(context.Background(), oauth.HTTPClient, hc)
//     c := oauth.Client{ /* Any settings */ }
//     resp, err := c.GetContext(ctx, &oauth.Credentials{}, rawurl, nil)
package oauth // import "github.com/garyburd/go-oauth/oauth"

import (
	"bytes"
	"crypto"
	"crypto/hmac"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"golang.org/x/net/context"
)

// noscape[b] is true if b should not be escaped per section 3.6 of the RFC.
var noEscape = [256]bool{
	'A': true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
	'a': true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
	'0': true, true, true, true, true, true, true, true, true, true,
	'-': true,
	'.': true,
	'_': true,
	'~': true,
}

// encode encodes string per section 3.6 of the RFC. If double is true, then
// the encoding is applied twice.
func encode(s string, double bool) []byte {
	// Compute size of result.
	m := 3
	if double {
		m = 5
	}
	n := 0
	for i := 0; i < len(s); i++ {
		if noEscape[s[i]] {
			n++
		} else {
			n += m
		}
	}

	p := make([]byte, n)

	// Encode it.
	j := 0
	for i := 0; i < len(s); i++ {
		b := s[i]
		if noEscape[b] {
			p[j] = b
			j++
		} else if double {
			p[j] = '%'
			p[j+1] = '2'
			p[j+2] = '5'
			p[j+3] = "0123456789ABCDEF"[b>>4]
			p[j+4] = "0123456789ABCDEF"[b&15]
			j += 5
		} else {
			p[j] = '%'
			p[j+1] = "0123456789ABCDEF"[b>>4]
			p[j+2] = "0123456789ABCDEF"[b&15]
			j += 3
		}
	}
	return p
}

type keyValue struct{ key, value []byte }

type byKeyValue []keyValue

func (p byKeyValue) Len() int      { return len(p) }
func (p byKeyValue) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p byKeyValue) Less(i, j int) bool {
	sgn := bytes.Compare(p[i].key, p[j].key)
	if sgn == 0 {
		sgn = bytes.Compare(p[i].value, p[j].value)
	}
	return sgn < 0
}

func (p byKeyValue) appendValues(values url.Values) byKeyValue {
	for k, vs := range values {
		k := encode(k, true)
		for _, v := range vs {
			v := encode(v, true)
			p = append(p, keyValue{k, v})
		}
	}
	return p
}

// writeBaseString writes method, url, and params to w using the OAuth signature
// base string computation described in section 3.4.1 of the RFC.
func writeBaseString(w io.Writer, method string, u *url.URL, form url.Values, oauthParams map[string]string) {
	// Method
	w.Write(encode(strings.ToUpper(method), false))
	w.Write([]byte{'&'})

	// URL
	scheme := strings.ToLower(u.Scheme)
	host := strings.ToLower(u.Host)

	uNoQuery := *u
	uNoQuery.RawQuery = ""
	path := uNoQuery.RequestURI()

	switch {
	case scheme == "http" && strings.HasSuffix(host, ":80"):
		host = host[:len(host)-len(":80")]
	case scheme == "https" && strings.HasSuffix(host, ":443"):
		host = host[:len(host)-len(":443")]
	}

	w.Write(encode(scheme, false))
	w.Write(encode("://", false))
	w.Write(encode(host, false))
	w.Write(encode(path, false))
	w.Write([]byte{'&'})

	// Create sorted slice of encoded parameters. Parameter keys and values are
	// double encoded in a single step. This is safe because double encoding
	// does not change the sort order.
	queryParams := u.Query()
	p := make(byKeyValue, 0, len(form)+len(queryParams)+len(oauthParams))
	p = p.appendValues(form)
	p = p.appendValues(queryParams)
	for k, v := range oauthParams {
		p = append(p, keyValue{encode(k, true), encode(v, true)})
	}
	sort.Sort(p)

	// Write the parameters.
	encodedAmp := encode("&", false)
	encodedEqual := encode("=", false)
	sep := false
	for _, kv := range p {
		if sep {
			w.Write(encodedAmp)
		} else {
			sep = true
		}
		w.Write(kv.key)
		w.Write(encodedEqual)
		w.Write(kv.value)
	}
}

var nonceCounter uint64

func init() {
	if err := binary.Read(rand.Reader, binary.BigEndian, &nonceCounter); err != nil {
		// fallback to time if rand reader is broken
		nonceCounter = uint64(time.Now().UnixNano())
	}
}

// nonce returns a unique string.
func nonce() string {
	return strconv.FormatUint(atomic.AddUint64(&nonceCounter, 1), 16)
}

// SignatureMethod identifies a signature method.
type SignatureMethod int

func (sm SignatureMethod) String() string {
	switch sm {
	case RSASHA1:
		return "RSA-SHA1"
	case HMACSHA1:
		return "HMAC-SHA1"
	case PLAINTEXT:
		return "PLAINTEXT"
	default:
		return "unknown"
	}
}

const (
	HMACSHA1  SignatureMethod = iota // HMAC-SHA1
	RSASHA1                          // RSA-SHA1
	PLAINTEXT                        // Plain text
)

// Credentials represents client, temporary and token credentials.
type Credentials struct {
	Token  string // Also known as consumer key or access token.
	Secret string // Also known as consumer secret or access token secret.
}

// Client represents an OAuth client.
type Client struct {
	// Credentials specifies the client key and secret.
	// Also known as the consumer key and secret
	Credentials Credentials

	// TemporaryCredentialRequestURI is the endpoint used by the client to
	// obtain a set of temporary credentials. Also known as the request token
	// URL.
	TemporaryCredentialRequestURI string

	// ResourceOwnerAuthorizationURI is the endpoint to which the resource
	// owner is redirected to grant authorization. Also known as authorization
	// URL.
	ResourceOwnerAuthorizationURI string

	// TokenRequestURI is the endpoint used by the client to request a set of
	// token credentials using a set of temporary credentials. Also known as
	// access token URL.
	TokenRequestURI string

	// RenewCredentialRequestURI is the endpoint the client uses to
	// request a new set of token credentials using the old set of credentials.
	RenewCredentialRequestURI string

	// TemporaryCredentialsMethod is the HTTP method used by the client to
	// obtain a set of temporary credentials. If this field is the empty
	// string, then POST is used.
	TemporaryCredentialsMethod string

	// TokenCredentailsMethod is the HTTP method used by the client to request
	// a set of token credentials. If this field is the empty string, then POST
	// is used.
	TokenCredentailsMethod string

	// Header specifies optional extra headers for requests.
	Header http.Header

	// SignatureMethod specifies the method for signing a request.
	SignatureMethod SignatureMethod

	// PrivateKey is the private key to use for RSA-SHA1 signatures. This field
	// must be set for RSA-SHA1 signatures and ignored for other signature
	// methods.
	PrivateKey *rsa.PrivateKey
}

type request struct {
	credentials   *Credentials
	method        string
	u             *url.URL
	form          url.Values
	verifier      string
	sessionHandle string
	callbackURL   string
}

var testHook = func(map[string]string) {}

// oauthParams returns the OAuth request parameters for the given credentials,
// method, URL and application params. See
// http://tools.ietf.org/html/rfc5849#section-3.4 for more information about
// signatures.
func (c *Client) oauthParams(r *request) (map[string]string, error) {
	oauthParams := map[string]string{
		"oauth_consumer_key":     c.Credentials.Token,
		"oauth_signature_method": c.SignatureMethod.String(),
		"oauth_version":          "1.0",
	}

	if c.SignatureMethod != PLAINTEXT {
		oauthParams["oauth_timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
		oauthParams["oauth_nonce"] = nonce()
	}

	if r.credentials != nil {
		oauthParams["oauth_token"] = r.credentials.Token
	}

	if r.verifier != "" {
		oauthParams["oauth_verifier"] = r.verifier
	}

	if r.sessionHandle != "" {
		oauthParams["oauth_session_handle"] = r.sessionHandle
	}

	if r.callbackURL != "" {
		oauthParams["oauth_callback"] = r.callbackURL
	}

	testHook(oauthParams)

	var signature string

	switch c.SignatureMethod {
	case HMACSHA1:
		key := encode(c.Credentials.Secret, false)
		key = append(key, '&')
		if r.credentials != nil {
			key = append(key, encode(r.credentials.Secret, false)...)
		}
		h := hmac.New(sha1.New, key)
		writeBaseString(h, r.method, r.u, r.form, oauthParams)
		signature = base64.StdEncoding.EncodeToString(h.Sum(key[:0]))
	case RSASHA1:
		if c.PrivateKey == nil {
			return nil, errors.New("oauth: private key not set")
		}
		h := sha1.New()
		writeBaseString(h, r.method, r.u, r.form, oauthParams)
		rawSignature, err := rsa.SignPKCS1v15(rand.Reader, c.PrivateKey, crypto.SHA1, h.Sum(nil))
		if err != nil {
			return nil, err
		}
		signature = base64.StdEncoding.EncodeToString(rawSignature)
	case PLAINTEXT:
		rawSignature := encode(c.Credentials.Secret, false)
		rawSignature = append(rawSignature, '&')
		if r.credentials != nil {
			rawSignature = append(rawSignature, encode(r.credentials.Secret, false)...)
		}
		signature = string(rawSignature)
	default:
		return nil, errors.New("oauth: unknown signature method")
	}

	oauthParams["oauth_signature"] = signature
	return oauthParams, nil
}

// SignForm adds an OAuth signature to form. The urlStr argument must not
// include a query string.
//
// See http://tools.ietf.org/html/rfc5849#section-3.5.2 for
// information about transmitting OAuth parameters in a request body and
// http://tools.ietf.org/html/rfc5849#section-3.5.2 for information about
// transmitting OAuth parameters in a query string.
func (c *Client) SignForm(credentials *Credentials, method, urlStr string, form url.Values) error {
	u, err := url.Parse(urlStr)
	switch {
	case err != nil:
		return err
	case u.RawQuery != "":
		return errors.New("oauth: urlStr argument to SignForm must not include a query string")
	}
	p, err := c.oauthParams(&request{credentials: credentials, method: method, u: u, form: form})
	if err != nil {
		return err
	}
	for k, v := range p {
		form.Set(k, v)
	}
	return nil
}

// SignParam is deprecated. Use SignForm instead.
func (c *Client) SignParam(credentials *Credentials, method, urlStr string, params url.Values) {
	u, _ := url.Parse(urlStr)
	u.RawQuery = ""
	p, _ := c.oauthParams(&request{credentials: credentials, method: method, u: u, form: params})
	for k, v := range p {
		params.Set(k, v)
	}
}

var oauthKeys = []string{
	"oauth_consumer_key",
	"oauth_nonce",
	"oauth_signature",
	"oauth_signature_method",
	"oauth_timestamp",
	"oauth_token",
	"oauth_version",
	"oauth_callback",
	"oauth_verifier",
	"oauth_session_handle",
}

func (c *Client) authorizationHeader(r *request) (string, error) {
	p, err := c.oauthParams(r)
	if err != nil {
		return "", err
	}
	var h []byte
	// Append parameters in a fixed order to support testing.
	for _, k := range oauthKeys {
		if v, ok := p[k]; ok {
			if h == nil {
				h = []byte(`OAuth `)
			} else {
				h = append(h, ", "...)
			}
			h = append(h, k...)
			h = append(h, `="`...)
			h = append(h, encode(v, false)...)
			h = append(h, '"')
		}
	}
	return string(h), nil
}

// AuthorizationHeader returns the HTTP authorization header value for given
// method, URL and parameters.
//
// AuthorizationHeader is deprecated. Use SetAuthorizationHeader instead.
func (c *Client) AuthorizationHeader(credentials *Credentials, method string, u *url.URL, params url.Values) string {
	// Signing a request can return an error. This method is deprecated because
	// this method does not return an error.
	v, _ := c.authorizationHeader(&request{credentials: credentials, method: method, u: u, form: params})
	return v
}

// SetAuthorizationHeader adds an OAuth signature to a request header.
//
// See http://tools.ietf.org/html/rfc5849#section-3.5.1 for information about
// transmitting OAuth parameters in an HTTP request header.
func (c *Client) SetAuthorizationHeader(header http.Header, credentials *Credentials, method string, u *url.URL, form url.Values) error {
	v, err := c.authorizationHeader(&request{credentials: credentials, method: method, u: u, form: form})
	if err != nil {
		return err
	}
	header.Set("Authorization", v)
	return nil
}

func (c *Client) do(ctx context.Context, urlStr string, r *request) (*http.Response, error) {
	var body io.Reader
	if r.method != http.MethodGet {
		body = strings.NewReader(r.form.Encode())
	}
	req, err := http.NewRequest(r.method, urlStr, body)
	if err != nil {
		return nil, err
	}
	if req.URL.RawQuery != "" {
		return nil, errors.New("oauth: url must not contain a query string")
	}
	for k, v := range c.Header {
		req.Header[k] = v
	}
	r.u = req.URL
	auth, err := c.authorizationHeader(r)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", auth)
	if r.method == http.MethodGet {
		req.URL.RawQuery = r.form.Encode()
	} else {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}
	req = requestWithContext(ctx, req)
	client := contextClient(ctx)
	return client.Do(req)
}

// Get issues a GET to the specified URL with form added as a query string.
func (c *Client) Get(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.GetContext(ctx, credentials, urlStr, form)
}

// GetContext uses Context to perform Get.
func (c *Client) GetContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	return c.do(ctx, urlStr, &request{method: http.MethodGet, credentials: credentials, form: form})
}

// Post issues a POST with the specified form.
func (c *Client) Post(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.PostContext(ctx, credentials, urlStr, form)
}

// PostContext uses Context to perform Post.
func (c *Client) PostContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	return c.do(ctx, urlStr, &request{method: http.MethodPost, credentials: credentials, form: form})
}

// Delete issues a DELETE with the specified form.
func (c *Client) Delete(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.DeleteContext(ctx, credentials, urlStr, form)
}

// DeleteContext uses Context to perform Delete.
func (c *Client) DeleteContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	return c.do(ctx, urlStr, &request{method: http.MethodDelete, credentials: credentials, form: form})
}

// Put issues a PUT with the specified form.
func (c *Client) Put(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.PutContext(ctx, credentials, urlStr, form)
}

// PutContext uses Context to perform Put.
func (c *Client) PutContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) {
	return c.do(ctx, urlStr, &request{method: http.MethodPut, credentials: credentials, form: form})
}

func (c *Client) requestCredentials(ctx context.Context, u string, r *request) (*Credentials, url.Values, error) {
	if r.method == "" {
		r.method = http.MethodPost
	}
	resp, err := c.do(ctx, u, r)
	if err != nil {
		return nil, nil, err
	}
	p, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header,
			Body: p, msg: err.Error()}
	}
	if resp.StatusCode != 200 && resp.StatusCode != 201 {
		return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header,
			Body: p, msg: fmt.Sprintf("OAuth server status %d, %s", resp.StatusCode, string(p))}
	}
	m, err := url.ParseQuery(string(p))
	if err != nil {
		return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header,
			Body: p, msg: err.Error()}
	}
	tokens := m["oauth_token"]
	if len(tokens) == 0 || tokens[0] == "" {
		return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header,
			Body: p, msg: "oauth: token missing from server result"}
	}
	secrets := m["oauth_token_secret"]
	if len(secrets) == 0 { // allow "" as a valid secret.
		return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header,
			Body: p, msg: "oauth: secret missing from server result"}
	}
	return &Credentials{Token: tokens[0], Secret: secrets[0]}, m, nil
}

// RequestTemporaryCredentials requests temporary credentials from the server.
// See http://tools.ietf.org/html/rfc5849#section-2.1 for information about
// temporary credentials.
func (c *Client) RequestTemporaryCredentials(client *http.Client, callbackURL string, additionalParams url.Values) (*Credentials, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.RequestTemporaryCredentialsContext(ctx, callbackURL, additionalParams)
}

// RequestTemporaryCredentialsContext uses Context to perform RequestTemporaryCredentials.
func (c *Client) RequestTemporaryCredentialsContext(ctx context.Context, callbackURL string, additionalParams url.Values) (*Credentials, error) {
	credentials, _, err := c.requestCredentials(ctx, c.TemporaryCredentialRequestURI,
		&request{method: c.TemporaryCredentialsMethod, form: additionalParams, callbackURL: callbackURL})
	return credentials, err
}

// RequestToken requests token credentials from the server. See
// http://tools.ietf.org/html/rfc5849#section-2.3 for information about token
// credentials.
func (c *Client) RequestToken(client *http.Client, temporaryCredentials *Credentials, verifier string) (*Credentials, url.Values, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.RequestTokenContext(ctx, temporaryCredentials, verifier)
}

// RequestTokenContext uses Context to perform RequestToken.
func (c *Client) RequestTokenContext(ctx context.Context, temporaryCredentials *Credentials, verifier string) (*Credentials, url.Values, error) {
	return c.requestCredentials(ctx, c.TokenRequestURI,
		&request{credentials: temporaryCredentials, method: c.TokenCredentailsMethod, verifier: verifier})
}

// RenewRequestCredentials requests new token credentials from the server.
// See http://wiki.oauth.net/w/page/12238549/ScalableOAuth#AccessTokenRenewal
// for information about access token renewal.
func (c *Client) RenewRequestCredentials(client *http.Client, credentials *Credentials, sessionHandle string) (*Credentials, url.Values, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.RenewRequestCredentialsContext(ctx, credentials, sessionHandle)
}

// RenewRequestCredentialsContext uses Context to perform RenewRequestCredentials.
func (c *Client) RenewRequestCredentialsContext(ctx context.Context, credentials *Credentials, sessionHandle string) (*Credentials, url.Values, error) {
	return c.requestCredentials(ctx, c.RenewCredentialRequestURI, &request{credentials: credentials, sessionHandle: sessionHandle})
}

// RequestTokenXAuth requests token credentials from the server using the xAuth protocol.
// See https://dev.twitter.com/oauth/xauth for information on xAuth.
func (c *Client) RequestTokenXAuth(client *http.Client, temporaryCredentials *Credentials, user, password string) (*Credentials, url.Values, error) {
	ctx := context.WithValue(context.Background(), HTTPClient, client)
	return c.RequestTokenXAuthContext(ctx, temporaryCredentials, user, password)
}

// RequestTokenXAuthContext uses Context to perform RequestTokenXAuth.
func (c *Client) RequestTokenXAuthContext(ctx context.Context, temporaryCredentials *Credentials, user, password string) (*Credentials, url.Values, error) {
	form := make(url.Values)
	form.Set("x_auth_mode", "client_auth")
	form.Set("x_auth_username", user)
	form.Set("x_auth_password", password)
	return c.requestCredentials(ctx, c.TokenRequestURI,
		&request{credentials: temporaryCredentials, method: c.TokenCredentailsMethod, form: form})
}

// AuthorizationURL returns the URL for resource owner authorization. See
// http://tools.ietf.org/html/rfc5849#section-2.2 for information about
// resource owner authorization.
func (c *Client) AuthorizationURL(temporaryCredentials *Credentials, additionalParams url.Values) string {
	params := make(url.Values)
	for k, vs := range additionalParams {
		params[k] = vs
	}
	params.Set("oauth_token", temporaryCredentials.Token)
	return c.ResourceOwnerAuthorizationURI + "?" + params.Encode()
}

// HTTPClient is the context key to use with context's
// WithValue function to associate an *http.Client value with a context.
var HTTPClient contextKey

type contextKey struct{}

func contextClient(ctx context.Context) *http.Client {
	if ctx != nil {
		if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok && hc != nil {
			return hc
		}
	}
	return http.DefaultClient
}

// RequestCredentialsError is an error containing
// response information when requesting credentials.
type RequestCredentialsError struct {
	StatusCode int
	Header     http.Header
	Body       []byte
	msg        string
}

func (e RequestCredentialsError) Error() string {
	return e.msg
}
