|
Packit |
63bb0d |
package oauth1
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"crypto/hmac"
|
|
Packit |
63bb0d |
"crypto/sha1"
|
|
Packit |
63bb0d |
"encoding/base64"
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"io/ioutil"
|
|
Packit |
63bb0d |
"math/rand"
|
|
Packit |
63bb0d |
"net/url"
|
|
Packit |
63bb0d |
"sort"
|
|
Packit |
63bb0d |
"strconv"
|
|
Packit |
63bb0d |
"strings"
|
|
Packit |
63bb0d |
"time"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
"github.com/gophercloud/gophercloud"
|
|
Packit |
63bb0d |
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
|
Packit |
63bb0d |
"github.com/gophercloud/gophercloud/pagination"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Type SignatureMethod is a OAuth1 SignatureMethod type.
|
|
Packit |
63bb0d |
type SignatureMethod string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
// HMACSHA1 is a recommended OAuth1 signature method.
|
|
Packit |
63bb0d |
HMACSHA1 SignatureMethod = "HMAC-SHA1"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// PLAINTEXT signature method is not recommended to be used in
|
|
Packit |
63bb0d |
// production environment.
|
|
Packit |
63bb0d |
PLAINTEXT SignatureMethod = "PLAINTEXT"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuth1TokenContentType is a supported content type for an OAuth1
|
|
Packit |
63bb0d |
// token.
|
|
Packit |
63bb0d |
OAuth1TokenContentType = "application/x-www-form-urlencoded"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AuthOptions represents options for authenticating a user using OAuth1 tokens.
|
|
Packit |
63bb0d |
type AuthOptions struct {
|
|
Packit |
63bb0d |
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
|
Packit |
63bb0d |
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
|
Packit |
63bb0d |
// an OAuth1 request signature.
|
|
Packit |
63bb0d |
OAuthConsumerSecret string `required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthToken is the OAuth1 Request Token.
|
|
Packit |
63bb0d |
OAuthToken string `q:"oauth_token" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
|
Packit |
63bb0d |
// an OAuth1 request signature.
|
|
Packit |
63bb0d |
OAuthTokenSecret string `required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
|
Packit |
63bb0d |
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
|
Packit |
63bb0d |
// "PLAINTEXT" is not recommended for production usage.
|
|
Packit |
63bb0d |
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
|
Packit |
63bb0d |
// timestamp will be used.
|
|
Packit |
63bb0d |
OAuthTimestamp *time.Time
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
|
Packit |
63bb0d |
// uniquely generated for each request. Will be generated automatically
|
|
Packit |
63bb0d |
// when it is not set.
|
|
Packit |
63bb0d |
OAuthNonce string `q:"oauth_nonce"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AllowReauth allows Gophercloud to re-authenticate automatically
|
|
Packit |
63bb0d |
// if/when your token expires.
|
|
Packit |
63bb0d |
AllowReauth bool
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToTokenV3HeadersMap builds the headers required for an OAuth1-based create
|
|
Packit |
63bb0d |
// request.
|
|
Packit |
63bb0d |
func (opts AuthOptions) ToTokenV3HeadersMap(headerOpts map[string]interface{}) (map[string]string, error) {
|
|
Packit |
63bb0d |
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
method := headerOpts["method"].(string)
|
|
Packit |
63bb0d |
u := headerOpts["url"].(string)
|
|
Packit |
63bb0d |
stringToSign := buildStringToSign(method, u, q.Query())
|
|
Packit |
63bb0d |
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
authHeader := buildAuthHeader(q.Query(), signature)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
headers := map[string]string{
|
|
Packit |
63bb0d |
"Authorization": authHeader,
|
|
Packit |
63bb0d |
"X-Auth-Token": "",
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return headers, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToTokenV3ScopeMap allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
|
Packit |
63bb0d |
// interface.
|
|
Packit |
63bb0d |
func (opts AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
|
Packit |
63bb0d |
return nil, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CanReauth allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
|
Packit |
63bb0d |
// interface.
|
|
Packit |
63bb0d |
func (opts AuthOptions) CanReauth() bool {
|
|
Packit |
63bb0d |
return opts.AllowReauth
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToTokenV3CreateMap builds a create request body.
|
|
Packit |
63bb0d |
func (opts AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
|
|
Packit |
63bb0d |
// identityReq defines the "identity" portion of an OAuth1-based authentication
|
|
Packit |
63bb0d |
// create request body.
|
|
Packit |
63bb0d |
type identityReq struct {
|
|
Packit |
63bb0d |
Methods []string `json:"methods"`
|
|
Packit |
63bb0d |
OAuth1 struct{} `json:"oauth1"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// authReq defines the "auth" portion of an OAuth1-based authentication
|
|
Packit |
63bb0d |
// create request body.
|
|
Packit |
63bb0d |
type authReq struct {
|
|
Packit |
63bb0d |
Identity identityReq `json:"identity"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// oauth1Request defines how an OAuth1-based authentication create
|
|
Packit |
63bb0d |
// request body looks.
|
|
Packit |
63bb0d |
type oauth1Request struct {
|
|
Packit |
63bb0d |
Auth authReq `json:"auth"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var req oauth1Request
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
req.Auth.Identity.Methods = []string{"oauth1"}
|
|
Packit |
63bb0d |
return gophercloud.BuildRequestBody(req, "")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create authenticates and either generates a new OpenStack token from an
|
|
Packit |
63bb0d |
// OAuth1 token.
|
|
Packit |
63bb0d |
func Create(client *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
|
Packit |
63bb0d |
b, err := opts.ToTokenV3CreateMap(nil)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
headerOpts := map[string]interface{}{
|
|
Packit |
63bb0d |
"method": "POST",
|
|
Packit |
63bb0d |
"url": authURL(client),
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
h, err := opts.ToTokenV3HeadersMap(headerOpts)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
resp, err := client.Post(authURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
MoreHeaders: h,
|
|
Packit |
63bb0d |
OkCodes: []int{201},
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CreateConsumerOptsBuilder allows extensions to add additional parameters to
|
|
Packit |
63bb0d |
// the CreateConsumer request.
|
|
Packit |
63bb0d |
type CreateConsumerOptsBuilder interface {
|
|
Packit |
63bb0d |
ToOAuth1CreateConsumerMap() (map[string]interface{}, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CreateConsumerOpts provides options used to create a new Consumer.
|
|
Packit |
63bb0d |
type CreateConsumerOpts struct {
|
|
Packit |
63bb0d |
// Description is the consumer description.
|
|
Packit |
63bb0d |
Description string `json:"description"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToOAuth1CreateConsumerMap formats a CreateConsumerOpts into a create request.
|
|
Packit |
63bb0d |
func (opts CreateConsumerOpts) ToOAuth1CreateConsumerMap() (map[string]interface{}, error) {
|
|
Packit |
63bb0d |
return gophercloud.BuildRequestBody(opts, "consumer")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create creates a new Consumer.
|
|
Packit |
63bb0d |
func CreateConsumer(client *gophercloud.ServiceClient, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) {
|
|
Packit |
63bb0d |
b, err := opts.ToOAuth1CreateConsumerMap()
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
resp, err := client.Post(consumersURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
OkCodes: []int{201},
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Delete deletes a Consumer.
|
|
Packit |
63bb0d |
func DeleteConsumer(client *gophercloud.ServiceClient, id string) (r DeleteConsumerResult) {
|
|
Packit |
63bb0d |
resp, err := client.Delete(consumerURL(client, id), nil)
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// List enumerates Consumers.
|
|
Packit |
63bb0d |
func ListConsumers(client *gophercloud.ServiceClient) pagination.Pager {
|
|
Packit |
63bb0d |
return pagination.NewPager(client, consumersURL(client), func(r pagination.PageResult) pagination.Page {
|
|
Packit |
63bb0d |
return ConsumersPage{pagination.LinkedPageBase{PageResult: r}}
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// GetConsumer retrieves details on a single Consumer by ID.
|
|
Packit |
63bb0d |
func GetConsumer(client *gophercloud.ServiceClient, id string) (r GetConsumerResult) {
|
|
Packit |
63bb0d |
resp, err := client.Get(consumerURL(client, id), &r.Body, nil)
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// UpdateConsumerOpts provides options used to update a consumer.
|
|
Packit |
63bb0d |
type UpdateConsumerOpts struct {
|
|
Packit |
63bb0d |
// Description is the consumer description.
|
|
Packit |
63bb0d |
Description string `json:"description"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToOAuth1UpdateConsumerMap formats an UpdateConsumerOpts into a consumer update
|
|
Packit |
63bb0d |
// request.
|
|
Packit |
63bb0d |
func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]interface{}, error) {
|
|
Packit |
63bb0d |
return gophercloud.BuildRequestBody(opts, "consumer")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// UpdateConsumer updates an existing Consumer.
|
|
Packit |
63bb0d |
func UpdateConsumer(client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) {
|
|
Packit |
63bb0d |
b, err := opts.ToOAuth1UpdateConsumerMap()
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
resp, err := client.Patch(consumerURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
OkCodes: []int{200},
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// RequestTokenOptsBuilder allows extensions to add additional parameters to the
|
|
Packit |
63bb0d |
// RequestToken request.
|
|
Packit |
63bb0d |
type RequestTokenOptsBuilder interface {
|
|
Packit |
63bb0d |
ToOAuth1RequestTokenHeaders(string, string) (map[string]string, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// RequestTokenOpts provides options used to get a consumer unauthorized
|
|
Packit |
63bb0d |
// request token.
|
|
Packit |
63bb0d |
type RequestTokenOpts struct {
|
|
Packit |
63bb0d |
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
|
Packit |
63bb0d |
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
|
Packit |
63bb0d |
// an OAuth1 request signature.
|
|
Packit |
63bb0d |
OAuthConsumerSecret string `required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
|
Packit |
63bb0d |
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
|
Packit |
63bb0d |
// "PLAINTEXT" is not recommended for production usage.
|
|
Packit |
63bb0d |
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
|
Packit |
63bb0d |
// timestamp will be used.
|
|
Packit |
63bb0d |
OAuthTimestamp *time.Time
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
|
Packit |
63bb0d |
// uniquely generated for each request. Will be generated automatically
|
|
Packit |
63bb0d |
// when it is not set.
|
|
Packit |
63bb0d |
OAuthNonce string `q:"oauth_nonce"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// RequestedProjectID is a Project ID a consumer user requested an
|
|
Packit |
63bb0d |
// access to.
|
|
Packit |
63bb0d |
RequestedProjectID string `h:"Requested-Project-Id"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToOAuth1RequestTokenHeaders formats a RequestTokenOpts into a map of request
|
|
Packit |
63bb0d |
// headers.
|
|
Packit |
63bb0d |
func (opts RequestTokenOpts) ToOAuth1RequestTokenHeaders(method, u string) (map[string]string, error) {
|
|
Packit |
63bb0d |
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "oob")
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
h, err := gophercloud.BuildHeaders(opts)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
signatureKeys := []string{opts.OAuthConsumerSecret}
|
|
Packit |
63bb0d |
stringToSign := buildStringToSign(method, u, q.Query())
|
|
Packit |
63bb0d |
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
|
Packit |
63bb0d |
authHeader := buildAuthHeader(q.Query(), signature)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
h["Authorization"] = authHeader
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return h, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// RequestToken requests an unauthorized OAuth1 Token.
|
|
Packit |
63bb0d |
func RequestToken(client *gophercloud.ServiceClient, opts RequestTokenOptsBuilder) (r TokenResult) {
|
|
Packit |
63bb0d |
h, err := opts.ToOAuth1RequestTokenHeaders("POST", requestTokenURL(client))
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
resp, err := client.Post(requestTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
MoreHeaders: h,
|
|
Packit |
63bb0d |
OkCodes: []int{201},
|
|
Packit |
63bb0d |
KeepResponseBody: true,
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
if r.Err != nil {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
defer resp.Body.Close()
|
|
Packit |
63bb0d |
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
|
Packit |
63bb0d |
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AuthorizeTokenOptsBuilder allows extensions to add additional parameters to
|
|
Packit |
63bb0d |
// the AuthorizeToken request.
|
|
Packit |
63bb0d |
type AuthorizeTokenOptsBuilder interface {
|
|
Packit |
63bb0d |
ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AuthorizeTokenOpts provides options used to authorize a request token.
|
|
Packit |
63bb0d |
type AuthorizeTokenOpts struct {
|
|
Packit |
63bb0d |
Roles []Role `json:"roles"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Role is a struct representing a role object in a AuthorizeTokenOpts struct.
|
|
Packit |
63bb0d |
type Role struct {
|
|
Packit |
63bb0d |
ID string `json:"id,omitempty"`
|
|
Packit |
63bb0d |
Name string `json:"name,omitempty"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToOAuth1AuthorizeTokenMap formats an AuthorizeTokenOpts into an authorize token
|
|
Packit |
63bb0d |
// request.
|
|
Packit |
63bb0d |
func (opts AuthorizeTokenOpts) ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error) {
|
|
Packit |
63bb0d |
for _, r := range opts.Roles {
|
|
Packit |
63bb0d |
if r == (Role{}) {
|
|
Packit |
63bb0d |
return nil, fmt.Errorf("role must not be empty")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return gophercloud.BuildRequestBody(opts, "")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AuthorizeToken authorizes an unauthorized consumer token.
|
|
Packit |
63bb0d |
func AuthorizeToken(client *gophercloud.ServiceClient, id string, opts AuthorizeTokenOptsBuilder) (r AuthorizeTokenResult) {
|
|
Packit |
63bb0d |
b, err := opts.ToOAuth1AuthorizeTokenMap()
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
resp, err := client.Put(authorizeTokenURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
OkCodes: []int{200},
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CreateAccessTokenOptsBuilder allows extensions to add additional parameters
|
|
Packit |
63bb0d |
// to the CreateAccessToken request.
|
|
Packit |
63bb0d |
type CreateAccessTokenOptsBuilder interface {
|
|
Packit |
63bb0d |
ToOAuth1CreateAccessTokenHeaders(string, string) (map[string]string, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CreateAccessTokenOpts provides options used to create an OAuth1 token.
|
|
Packit |
63bb0d |
type CreateAccessTokenOpts struct {
|
|
Packit |
63bb0d |
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
|
Packit |
63bb0d |
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
|
Packit |
63bb0d |
// an OAuth1 request signature.
|
|
Packit |
63bb0d |
OAuthConsumerSecret string `required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthToken is the OAuth1 Request Token.
|
|
Packit |
63bb0d |
OAuthToken string `q:"oauth_token" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
|
Packit |
63bb0d |
// an OAuth1 request signature.
|
|
Packit |
63bb0d |
OAuthTokenSecret string `required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthVerifier is the OAuth1 verification code.
|
|
Packit |
63bb0d |
OAuthVerifier string `q:"oauth_verifier" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
|
Packit |
63bb0d |
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
|
Packit |
63bb0d |
// "PLAINTEXT" is not recommended for production usage.
|
|
Packit |
63bb0d |
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
|
Packit |
63bb0d |
// timestamp will be used.
|
|
Packit |
63bb0d |
OAuthTimestamp *time.Time
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
|
Packit |
63bb0d |
// uniquely generated for each request. Will be generated automatically
|
|
Packit |
63bb0d |
// when it is not set.
|
|
Packit |
63bb0d |
OAuthNonce string `q:"oauth_nonce"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ToOAuth1CreateAccessTokenHeaders formats a CreateAccessTokenOpts into a map of
|
|
Packit |
63bb0d |
// request headers.
|
|
Packit |
63bb0d |
func (opts CreateAccessTokenOpts) ToOAuth1CreateAccessTokenHeaders(method, u string) (map[string]string, error) {
|
|
Packit |
63bb0d |
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
|
Packit |
63bb0d |
stringToSign := buildStringToSign(method, u, q.Query())
|
|
Packit |
63bb0d |
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
|
Packit |
63bb0d |
authHeader := buildAuthHeader(q.Query(), signature)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
headers := map[string]string{
|
|
Packit |
63bb0d |
"Authorization": authHeader,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return headers, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// CreateAccessToken creates a new OAuth1 Access Token
|
|
Packit |
63bb0d |
func CreateAccessToken(client *gophercloud.ServiceClient, opts CreateAccessTokenOptsBuilder) (r TokenResult) {
|
|
Packit |
63bb0d |
h, err := opts.ToOAuth1CreateAccessTokenHeaders("POST", createAccessTokenURL(client))
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
r.Err = err
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
resp, err := client.Post(createAccessTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
|
Packit |
63bb0d |
MoreHeaders: h,
|
|
Packit |
63bb0d |
OkCodes: []int{201},
|
|
Packit |
63bb0d |
KeepResponseBody: true,
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
if r.Err != nil {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
defer resp.Body.Close()
|
|
Packit |
63bb0d |
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
|
Packit |
63bb0d |
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// GetAccessToken retrieves details on a single OAuth1 access token by an ID.
|
|
Packit |
63bb0d |
func GetAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r GetAccessTokenResult) {
|
|
Packit |
63bb0d |
resp, err := client.Get(userAccessTokenURL(client, userID, id), &r.Body, nil)
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// RevokeAccessToken revokes an OAuth1 access token.
|
|
Packit |
63bb0d |
func RevokeAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r RevokeAccessTokenResult) {
|
|
Packit |
63bb0d |
resp, err := client.Delete(userAccessTokenURL(client, userID, id), nil)
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ListAccessTokens enumerates authorized access tokens.
|
|
Packit |
63bb0d |
func ListAccessTokens(client *gophercloud.ServiceClient, userID string) pagination.Pager {
|
|
Packit |
63bb0d |
url := userAccessTokensURL(client, userID)
|
|
Packit |
63bb0d |
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
|
Packit |
63bb0d |
return AccessTokensPage{pagination.LinkedPageBase{PageResult: r}}
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ListAccessTokenRoles enumerates authorized access token roles.
|
|
Packit |
63bb0d |
func ListAccessTokenRoles(client *gophercloud.ServiceClient, userID string, id string) pagination.Pager {
|
|
Packit |
63bb0d |
url := userAccessTokenRolesURL(client, userID, id)
|
|
Packit |
63bb0d |
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
|
Packit |
63bb0d |
return AccessTokenRolesPage{pagination.LinkedPageBase{PageResult: r}}
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// GetAccessTokenRole retrieves details on a single OAuth1 access token role by
|
|
Packit |
63bb0d |
// an ID.
|
|
Packit |
63bb0d |
func GetAccessTokenRole(client *gophercloud.ServiceClient, userID string, id string, roleID string) (r GetAccessTokenRoleResult) {
|
|
Packit |
63bb0d |
resp, err := client.Get(userAccessTokenRoleURL(client, userID, id, roleID), &r.Body, nil)
|
|
Packit |
63bb0d |
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// The following are small helper functions used to help build the signature.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// buildOAuth1QueryString builds a URLEncoded parameters string specific for
|
|
Packit |
63bb0d |
// OAuth1-based requests.
|
|
Packit |
63bb0d |
func buildOAuth1QueryString(opts interface{}, timestamp *time.Time, callback string) (*url.URL, error) {
|
|
Packit |
63bb0d |
q, err := gophercloud.BuildQueryString(opts)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
query := q.Query()
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if timestamp != nil {
|
|
Packit |
63bb0d |
// use provided timestamp
|
|
Packit |
63bb0d |
query.Set("oauth_timestamp", strconv.FormatInt(timestamp.Unix(), 10))
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
// use current timestamp
|
|
Packit |
63bb0d |
query.Set("oauth_timestamp", strconv.FormatInt(time.Now().UTC().Unix(), 10))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if query.Get("oauth_nonce") == "" {
|
|
Packit |
63bb0d |
// when nonce is not set, generate a random one
|
|
Packit |
63bb0d |
query.Set("oauth_nonce", strconv.FormatInt(rand.Int63(), 10)+query.Get("oauth_timestamp"))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if callback != "" {
|
|
Packit |
63bb0d |
query.Set("oauth_callback", callback)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
query.Set("oauth_version", "1.0")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return &url.URL{RawQuery: query.Encode()}, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// buildStringToSign builds a string to be signed.
|
|
Packit |
63bb0d |
func buildStringToSign(method string, u string, query url.Values) []byte {
|
|
Packit |
63bb0d |
parsedURL, _ := url.Parse(u)
|
|
Packit |
63bb0d |
p := parsedURL.Port()
|
|
Packit |
63bb0d |
s := parsedURL.Scheme
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Default scheme port must be stripped
|
|
Packit |
63bb0d |
if s == "http" && p == "80" || s == "https" && p == "443" {
|
|
Packit |
63bb0d |
parsedURL.Host = strings.TrimSuffix(parsedURL.Host, ":"+p)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Ensure that URL doesn't contain queries
|
|
Packit |
63bb0d |
parsedURL.RawQuery = ""
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
v := strings.Join(
|
|
Packit |
63bb0d |
[]string{method, url.QueryEscape(parsedURL.String()), url.QueryEscape(query.Encode())}, "&")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return []byte(v)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// signString signs a string using an OAuth1 signature method.
|
|
Packit |
63bb0d |
func signString(signatureMethod SignatureMethod, strToSign []byte, signatureKeys []string) string {
|
|
Packit |
63bb0d |
var key []byte
|
|
Packit |
63bb0d |
for i, k := range signatureKeys {
|
|
Packit |
63bb0d |
key = append(key, []byte(url.QueryEscape(k))...)
|
|
Packit |
63bb0d |
if i == 0 {
|
|
Packit |
63bb0d |
key = append(key, '&')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var signedString string
|
|
Packit |
63bb0d |
switch signatureMethod {
|
|
Packit |
63bb0d |
case PLAINTEXT:
|
|
Packit |
63bb0d |
signedString = string(key)
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
h := hmac.New(sha1.New, key)
|
|
Packit |
63bb0d |
h.Write(strToSign)
|
|
Packit |
63bb0d |
signedString = base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return signedString
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// buildAuthHeader generates an OAuth1 Authorization header with a signature
|
|
Packit |
63bb0d |
// calculated using an OAuth1 signature method.
|
|
Packit |
63bb0d |
func buildAuthHeader(query url.Values, signature string) string {
|
|
Packit |
63bb0d |
var authHeader []string
|
|
Packit |
63bb0d |
var keys []string
|
|
Packit |
63bb0d |
for k := range query {
|
|
Packit |
63bb0d |
keys = append(keys, k)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
sort.Strings(keys)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for _, k := range keys {
|
|
Packit |
63bb0d |
for _, v := range query[k] {
|
|
Packit |
63bb0d |
authHeader = append(authHeader, fmt.Sprintf("%s=%q", k, url.QueryEscape(v)))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
authHeader = append(authHeader, fmt.Sprintf("oauth_signature=%q", signature))
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return "OAuth " + strings.Join(authHeader, ", ")
|
|
Packit |
63bb0d |
}
|