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