restic/vendor/github.com/ncw/swift/auth.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
}
}
}
}