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

Fix keystone v3 token auth #528

Merged
merged 3 commits into from
Jun 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions openstack/auth_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ var nilOptions = gophercloud.AuthOptions{}
// environment variables, respectively, remain undefined. See the AuthOptions() function for more details.
var (
ErrNoAuthURL = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.")
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD needs to be set.")
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME, OS_USERID, or OS_TOKEN needs to be set.")
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_TOKEN needs to be set.")
)

// AuthOptions fills out an identity.AuthOptions structure with the settings found on the various OpenStack
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
// AuthOptionsFromEnv fills out an AuthOptions structure from the environment
// variables: OS_AUTH_URL, OS_USERNAME, OS_USERID, OS_PASSWORD, OS_TENANT_ID,
// OS_TENANT_NAME, OS_DOMAIN_ID, OS_DOMAIN_NAME, OS_TOKEN. It checks that
// (1) OS_AUTH_URL is set, (2) OS_USERNAME, OS_USERID, or OS_TOKEN is set,
// (3) OS_PASSWORD or OS_TOKEN is set.
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME")
Expand All @@ -30,16 +31,17 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
tokenID := os.Getenv("OS_TOKEN")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me understand the use-case for this? Since tokens are presumably ephemeral, why would we want to load them from an environment variable? And if a user has set OS_TOKEN, what then when the token inevitably expires?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://specs.openstack.org/openstack/keystone-specs/api/v3/identity-api-v3-os-oauth1-ext.html says that OAuth tokens

Provide the ability for identity users to delegate roles to third party consumers via the OAuth 1.0a specification. Requires v3.0+ of the Identity API. An OAuth-derived token will provide a means of acting on behalf of the authorizing user.

They can be revoked by the user or expire (which is optional), after which they are meant to no longer provide authorization to perform on behalf of the user.


if authURL == "" {
return nilOptions, ErrNoAuthURL
}

if username == "" && userID == "" {
if username == "" && userID == "" && tokenID == "" {
return nilOptions, ErrNoUsername
}

if password == "" {
if password == "" && tokenID == "" {
return nilOptions, ErrNoPassword
}

Expand All @@ -52,6 +54,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
TokenID: tokenID,
}

return ao, nil
Expand Down
4 changes: 2 additions & 2 deletions openstack/identity/v3/tokens/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ var (
// It may also indicate that both a DomainID and a DomainName were provided at once.
ErrDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName to authenticate by Username")

// ErrMissingPassword indicates that no password was provided and no token is available.
ErrMissingPassword = errors.New("You must provide a password to authenticate")
// ErrMissingPassword indicates that no password and no token were provided and no token is available.
ErrMissingPassword = errors.New("You must provide a password or a token to authenticate")

// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
ErrScopeDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName in a Scope with ProjectName")
Expand Down
9 changes: 3 additions & 6 deletions openstack/identity/v3/tokens/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope
}

if options.Password == "" {
if options.TokenID != "" {
c.TokenID = options.TokenID
}
if c.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
Expand All @@ -93,12 +96,6 @@ func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope
if options.UserID != "" {
return createErr(ErrUserIDWithToken)
}
if options.DomainID != "" {
return createErr(ErrDomainIDWithToken)
}
if options.DomainName != "" {
return createErr(ErrDomainNameWithToken)
}

// Configure the request for Token authentication.
req.Auth.Identity.Methods = []string{"token"}
Expand Down
65 changes: 61 additions & 4 deletions openstack/identity/v3/tokens/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ func TestCreateTokenID(t *testing.T) {
`)
}

func TestCreateNewTokenID(t *testing.T) {
authTokenPost(t, gophercloud.AuthOptions{TokenID: "asdf"}, nil, `
{
"auth": {
"identity": {
"methods": ["token"],
"token": {
"id": "asdf"
}
}
}
}
`)
}

func TestCreateProjectIDScope(t *testing.T) {
options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"}
scope := &Scope{ProjectID: "123456"}
Expand Down Expand Up @@ -296,12 +311,54 @@ func TestCreateFailureTokenIDUserID(t *testing.T) {
authTokenPostErr(t, gophercloud.AuthOptions{UserID: "something"}, nil, true, ErrUserIDWithToken)
}

func TestCreateFailureTokenIDDomainID(t *testing.T) {
authTokenPostErr(t, gophercloud.AuthOptions{DomainID: "something"}, nil, true, ErrDomainIDWithToken)
func TestCreateTokenIDDomainID(t *testing.T) {
scope := &Scope{ProjectName: "world-domination", DomainID: "1000"}
authTokenPost(t, gophercloud.AuthOptions{DomainID: "something"}, scope, `
{
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": "12345abcdef"
}
},
"scope": {
"project": {
"domain": {
"id": "1000"
},
"name": "world-domination"
}
}
}
}`)
}

func TestCreateFailureTokenIDDomainName(t *testing.T) {
authTokenPostErr(t, gophercloud.AuthOptions{DomainName: "something"}, nil, true, ErrDomainNameWithToken)
func TestCreateTokenIDDomainName(t *testing.T) {
scope := &Scope{ProjectName: "world-domination", DomainName: "evil-plans"}
authTokenPost(t, gophercloud.AuthOptions{DomainName: "something"}, scope, `
{
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": "12345abcdef"
}
},
"scope": {
"project": {
"domain": {
"name": "evil-plans"
},
"name": "world-domination"
}
}
}
}`)
}

func TestCreateFailureMissingUser(t *testing.T) {
Expand Down