Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
Merge dc273fc into 985a863
Browse files Browse the repository at this point in the history
  • Loading branch information
codevulture committed Jul 6, 2016
2 parents 985a863 + dc273fc commit 104ad23
Show file tree
Hide file tree
Showing 15 changed files with 641 additions and 8 deletions.
2 changes: 1 addition & 1 deletion auth_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type AuthOptions struct {
// possible. This setting defaults to false.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool
Expand Down
2 changes: 1 addition & 1 deletion openstack/blockstorage/v1/apiversions/urls.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package apiversions

import (
"strings"
"net/url"
"strings"

"github.com/rackspace/gophercloud"
)
Expand Down
1 change: 1 addition & 0 deletions openstack/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
v3Options := options

var scope *tokens3.Scope

if options.TenantID != "" {
scope = &tokens3.Scope{
ProjectID: options.TenantID,
Expand Down
23 changes: 23 additions & 0 deletions openstack/identity/v3/extensions/auth_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package extensions

import (
"github.com/rackspace/gophercloud"
)

/*
AuthOptions stores information needed to authenticate to an OpenStack cluster.
Pass one to a provider's AuthenticatedClient function to authenticate and obtain a
ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
*/
type AuthOptions struct {
//Populate fields in gophercloud AuthOptions also.
*gophercloud.AuthOptions

// Trust allows users to authenticate with Trust ID,
// The TrustID field is to be used with Identity V3 API only.
// ID of the Trust.
TrustID string
}
3 changes: 3 additions & 0 deletions openstack/identity/v3/extensions/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package extensions provides information and interaction with the
// different extensions available for the OpenStack Identity v3 service.
package extensions
47 changes: 47 additions & 0 deletions openstack/identity/v3/extensions/tokens/endpoint_location.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tokens

import (
"fmt"

"github.com/rackspace/gophercloud"
)

// TrustV3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
func TrustV3EndpointURL(catalog *ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
// Name if provided, and Region if provided.
var endpoints = make([]Endpoint, 0, 1)
for _, entry := range catalog.Entries {
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
for _, endpoint := range entry.Endpoints {
if opts.Availability != gophercloud.AvailabilityAdmin &&
opts.Availability != gophercloud.AvailabilityPublic &&
opts.Availability != gophercloud.AvailabilityInternal {
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
}
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
(opts.Region == "" || endpoint.Region == opts.Region) {
endpoints = append(endpoints, endpoint)
}
}
}
}

// Report an error if the options were ambiguous.
if len(endpoints) > 1 {
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
}

// Extract the URL from the matching Endpoint.
for _, endpoint := range endpoints {
return gophercloud.NormalizeURL(endpoint.URL), nil
}

// Report an error if there were no matching endpoints.
return "", gophercloud.ErrEndpointNotFound
}
229 changes: 229 additions & 0 deletions openstack/identity/v3/extensions/tokens/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package tokens

import (
"net/http"

"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
"github.com/rackspace/gophercloud/openstack/identity/v3/extensions"
)

// Scope allows a created token to be limited to a specific domain or project.
type Scope struct {
TrustID string
}

func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
return map[string]string{
"X-Subject-Token": subjectToken,
}
}

// Create authenticates and either generates a new token, or changes the Scope of an existing token.
func Create(c *gophercloud.ServiceClient, options extensions.AuthOptions, scope *Scope) CreateResult {
type domainReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
}

type v3TrustReq struct {
ID *string `json:"id,omitempty"`
}

type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password"`
Domain *domainReq `json:"domain,omitempty"`
}

type passwordReq struct {
User userReq `json:"user"`
}

type tokenReq struct {
ID string `json:"id"`
}

type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
}

type scopeReq struct {
Trust *v3TrustReq `json:"OS-TRUST:trust,omitempty"`
}

type authReq struct {
Identity identityReq `json:"identity"`
Scope *scopeReq `json:"scope,omitempty"`
}

