336 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
package swift
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// Auth defines the operations needed to authenticate with swift
 | 
						|
//
 | 
						|
// This encapsulates the different authentication schemes in use
 | 
						|
type Authenticator interface {
 | 
						|
	// Request creates an http.Request for the auth - return nil if not needed
 | 
						|
	Request(*Connection) (*http.Request, error)
 | 
						|
	// Response parses the http.Response
 | 
						|
	Response(resp *http.Response) error
 | 
						|
	// The public storage URL - set Internal to true to read
 | 
						|
	// internal/service net URL
 | 
						|
	StorageUrl(Internal bool) string
 | 
						|
	// The access token
 | 
						|
	Token() string
 | 
						|
	// The CDN url if available
 | 
						|
	CdnUrl() string
 | 
						|
}
 | 
						|
 | 
						|
// Expireser is an optional interface to read the expiration time of the token
 | 
						|
type Expireser interface {
 | 
						|
	Expires() time.Time
 | 
						|
}
 | 
						|
 | 
						|
type CustomEndpointAuthenticator interface {
 | 
						|
	StorageUrlForEndpoint(endpointType EndpointType) string
 | 
						|
}
 | 
						|
 | 
						|
type EndpointType string
 | 
						|
 | 
						|
const (
 | 
						|
	// Use public URL as storage URL
 | 
						|
	EndpointTypePublic = EndpointType("public")
 | 
						|
 | 
						|
	// Use internal URL as storage URL
 | 
						|
	EndpointTypeInternal = EndpointType("internal")
 | 
						|
 | 
						|
	// Use admin URL as storage URL
 | 
						|
	EndpointTypeAdmin = EndpointType("admin")
 | 
						|
)
 | 
						|
 | 
						|
// newAuth - create a new Authenticator from the AuthUrl
 | 
						|
//
 | 
						|
// A hint for AuthVersion can be provided
 | 
						|