type request struct {
Auth authReq `json:"auth"`
}

// Populate the request structure based on the provided arguments. Create and return an error
// if insufficient or incompatible information is present.
var req request

// Test first for unrecognized arguments.
if options.APIKey != "" {
return createErr(tokens.ErrAPIKeyProvided)
}
if options.TenantID != "" {
return createErr(tokens.ErrTenantIDProvided)
}
if options.TenantName != "" {
return createErr(tokens.ErrTenantNameProvided)
}

if options.Password == "" {
if c.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
if options.Username != "" {
return createErr(tokens.ErrUsernameWithToken)
}
if options.UserID != "" {
return createErr(tokens.ErrUserIDWithToken)
}
if options.DomainID != "" {
return createErr(tokens.ErrDomainIDWithToken)
}
if options.DomainName != "" {
return createErr(tokens.ErrDomainNameWithToken)
}

// Configure the request for Token authentication.
req.Auth.Identity.Methods = []string{"token"}
req.Auth.Identity.Token = &tokenReq{
ID: c.TokenID,
}
} else {
// If no password or token ID are available, authentication can't continue.
return createErr(tokens.ErrMissingPassword)
}
} else {
// Password authentication.
req.Auth.Identity.Methods = []string{"password"}

// At least one of Username and UserID must be specified.
if options.Username == "" && options.UserID == "" {
return createErr(tokens.ErrUsernameOrUserID)
}

if options.Username != "" {
// If Username is provided, UserID may not be provided.
if options.UserID != "" {
return createErr(tokens.ErrUsernameOrUserID)
}

// Either DomainID or DomainName must also be specified.
if options.DomainID == "" && options.DomainName == "" {
return createErr(tokens.ErrDomainIDOrDomainName)
}

if options.DomainID != "" {
if options.DomainName != "" {
return createErr(tokens.ErrDomainIDOrDomainName)
}

// Configure the request for Username and Password authentication with a DomainID.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &options.Username,
Password: options.Password,
Domain: &domainReq{ID: &options.DomainID},
},
}
}

if options.DomainName != "" {
// Configure the request for Username and Password authentication with a DomainName.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &options.Username,
Password: options.Password,
Domain: &domainReq{Name: &options.DomainName},
},
}
}
}

if options.UserID != "" {
// If UserID is specified, neither DomainID nor DomainName may be.
if options.DomainID != "" {
return createErr(tokens.ErrDomainIDWithUserID)
}
if options.DomainName != "" {
return createErr(tokens.ErrDomainNameWithUserID)
}

// Configure the request for UserID and Password authentication.
req.Auth.Identity.Password = &passwordReq{
User: userReq{ID: &options.UserID, Password: options.Password},
}
}

}

// Add a "scope" element if a Scope has been provided.
if scope != nil {
if scope.TrustID != "" {
// TrustID provided.
req.Auth.Scope = &scopeReq{
Trust: &v3TrustReq{ID: &scope.TrustID},
}
} else {
return createErr(tokens.ErrScopeEmpty)
}
}

var result CreateResult
var response *http.Response
response, result.Err = c.Post(tokenURL(c), req, &result.Body, nil)
if result.Err != nil {
return result
}
result.Header = response.Header
return result
}

// Get validates and retrieves information about another token.
func Get(c *gophercloud.ServiceClient, token string) GetResult {
var result GetResult
var response *http.Response
response, result.Err = c.Get(tokenURL(c), &result.Body, &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{200, 203},
})
if result.Err != nil {
return result
}
result.Header = response.Header
return result
}

// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
response, err := c.Request("HEAD", tokenURL(c), gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404},
})
if err != nil {
return false, err
}

return response.StatusCode == 204, nil
}

// Revoke immediately makes specified token invalid.
func Revoke(c *gophercloud.ServiceClient, token string) RevokeResult {
var res RevokeResult
_, res.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
})
return res
}
Loading

0 comments on commit 104ad23

Please sign in to comment.