func newAuth(c *Connection) (Authenticator, error) {
 | 
						|
	AuthVersion := c.AuthVersion
 | 
						|
	if AuthVersion == 0 {
 | 
						|
		if strings.Contains(c.AuthUrl, "v3") {
 | 
						|
			AuthVersion = 3
 | 
						|
		} else if strings.Contains(c.AuthUrl, "v2") {
 | 
						|
			AuthVersion = 2
 | 
						|
		} else if strings.Contains(c.AuthUrl, "v1") {
 | 
						|
			AuthVersion = 1
 | 
						|
		} else {
 | 
						|
			return nil, newErrorf(500, "Can't find AuthVersion in AuthUrl - set explicitly")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	switch AuthVersion {
 | 
						|
	case 1:
 | 
						|
		return &v1Auth{}, nil
 | 
						|
	case 2:
 | 
						|
		return &v2Auth{
 | 
						|
			// Guess as to whether using API key or
 | 
						|
			// password it will try both eventually so
 | 
						|
			// this is just an optimization.
 | 
						|
			useApiKey: len(c.ApiKey) >= 32,
 | 
						|
		}, nil
 | 
						|
	case 3:
 | 
						|
		return &v3Auth{}, nil
 | 
						|
	}
 | 
						|
	return nil, newErrorf(500, "Auth Version %d not supported", AuthVersion)
 | 
						|
}
 | 
						|
 | 
						|
// ------------------------------------------------------------
 | 
						|
 | 
						|
// v1 auth
 | 
						|
type v1Auth struct {
 | 
						|
	Headers http.Header // V1 auth: the authentication headers so extensions can access them
 | 
						|
}
 | 
						|
 | 
						|
// v1 Authentication - make request
 | 
						|
func (auth *v1Auth) Request(c *Connection) (*http.Request, error) {
 | 
						|
	req, err := http.NewRequest("GET", c.AuthUrl, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	req.Header.Set("User-Agent", c.UserAgent)
 | 
						|
	req.Header.Set("X-Auth-Key", c.ApiKey)
 | 
						|
	req.Header.Set("X-Auth-User", c.UserName)
 | 
						|
	return req, nil
 | 
						|
}
 | 
						|
 | 
						|
// v1 Authentication - read response
 | 
						|
func (auth *v1Auth) Response(resp *http.Response) error {
 | 
						|
	auth.Headers = resp.Header
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// v1 Authentication - read storage url
 | 
						|
func (auth *v1Auth) StorageUrl(Internal bool) string {
 | 
						|
	storageUrl := auth.Headers.Get("X-Storage-Url")
 | 
						|
	if Internal {
 | 
						|
		newUrl, err := url.Parse(storageUrl)
 | 
						|
		if err != nil {
 | 
						|
			return storageUrl
 | 
						|
		}
 | 
						|
		newUrl.Host = "snet-" + newUrl.Host
 | 
						|
		storageUrl = newUrl.String()
 | 
						|
	}
 | 
						|
	return storageUrl
 | 
						|
}
 | 
						|
 | 
						|
// v1 Authentication - read auth token
 | 
						|
func (auth *v1Auth) Token() string {
 | 
						|
	return auth.Headers.Get("X-Auth-Token")
 | 
						|
}
 | 
						|
 | 
						|
// v1 Authentication - read cdn url
 | 
						|
func (auth *v1Auth) CdnUrl() string {
 | 
						|
	return auth.Headers.Get("X-CDN-Management-Url")
 | 
						|
}
 | 
						|
 | 
						|
// ------------------------------------------------------------
 | 
						|
 | 
						|
// v2 Authentication
 | 
						|
type v2Auth struct {
 | 
						|
	Auth        *v2AuthResponse
 | 
						|
	Region      string
 | 
						|
	useApiKey   bool // if set will use API key not Password
 | 
						|
	useApiKeyOk bool // if set won't change useApiKey any more
 | 
						|
	notFirst    bool // set after first run
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - make request
 | 
						|
func (auth *v2Auth) Request(c *Connection) (*http.Request, error) {
 | 
						|
	auth.Region = c.Region
 | 
						|
	// Toggle useApiKey if not first run and not OK yet
 | 
						|
	if auth.notFirst && !auth.useApiKeyOk {
 | 
						|
		auth.useApiKey = !auth.useApiKey
 | 
						|
	}
 | 
						|
	auth.notFirst = true
 | 
						|
	// Create a V2 auth request for the body of the connection
 | 
						|
	var v2i interface{}
 | 
						|
	if !auth.useApiKey {
 | 
						|
		// Normal swift authentication
 | 
						|
		v2 := v2AuthRequest{}
 | 
						|
		v2.Auth.PasswordCredentials.UserName = c.UserName
 | 
						|
		v2.Auth.PasswordCredentials.Password = c.ApiKey
 | 
						|
		v2.Auth.Tenant = c.Tenant
 | 
						|
		v2.Auth.TenantId = c.TenantId
 | 
						|
		v2i = v2
 | 
						|
	} else {
 | 
						|
		// Rackspace special with API Key
 | 
						|
		v2 := v2AuthRequestRackspace{}
 | 
						|
		v2.Auth.ApiKeyCredentials.UserName = c.UserName
 | 
						|
		v2.Auth.ApiKeyCredentials.ApiKey = c.ApiKey
 | 
						|
		v2.Auth.Tenant = c.Tenant
 | 
						|
		v2.Auth.TenantId = c.TenantId
 | 
						|
		v2i = v2
 | 
						|
	}
 | 
						|
	body, err := json.Marshal(v2i)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	url := c.AuthUrl
 | 
						|
	if !strings.HasSuffix(url, "/") {
 | 
						|
		url += "/"
 | 
						|
	}
 | 
						|
	url += "tokens"
 | 
						|
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	req.Header.Set("Content-Type", "application/json")
 | 
						|
	req.Header.Set("User-Agent", c.UserAgent)
 | 
						|
	return req, nil
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read response
 | 
						|
func (auth *v2Auth) Response(resp *http.Response) error {
 | 
						|
	auth.Auth = new(v2AuthResponse)
 | 
						|
	err := readJson(resp, auth.Auth)
 | 
						|
	// If successfully read Auth then no need to toggle useApiKey any more
 | 
						|
	if err == nil {
 | 
						|
		auth.useApiKeyOk = true
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// Finds the Endpoint Url of "type" from the v2AuthResponse using the
 | 
						|
// Region if set or defaulting to the first one if not
 | 
						|
//
 | 
						|
// Returns "" if not found
 | 
						|
func (auth *v2Auth) endpointUrl(Type string, endpointType EndpointType) string {
 | 
						|
	for _, catalog := range auth.Auth.Access.ServiceCatalog {
 | 
						|
		if catalog.Type == Type {
 | 
						|
			for _, endpoint := range catalog.Endpoints {
 | 
						|
				if auth.Region == "" || (auth.Region == endpoint.Region) {
 | 
						|
					switch endpointType {
 | 
						|
					case EndpointTypeInternal:
 | 
						|
						return endpoint.InternalUrl
 | 
						|
					case EndpointTypePublic:
 | 
						|
						return endpoint.PublicUrl
 | 
						|
					case EndpointTypeAdmin:
 | 
						|
						return endpoint.AdminUrl
 | 
						|
					default:
 | 
						|
						return ""
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read storage url
 | 
						|
//
 | 
						|
// If Internal is true then it reads the private (internal / service
 | 
						|
// net) URL.
 | 
						|
func (auth *v2Auth) StorageUrl(Internal bool) string {
 | 
						|
	endpointType := EndpointTypePublic
 | 
						|
	if Internal {
 | 
						|
		endpointType = EndpointTypeInternal
 | 
						|
	}
 | 
						|
	return auth.StorageUrlForEndpoint(endpointType)
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read storage url
 | 
						|
//
 | 
						|
// Use the indicated endpointType to choose a URL.
 | 
						|
func (auth *v2Auth) StorageUrlForEndpoint(endpointType EndpointType) string {
 | 
						|
	return auth.endpointUrl("object-store", endpointType)
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read auth token
 | 
						|
func (auth *v2Auth) Token() string {
 | 
						|
	return auth.Auth.Access.Token.Id
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read expires
 | 
						|
func (auth *v2Auth) Expires() time.Time {
 | 
						|
	t, err := time.Parse(time.RFC3339, auth.Auth.Access.Token.Expires)
 | 
						|
	if err != nil {
 | 
						|
		return time.Time{} // return Zero if not parsed
 | 
						|
	}
 | 
						|
	return t
 | 
						|
}
 | 
						|
 | 
						|
// v2 Authentication - read cdn url
 | 
						|
func (auth *v2Auth) CdnUrl() string {
 | 
						|
	return auth.endpointUrl("rax:object-cdn", EndpointTypePublic)
 | 
						|
}
 | 
						|
 | 
						|
// ------------------------------------------------------------
 | 
						|
 | 
						|
// V2 Authentication request
 | 
						|
//
 | 
						|
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
 | 
						|
// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html
 | 
						|
// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html
 | 
						|
type v2AuthRequest struct {
 | 
						|
	Auth struct {
 | 
						|
		PasswordCredentials struct {
 | 
						|
			UserName string `json:"username"`
 | 
						|
			Password string `json:"password"`
 | 
						|
		} `json:"passwordCredentials"`
 | 
						|
		Tenant   string `json:"tenantName,omitempty"`
 | 
						|
		TenantId string `json:"tenantId,omitempty"`
 | 
						|
	} `json:"auth"`
 | 
						|
}
 | 
						|
 | 
						|
// V2 Authentication request - Rackspace variant
 | 
						|
//
 | 
						|
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
 | 
						|
// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html
 | 
						|
// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html
 | 
						|
type v2AuthRequestRackspace struct {
 | 
						|
	Auth struct {
 | 
						|
		ApiKeyCredentials struct {
 | 
						|
			UserName string `json:"username"`
 | 
						|
			ApiKey   string `json:"apiKey"`
 | 
						|
		} `json:"RAX-KSKEY:apiKeyCredentials"`
 | 
						|
		Tenant   string `json:"tenantName,omitempty"`
 | 
						|
		TenantId string `json:"tenantId,omitempty"`
 | 
						|
	} `json:"auth"`
 | 
						|
}
 | 
						|
 | 
						|
// V2 Authentication reply
 | 
						|
//
 | 
						|
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
 | 
						|
// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html
 | 
						|
// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html
 | 
						|
type v2AuthResponse struct {
 | 
						|
	Access struct {
 | 
						|
		ServiceCatalog []struct {
 | 
						|
			Endpoints []struct {
 | 
						|
				InternalUrl string
 | 
						|
				PublicUrl   string
 | 
						|
				AdminUrl    string
 | 
						|
				Region      string
 | 
						|
				TenantId    string
 | 
						|
			}
 | 
						|
			Name string
 | 
						|
			Type string
 | 
						|
		}
 | 
						|
		Token struct {
 | 
						|
			Expires string
 | 
						|
			Id      string
 | 
						|
			Tenant  struct {
 | 
						|
				Id   string
 | 
						|
				Name string
 | 
						|
			}
 | 
						|
		}
 | 
						|
		User struct {
 | 
						|
			DefaultRegion string `json:"RAX-AUTH:defaultRegion"`
 | 
						|
			Id            string
 | 
						|
			Name          string
 | 
						|
			Roles         []struct {
 | 
						|
				Description string
 | 
						|
				Id          string
 | 
						|
				Name        string
 | 
						|
				TenantId    string
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |