no naked returns in go; fix auth v3 unit tests
diff --git a/openstack/identity/v2/extensions/admin/roles/requests.go b/openstack/identity/v2/extensions/admin/roles/requests.go
index ba19fd4..4d27972 100644
--- a/openstack/identity/v2/extensions/admin/roles/requests.go
+++ b/openstack/identity/v2/extensions/admin/roles/requests.go
@@ -18,6 +18,7 @@
// ID is a required argument.
func AddUser(client *gophercloud.ServiceClient, tenantID, userID, roleID string) (r UserRoleResult) {
_, r.Err = client.Put(userRoleURL(client, tenantID, userID, roleID), nil, nil, nil)
+ return
}
// DeleteUser is the operation responsible for deleting a particular role
@@ -25,4 +26,5 @@
// tenant ID is a required argument.
func DeleteUser(client *gophercloud.ServiceClient, tenantID, userID, roleID string) (r UserRoleResult) {
_, r.Err = client.Delete(userRoleURL(client, tenantID, userID, roleID), nil)
+ return
}
diff --git a/openstack/identity/v2/tokens/requests.go b/openstack/identity/v2/tokens/requests.go
index 12c0f17..73e0b91 100644
--- a/openstack/identity/v2/tokens/requests.go
+++ b/openstack/identity/v2/tokens/requests.go
@@ -2,6 +2,32 @@
import "github.com/gophercloud/gophercloud"
+type PasswordCredentialsV2 struct {
+ Username string `json:"username" required:"true"`
+ Password string `json:"password" required:"true"`
+}
+
+type TokenCredentialsV2 struct {
+ ID string `json:"id,omitempty" required:"true"`
+}
+
+// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
+// interface.
+type AuthOptionsV2 struct {
+ PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
+
+ // The TenantID and TenantName fields are optional for the Identity V2 API.
+ // Some providers allow you to specify a TenantName instead of the TenantId.
+ // Some require both. Your provider's authentication policies will determine
+ // how these fields influence authentication.
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+
+ // TokenCredentials allows users to authenticate (possibly as another user) with an
+ // authentication token ID.
+ TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
+}
+
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
@@ -9,6 +35,38 @@
ToTokenV2CreateMap() (map[string]interface{}, error)
}
+// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
+// interface.
+type AuthOptions struct {
+ gophercloud.AuthOptions
+}
+
+// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
+// interface in the v2 tokens package
+func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
+ v2Opts := AuthOptionsV2{
+ TenantID: opts.TenantID,
+ TenantName: opts.TenantName,
+ }
+
+ if opts.Password != "" {
+ v2Opts.PasswordCredentials = &PasswordCredentialsV2{
+ Username: opts.Username,
+ Password: opts.Password,
+ }
+ } else {
+ v2Opts.TokenCredentials = &TokenCredentialsV2{
+ ID: opts.TokenID,
+ }
+ }
+
+ b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
// Create authenticates to the identity service and attempts to acquire a Token.
// If successful, the CreateResult
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
@@ -22,6 +80,7 @@
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
+ return
}
// Get validates and retrieves information for user's token.
@@ -29,4 +88,5 @@
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
+ return
}
diff --git a/openstack/identity/v2/tokens/requests_test.go b/openstack/identity/v2/tokens/requests_test.go
index d25c2d7..0cbb00c 100644
--- a/openstack/identity/v2/tokens/requests_test.go
+++ b/openstack/identity/v2/tokens/requests_test.go
@@ -1,7 +1,6 @@
package tokens
import (
- "reflect"
"testing"
"github.com/gophercloud/gophercloud"
@@ -13,7 +12,8 @@
th.SetupHTTP()
defer th.TeardownHTTP()
HandleTokenPost(t, requestJSON)
- return Create(client.ServiceClient(), options)
+
+ return Create(client.ServiceClient(), AuthOptions{options})
}
func tokenPostErr(t *testing.T, options gophercloud.AuthOptions, expectedErr error) {
@@ -21,30 +21,15 @@
defer th.TeardownHTTP()
HandleTokenPost(t, "")
- actualErr := Create(client.ServiceClient(), options).Err
- th.CheckDeepEquals(t, reflect.TypeOf(expectedErr), reflect.TypeOf(actualErr))
-}
-
-func TestCreateWithToken(t *testing.T) {
- options := gophercloud.AuthOptions{
- TokenID: "cbc36478b0bd8e67e89469c7749d4127",
- }
-
- IsSuccessful(t, tokenPost(t, options, `
- {
- "auth": {
- "token": {
- "id": "cbc36478b0bd8e67e89469c7749d4127"
- }
- }
- }
- `))
+ actualErr := Create(client.ServiceClient(), AuthOptions{options}).Err
+ th.CheckDeepEquals(t, expectedErr, actualErr)
}
func TestCreateWithPassword(t *testing.T) {
- options := gophercloud.AuthOptions{}
- options.Username = "me"
- options.Password = "swordfish"
+ options := gophercloud.AuthOptions{
+ Username: "me",
+ Password: "swordfish",
+ }
IsSuccessful(t, tokenPost(t, options, `
{
@@ -59,10 +44,11 @@
}
func TestCreateTokenWithTenantID(t *testing.T) {
- options := gophercloud.AuthOptions{}
- options.Username = "me"
- options.Password = "opensesame"
- options.TenantID = "fc394f2ab2df4114bde39905f800dc57"
+ options := gophercloud.AuthOptions{
+ Username: "me",
+ Password: "opensesame",
+ TenantID: "fc394f2ab2df4114bde39905f800dc57",
+ }
IsSuccessful(t, tokenPost(t, options, `
{
@@ -78,10 +64,11 @@
}
func TestCreateTokenWithTenantName(t *testing.T) {
- options := gophercloud.AuthOptions{}
- options.Username = "me"
- options.Password = "opensesame"
- options.TenantName = "demo"
+ options := gophercloud.AuthOptions{
+ Username: "me",
+ Password: "opensesame",
+ TenantName: "demo",
+ }
IsSuccessful(t, tokenPost(t, options, `
{
@@ -97,29 +84,18 @@
}
func TestRequireUsername(t *testing.T) {
- options := gophercloud.AuthOptions{}
- options.Password = "thing"
+ options := gophercloud.AuthOptions{
+ Password: "thing",
+ }
- expected := gophercloud.ErrMissingInput{}
- expected.Argument = "tokens.AuthOptions.Username/tokens.AuthOptions.TokenID"
- expected.Info = "You must provide either username/password or tenantID/token values."
- tokenPostErr(t, options, expected)
+ tokenPostErr(t, options, gophercloud.ErrMissingInput{Argument: "Username"})
}
-func TestRequirePassword(t *testing.T) {
- options := gophercloud.AuthOptions{}
- options.Username = "me"
-
- expected := gophercloud.ErrMissingInput{}
- expected.Argument = "tokens.AuthOptions.Password"
- tokenPostErr(t, options, expected)
-}
-
-func tokenGet(t *testing.T, tokenID string) GetResult {
+func tokenGet(t *testing.T, tokenId string) GetResult {
th.SetupHTTP()
defer th.TeardownHTTP()
- HandleTokenGet(t, tokenID)
- return Get(client.ServiceClient(), tokenID)
+ HandleTokenGet(t, tokenId)
+ return Get(client.ServiceClient(), tokenId)
}
func TestGetWithToken(t *testing.T) {
diff --git a/openstack/identity/v2/users/requests.go b/openstack/identity/v2/users/requests.go
index 90f7c72..ef77d39 100644
--- a/openstack/identity/v2/users/requests.go
+++ b/openstack/identity/v2/users/requests.go
@@ -57,11 +57,13 @@
_, r.Err = client.Post(rootURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
+ return
}
// Get requests details on a single user, either by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(ResourceURL(client, id), &r.Body, nil)
+ return
}
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
@@ -87,11 +89,13 @@
_, r.Err = client.Put(ResourceURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
+ return
}
// Delete is the operation responsible for permanently deleting an API user.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(ResourceURL(client, id), nil)
+ return
}
// ListRoles lists the existing roles that can be assigned to users.
diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go
index 30ceacf..fc44365 100644
--- a/openstack/identity/v3/endpoints/requests.go
+++ b/openstack/identity/v3/endpoints/requests.go
@@ -31,6 +31,7 @@
return
}
_, r.Err = client.Post(listURL(client), &b, &r.Body, nil)
+ return
}
type ListOptsBuilder interface {
@@ -91,9 +92,11 @@
return
}
_, r.Err = client.Patch(endpointURL(client, endpointID), &b, &r.Body, nil)
+ return
}
// Delete removes an endpoint from the service catalog.
func Delete(client *gophercloud.ServiceClient, endpointID string) (r DeleteResult) {
_, r.Err = client.Delete(endpointURL(client, endpointID), nil)
+ return
}
diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go
index 81c8960..bb7bb04 100644
--- a/openstack/identity/v3/services/requests.go
+++ b/openstack/identity/v3/services/requests.go
@@ -9,6 +9,7 @@
func Create(client *gophercloud.ServiceClient, serviceType string) (r CreateResult) {
b := map[string]string{"type": serviceType}
_, r.Err = client.Post(listURL(client), b, &r.Body, nil)
+ return
}
type ListOptsBuilder interface {
@@ -45,16 +46,19 @@
// Get returns additional information about a service, given its ID.
func Get(client *gophercloud.ServiceClient, serviceID string) (r GetResult) {
_, r.Err = client.Get(serviceURL(client, serviceID), &r.Body, nil)
+ return
}
// Update changes the service type of an existing service.
func Update(client *gophercloud.ServiceClient, serviceID string, serviceType string) (r UpdateResult) {
b := map[string]string{"type": serviceType}
_, r.Err = client.Patch(serviceURL(client, serviceID), &b, &r.Body, nil)
+ return
}
// Delete removes an existing service.
// It either deletes all associated endpoints, or fails until all endpoints are deleted.
func Delete(client *gophercloud.ServiceClient, serviceID string) (r DeleteResult) {
_, r.Err = client.Delete(serviceURL(client, serviceID), nil)
+ return
}
diff --git a/openstack/identity/v3/tokens/errors.go b/openstack/identity/v3/tokens/errors.go
index 4476109..9cc1d59 100644
--- a/openstack/identity/v3/tokens/errors.go
+++ b/openstack/identity/v3/tokens/errors.go
@@ -1,72 +1,139 @@
package tokens
import (
- "errors"
"fmt"
+
+ "github.com/gophercloud/gophercloud"
)
-func unacceptedAttributeErr(attribute string) error {
- return fmt.Errorf("The base Identity V3 API does not accept authentication by %s", attribute)
+func unacceptedAttributeErr(attribute string) string {
+ return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
}
-func redundantWithTokenErr(attribute string) error {
- return fmt.Errorf("%s may not be provided when authenticating with a TokenID", attribute)
+func redundantWithTokenErr(attribute string) string {
+ return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
}
-func redundantWithUserID(attribute string) error {
- return fmt.Errorf("%s may not be provided when authenticating with a UserID", attribute)
+func redundantWithUserID(attribute string) string {
+ return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
}
-var (
- // ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
- ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
+// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
+type ErrAPIKeyProvided struct{ gophercloud.BaseError }
- // ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
- ErrTenantIDProvided = unacceptedAttributeErr("TenantID")
+func (e ErrAPIKeyProvided) Error() string {
+ return unacceptedAttributeErr("APIKey")
+}
- // ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
- ErrTenantNameProvided = unacceptedAttributeErr("TenantName")
+// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
+type ErrTenantIDProvided struct{ gophercloud.BaseError }
- // ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
- ErrUsernameWithToken = redundantWithTokenErr("Username")
+func (e ErrTenantIDProvided) Error() string {
+ return unacceptedAttributeErr("TenantID")
+}
- // ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
- ErrUserIDWithToken = redundantWithTokenErr("UserID")
+// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
+type ErrTenantNameProvided struct{ gophercloud.BaseError }
- // ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
- ErrDomainIDWithToken = redundantWithTokenErr("DomainID")
+func (e ErrTenantNameProvided) Error() string {
+ return unacceptedAttributeErr("TenantName")
+}
- // ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
- ErrDomainNameWithToken = redundantWithTokenErr("DomainName")
+// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
+type ErrUsernameWithToken struct{ gophercloud.BaseError }
- // ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
- ErrUsernameOrUserID = errors.New("Exactly one of Username and UserID must be provided for password authentication")
+func (e ErrUsernameWithToken) Error() string {
+ return redundantWithTokenErr("Username")
+}
- // ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
- ErrDomainIDWithUserID = redundantWithUserID("DomainID")
+// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
+type ErrUserIDWithToken struct{ gophercloud.BaseError }
- // ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
- ErrDomainNameWithUserID = redundantWithUserID("DomainName")
+func (e ErrUserIDWithToken) Error() string {
+ return redundantWithTokenErr("UserID")
+}
- // ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
- // 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")
+// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
+type ErrDomainIDWithToken struct{ gophercloud.BaseError }
- // ErrMissingPassword indicates that no password was provided and no token is available.
- ErrMissingPassword = errors.New("You must provide a password to authenticate")
+func (e ErrDomainIDWithToken) Error() string {
+ return redundantWithTokenErr("DomainID")
+}
- // 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")
+// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
+type ErrDomainNameWithToken struct{ gophercloud.BaseError }
- // ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
- ErrScopeProjectIDOrProjectName = errors.New("You must provide at most one of ProjectID or ProjectName in a Scope")
+func (e ErrDomainNameWithToken) Error() string {
+ return redundantWithTokenErr("DomainName")
+}
- // ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
- ErrScopeProjectIDAlone = errors.New("ProjectID must be supplied alone in a Scope")
+// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
+type ErrUsernameOrUserID struct{ gophercloud.BaseError }
- // ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
- ErrScopeDomainName = errors.New("DomainName must be supplied with a ProjectName or ProjectID in a Scope.")
+func (e ErrUsernameOrUserID) Error() string {
+ return "Exactly one of Username and UserID must be provided for password authentication"
+}
- // ErrScopeEmpty indicates that no credentials were provided in a Scope.
- ErrScopeEmpty = errors.New("You must provide either a Project or Domain in a Scope")
-)
+// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
+type ErrDomainIDWithUserID struct{ gophercloud.BaseError }
+
+func (e ErrDomainIDWithUserID) Error() string {
+ return redundantWithUserID("DomainID")
+}
+
+// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
+type ErrDomainNameWithUserID struct{ gophercloud.BaseError }
+
+func (e ErrDomainNameWithUserID) Error() string {
+ return redundantWithUserID("DomainName")
+}
+
+// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
+// It may also indicate that both a DomainID and a DomainName were provided at once.
+type ErrDomainIDOrDomainName struct{ gophercloud.BaseError }
+
+func (e ErrDomainIDOrDomainName) Error() string {
+ return "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.
+type ErrMissingPassword struct{ gophercloud.BaseError }
+
+func (e ErrMissingPassword) Error() string {
+ return "You must provide a password to authenticate"
+}
+
+// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
+type ErrScopeDomainIDOrDomainName struct{ gophercloud.BaseError }
+
+func (e ErrScopeDomainIDOrDomainName) Error() string {
+ return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
+}
+
+// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
+type ErrScopeProjectIDOrProjectName struct{ gophercloud.BaseError }
+
+func (e ErrScopeProjectIDOrProjectName) Error() string {
+ return "You must provide at most one of ProjectID or ProjectName in a Scope"
+}
+
+// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
+type ErrScopeProjectIDAlone struct{ gophercloud.BaseError }
+
+func (e ErrScopeProjectIDAlone) Error() string {
+ return "ProjectID must be supplied alone in a Scope"
+}
+
+// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
+type ErrScopeDomainName struct{ gophercloud.BaseError }
+
+func (e ErrScopeDomainName) Error() string {
+ return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
+}
+
+// ErrScopeEmpty indicates that no credentials were provided in a Scope.
+type ErrScopeEmpty struct{ gophercloud.BaseError }
+
+func (e ErrScopeEmpty) Error() string {
+ return "You must provide either a Project or Domain in a Scope"
+}
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index 6978186..12930f9 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -1,16 +1,275 @@
package tokens
-import (
- "net/http"
+import "github.com/gophercloud/gophercloud"
- "github.com/gophercloud/gophercloud"
-)
+// Scope allows a created token to be limited to a specific domain or project.
+type Scope struct {
+ ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"`
+ ProjectName string `json:"scope.project.name,omitempty"`
+ DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"`
+ DomainName string `json:"scope.project.id,omitempty"`
+}
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
- ToTokenV3CreateMap(*gophercloud.ScopeOptsV3) (map[string]interface{}, error)
+ ToTokenV3CreateMap(*Scope) (map[string]interface{}, error)
+}
+
+type AuthOptions struct {
+ // IdentityEndpoint specifies the HTTP endpoint that is required to work with
+ // the Identity API of the appropriate version. While it's ultimately needed by
+ // all of the identity services, it will often be populated by a provider-level
+ // function.
+ IdentityEndpoint string `json:"-"`
+
+ // Username is required if using Identity V2 API. Consult with your provider's
+ // control panel to discover your account's username. In Identity V3, either
+ // UserID or a combination of Username and DomainID or DomainName are needed.
+ Username string `json:"username,omitempty"`
+ UserID string `json:"id,omitempty"`
+
+ Password string `json:"password,omitempty"`
+
+ // At most one of DomainID and DomainName must be provided if using Username
+ // with Identity V3. Otherwise, either are optional.
+ DomainID string `json:"id,omitempty"`
+ DomainName string `json:"name,omitempty"`
+
+ // The TenantID and TenantName fields are optional for the Identity V2 API.
+ // Some providers allow you to specify a TenantName instead of the TenantId.
+ // Some require both. Your provider's authentication policies will determine
+ // how these fields influence authentication.
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+
+ // AllowReauth should be set to true if you grant permission for Gophercloud to
+ // cache your credentials in memory, and to allow Gophercloud to attempt to
+ // re-authenticate automatically if/when your token expires. If you set it to
+ // false, it will not cache these settings, but re-authentication will not be
+ // possible. This setting defaults to false.
+ AllowReauth bool `json:"-"`
+
+ // TokenID allows users to authenticate (possibly as another user) with an
+ // authentication token ID.
+ TokenID string
+}
+
+func (opts AuthOptions) ToTokenV3CreateMap(scope *Scope) (map[string]interface{}, error) {
+ type domainReq struct {
+ ID *string `json:"id,omitempty"`
+ Name *string `json:"name,omitempty"`
+ }
+
+ type projectReq struct {
+ Domain *domainReq `json:"domain,omitempty"`
+ Name *string `json:"name,omitempty"`
+ 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 {
+ Domain *domainReq `json:"domain,omitempty"`
+ Project *projectReq `json:"project,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 opts.TenantID != "" {
+ return nil, ErrTenantIDProvided{}
+ }
+ if opts.TenantName != "" {
+ return nil, ErrTenantNameProvided{}
+ }
+
+ if opts.Password == "" {
+ if opts.TokenID != "" {
+ // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
+ // parameters.
+ if opts.Username != "" {
+ return nil, ErrUsernameWithToken{}
+ }
+ if opts.UserID != "" {
+ return nil, ErrUserIDWithToken{}
+ }
+ if opts.DomainID != "" {
+ return nil, ErrDomainIDWithToken{}
+ }
+ if opts.DomainName != "" {
+ return nil, ErrDomainNameWithToken{}
+ }
+
+ // Configure the request for Token authentication.
+ req.Auth.Identity.Methods = []string{"token"}
+ req.Auth.Identity.Token = &tokenReq{
+ ID: opts.TokenID,
+ }
+ } else {
+ // If no password or token ID are available, authentication can't continue.
+ return nil, ErrMissingPassword{}
+ }
+ } else {
+ // Password authentication.
+ req.Auth.Identity.Methods = []string{"password"}
+
+ // At least one of Username and UserID must be specified.
+ if opts.Username == "" && opts.UserID == "" {
+ return nil, ErrUsernameOrUserID{}
+ }
+
+ if opts.Username != "" {
+ // If Username is provided, UserID may not be provided.
+ if opts.UserID != "" {
+ return nil, ErrUsernameOrUserID{}
+ }
+
+ // Either DomainID or DomainName must also be specified.
+ if opts.DomainID == "" && opts.DomainName == "" {
+ return nil, ErrDomainIDOrDomainName{}
+ }
+
+ if opts.DomainID != "" {
+ if opts.DomainName != "" {
+ return nil, ErrDomainIDOrDomainName{}
+ }
+
+ // Configure the request for Username and Password authentication with a DomainID.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{
+ Name: &opts.Username,
+ Password: opts.Password,
+ Domain: &domainReq{ID: &opts.DomainID},
+ },
+ }
+ }
+
+ if opts.DomainName != "" {
+ // Configure the request for Username and Password authentication with a DomainName.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{
+ Name: &opts.Username,
+ Password: opts.Password,
+ Domain: &domainReq{Name: &opts.DomainName},
+ },
+ }
+ }
+ }
+
+ if opts.UserID != "" {
+ // If UserID is specified, neither DomainID nor DomainName may be.
+ if opts.DomainID != "" {
+ return nil, ErrDomainIDWithUserID{}
+ }
+ if opts.DomainName != "" {
+ return nil, ErrDomainNameWithUserID{}
+ }
+
+ // Configure the request for UserID and Password authentication.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{ID: &opts.UserID, Password: opts.Password},
+ }
+ }
+ }
+
+ // Add a "scope" element if a Scope has been provided.
+ if scope != nil {
+ if scope.ProjectName != "" {
+ // ProjectName provided: either DomainID or DomainName must also be supplied.
+ // ProjectID may not be supplied.
+ if scope.DomainID == "" && scope.DomainName == "" {
+ return nil, ErrScopeDomainIDOrDomainName{}
+ }
+ if scope.ProjectID != "" {
+ return nil, ErrScopeProjectIDOrProjectName{}
+ }
+
+ if scope.DomainID != "" {
+ // ProjectName + DomainID
+ req.Auth.Scope = &scopeReq{
+ Project: &projectReq{
+ Name: &scope.ProjectName,
+ Domain: &domainReq{ID: &scope.DomainID},
+ },
+ }
+ }
+
+ if scope.DomainName != "" {
+ // ProjectName + DomainName
+ req.Auth.Scope = &scopeReq{
+ Project: &projectReq{
+ Name: &scope.ProjectName,
+ Domain: &domainReq{Name: &scope.DomainName},
+ },
+ }
+ }
+ } else if scope.ProjectID != "" {
+ // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
+ if scope.DomainID != "" {
+ return nil, ErrScopeProjectIDAlone{}
+ }
+ if scope.DomainName != "" {
+ return nil, ErrScopeProjectIDAlone{}
+ }
+
+ // ProjectID
+ req.Auth.Scope = &scopeReq{
+ Project: &projectReq{ID: &scope.ProjectID},
+ }
+ } else if scope.DomainID != "" {
+ // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
+ if scope.DomainName != "" {
+ return nil, ErrScopeDomainIDOrDomainName{}
+ }
+
+ // DomainID
+ req.Auth.Scope = &scopeReq{
+ Domain: &domainReq{ID: &scope.DomainID},
+ }
+ } else if scope.DomainName != "" {
+ return nil, ErrScopeDomainName{}
+ } else {
+ return nil, ErrScopeEmpty{}
+ }
+ }
+
+ b, err2 := gophercloud.BuildRequestBody(req, "")
+ if err2 != nil {
+ return nil, err2
+ }
+ return b, nil
}
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
@@ -20,34 +279,36 @@
}
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
-func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder, scopeOpts *gophercloud.ScopeOptsV3) (r CreateResult) {
+func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder, scopeOpts *Scope) (r CreateResult) {
b, err := opts.ToTokenV3CreateMap(scopeOpts)
if err != nil {
r.Err = err
return
}
- var resp *http.Response
- resp, r.Err = c.Post(tokenURL(c), b, &r.Body, nil)
+ resp, err := c.Post(tokenURL(c), b, &r.Body, nil)
if resp != nil {
+ r.Err = err
r.Header = resp.Header
}
+ return
}
// Get validates and retrieves information about another token.
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
- var resp *http.Response
- resp, r.Err = c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
+ resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{200, 203},
})
if resp != nil {
+ r.Err = err
r.Header = resp.Header
}
+ return
}
// 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{
+ resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404},
})
@@ -55,7 +316,7 @@
return false, err
}
- return response.StatusCode == 204, nil
+ return resp.StatusCode == 204, nil
}
// Revoke immediately makes specified token invalid.
@@ -63,4 +324,5 @@
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
})
+ return
}
diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go
index a39a6f4..faa79e0 100644
--- a/openstack/identity/v3/tokens/requests_test.go
+++ b/openstack/identity/v3/tokens/requests_test.go
@@ -11,15 +11,13 @@
)
// authTokenPost verifies that providing certain AuthOptions and Scope results in an expected JSON structure.
-func authTokenPost(t *testing.T, options AuthOptionsBuilder, scope *gophercloud.ScopeOptsV3, requestJSON string) {
+func authTokenPost(t *testing.T, options AuthOptions, scope *Scope, requestJSON string) {
testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()
client := gophercloud.ServiceClient{
- ProviderClient: &gophercloud.ProviderClient{
- TokenID: "12345abcdef",
- },
- Endpoint: testhelper.Endpoint(),
+ ProviderClient: &gophercloud.ProviderClient{},
+ Endpoint: testhelper.Endpoint(),
}
testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
@@ -42,7 +40,7 @@
}
}
-func authTokenPostErr(t *testing.T, options AuthOptionsBuilder, scope *gophercloud.ScopeOptsV3, includeToken bool, expectedErr error) {
+func authTokenPostErr(t *testing.T, options AuthOptions, scope *Scope, includeToken bool, expectedErr error) {
testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()
@@ -64,10 +62,7 @@
}
func TestCreateUserIDAndPassword(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "me"
- ao.Password = "squirrel!"
- authTokenPost(t, ao, nil, `
+ authTokenPost(t, AuthOptions{UserID: "me", Password: "squirrel!"}, nil, `
{
"auth": {
"identity": {
@@ -82,10 +77,7 @@
}
func TestCreateUsernameDomainIDPassword(t *testing.T) {
- ao := gophercloud.AuthOptions{DomainID: "abc123"}
- ao.Username = "fakey"
- ao.Password = "notpassword"
- authTokenPost(t, ao, nil, `
+ authTokenPost(t, AuthOptions{Username: "fakey", Password: "notpassword", DomainID: "abc123"}, nil, `
{
"auth": {
"identity": {
@@ -106,10 +98,7 @@
}
func TestCreateUsernameDomainNamePassword(t *testing.T) {
- ao := gophercloud.AuthOptions{DomainName: "spork.net"}
- ao.Username = "frank"
- ao.Password = "swordfish"
- authTokenPost(t, ao, nil, `
+ authTokenPost(t, AuthOptions{Username: "frank", Password: "swordfish", DomainName: "spork.net"}, nil, `
{
"auth": {
"identity": {
@@ -130,7 +119,7 @@
}
func TestCreateTokenID(t *testing.T) {
- authTokenPost(t, gophercloud.AuthOptions{TokenID: "12345abcdef"}, nil, `
+ authTokenPost(t, AuthOptions{TokenID: "12345abcdef"}, nil, `
{
"auth": {
"identity": {
@@ -145,11 +134,9 @@
}
func TestCreateProjectIDScope(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "fenris"
- ao.Password = "g0t0h311"
- scope := &gophercloud.ScopeOptsV3{ProjectID: "123456"}
- authTokenPost(t, ao, scope, `
+ options := AuthOptions{UserID: "fenris", Password: "g0t0h311"}
+ scope := &Scope{ProjectID: "123456"}
+ authTokenPost(t, options, scope, `
{
"auth": {
"identity": {
@@ -172,11 +159,9 @@
}
func TestCreateDomainIDScope(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "fenris"
- ao.Password = "g0t0h311"
- scope := &gophercloud.ScopeOptsV3{DomainID: "1000"}
- authTokenPost(t, ao, scope, `
+ options := AuthOptions{UserID: "fenris", Password: "g0t0h311"}
+ scope := &Scope{DomainID: "1000"}
+ authTokenPost(t, options, scope, `
{
"auth": {
"identity": {
@@ -199,11 +184,9 @@
}
func TestCreateProjectNameAndDomainIDScope(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "fenris"
- ao.Password = "g0t0h311"
- scope := &gophercloud.ScopeOptsV3{ProjectName: "world-domination", DomainID: "1000"}
- authTokenPost(t, ao, scope, `
+ options := AuthOptions{UserID: "fenris", Password: "g0t0h311"}
+ scope := &Scope{ProjectName: "world-domination", DomainID: "1000"}
+ authTokenPost(t, options, scope, `
{
"auth": {
"identity": {
@@ -229,11 +212,9 @@
}
func TestCreateProjectNameAndDomainNameScope(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "fenris"
- ao.Password = "g0t0h311"
- scope := &gophercloud.ScopeOptsV3{ProjectName: "world-domination", DomainName: "evil-plans"}
- authTokenPost(t, ao, scope, `
+ options := AuthOptions{UserID: "fenris", Password: "g0t0h311"}
+ scope := &Scope{ProjectName: "world-domination", DomainName: "evil-plans"}
+ authTokenPost(t, options, scope, `
{
"auth": {
"identity": {
@@ -278,10 +259,8 @@
}`)
})
- ao := gophercloud.AuthOptions{}
- ao.UserID = "me"
- ao.Password = "shhh"
- token, err := Create(&client, ao, nil).Extract()
+ options := AuthOptions{UserID: "me", Password: "shhh"}
+ token, err := Create(&client, options, nil).Extract()
if err != nil {
t.Fatalf("Create returned an error: %v", err)
}
@@ -292,127 +271,123 @@
}
func TestCreateFailureEmptyAuth(t *testing.T) {
- authTokenPostErr(t, gophercloud.AuthOptions{}, nil, false, ErrMissingPassword)
+ authTokenPostErr(t, AuthOptions{}, nil, false, ErrMissingPassword{})
+}
+
+func TestCreateFailureTenantID(t *testing.T) {
+ authTokenPostErr(t, AuthOptions{TenantID: "something"}, nil, false, ErrTenantIDProvided{})
+}
+
+func TestCreateFailureTenantName(t *testing.T) {
+ authTokenPostErr(t, AuthOptions{TenantName: "something"}, nil, false, ErrTenantNameProvided{})
}
func TestCreateFailureTokenIDUsername(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.Username = "somthing"
- authTokenPostErr(t, ao, nil, true, ErrUsernameWithToken)
+ authTokenPostErr(t, AuthOptions{Username: "something", TokenID: "12345"}, nil, true, ErrUsernameWithToken{})
}
func TestCreateFailureTokenIDUserID(t *testing.T) {
- authTokenPostErr(t, gophercloud.AuthOptions{UserID: "something"}, nil, true, ErrUserIDWithToken)
+ authTokenPostErr(t, AuthOptions{UserID: "something", TokenID: "12345"}, nil, true, ErrUserIDWithToken{})
}
func TestCreateFailureTokenIDDomainID(t *testing.T) {
- authTokenPostErr(t, gophercloud.AuthOptions{DomainID: "something"}, nil, true, ErrDomainIDWithToken)
+ authTokenPostErr(t, AuthOptions{DomainID: "something", TokenID: "12345"}, nil, true, ErrDomainIDWithToken{})
}
func TestCreateFailureTokenIDDomainName(t *testing.T) {
- authTokenPostErr(t, gophercloud.AuthOptions{DomainName: "something"}, nil, true, ErrDomainNameWithToken)
+ authTokenPostErr(t, AuthOptions{DomainName: "something", TokenID: "12345"}, nil, true, ErrDomainNameWithToken{})
}
func TestCreateFailureMissingUser(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.Password = "supersecure"
- authTokenPostErr(t, ao, nil, false, ErrUsernameOrUserID)
+ options := AuthOptions{Password: "supersecure"}
+ authTokenPostErr(t, options, nil, false, ErrUsernameOrUserID{})
}
func TestCreateFailureBothUser(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "redundancy"
- ao.Username = "oops"
- ao.Password = "supersecure"
- authTokenPostErr(t, ao, nil, false, ErrUsernameOrUserID)
+ options := AuthOptions{
+ Password: "supersecure",
+ Username: "oops",
+ UserID: "redundancy",
+ }
+ authTokenPostErr(t, options, nil, false, ErrUsernameOrUserID{})
}
func TestCreateFailureMissingDomain(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.Username = "notuniqueenough"
- ao.Password = "supersecure"
- authTokenPostErr(t, ao, nil, false, ErrDomainIDOrDomainName)
+ options := AuthOptions{
+ Password: "supersecure",
+ Username: "notuniqueenough",
+ }
+ authTokenPostErr(t, options, nil, false, ErrDomainIDOrDomainName{})
}
func TestCreateFailureBothDomain(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.Username = "someone"
- ao.Password = "supersecure"
- ao.DomainID = "hurf"
- ao.DomainName = "durf"
- authTokenPostErr(t, ao, nil, false, ErrDomainIDOrDomainName)
+ options := AuthOptions{
+ Password: "supersecure",
+ Username: "someone",
+ DomainID: "hurf",
+ DomainName: "durf",
+ }
+ authTokenPostErr(t, options, nil, false, ErrDomainIDOrDomainName{})
}
func TestCreateFailureUserIDDomainID(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "100"
- ao.Password = "stuff"
- ao.DomainID = "oops"
- authTokenPostErr(t, ao, nil, false, ErrDomainIDWithUserID)
+ options := AuthOptions{
+ UserID: "100",
+ Password: "stuff",
+ DomainID: "oops",
+ }
+ authTokenPostErr(t, options, nil, false, ErrDomainIDWithUserID{})
}
func TestCreateFailureUserIDDomainName(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "100"
- ao.Password = "sssh"
- ao.DomainName = "oops"
- authTokenPostErr(t, ao, nil, false, ErrDomainNameWithUserID)
+ options := AuthOptions{
+ UserID: "100",
+ Password: "sssh",
+ DomainName: "oops",
+ }
+ authTokenPostErr(t, options, nil, false, ErrDomainNameWithUserID{})
}
func TestCreateFailureScopeProjectNameAlone(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{ProjectName: "notenough"}
- authTokenPostErr(t, ao, scope, false, ErrScopeDomainIDOrDomainName)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{ProjectName: "notenough"}
+ authTokenPostErr(t, options, scope, false, ErrScopeDomainIDOrDomainName{})
}
func TestCreateFailureScopeProjectNameAndID(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{ProjectName: "whoops", ProjectID: "toomuch", DomainID: "1234"}
- authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDOrProjectName)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{ProjectName: "whoops", ProjectID: "toomuch", DomainID: "1234"}
+ authTokenPostErr(t, options, scope, false, ErrScopeProjectIDOrProjectName{})
}
func TestCreateFailureScopeProjectIDAndDomainID(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{ProjectID: "toomuch", DomainID: "notneeded"}
- authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDAlone)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{ProjectID: "toomuch", DomainID: "notneeded"}
+ authTokenPostErr(t, options, scope, false, ErrScopeProjectIDAlone{})
}
func TestCreateFailureScopeProjectIDAndDomainNAme(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{ProjectID: "toomuch", DomainName: "notneeded"}
- authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDAlone)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{ProjectID: "toomuch", DomainName: "notneeded"}
+ authTokenPostErr(t, options, scope, false, ErrScopeProjectIDAlone{})
}
func TestCreateFailureScopeDomainIDAndDomainName(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{DomainID: "toomuch", DomainName: "notneeded"}
- authTokenPostErr(t, ao, scope, false, ErrScopeDomainIDOrDomainName)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{DomainID: "toomuch", DomainName: "notneeded"}
+ authTokenPostErr(t, options, scope, false, ErrScopeDomainIDOrDomainName{})
}
func TestCreateFailureScopeDomainNameAlone(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{DomainName: "notenough"}
- authTokenPostErr(t, ao, scope, false, ErrScopeDomainName)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{DomainName: "notenough"}
+ authTokenPostErr(t, options, scope, false, ErrScopeDomainName{})
}
func TestCreateFailureEmptyScope(t *testing.T) {
- ao := gophercloud.AuthOptions{}
- ao.UserID = "myself"
- ao.Password = "swordfish"
- scope := &gophercloud.ScopeOptsV3{}
- authTokenPostErr(t, ao, scope, false, ErrScopeEmpty)
+ options := AuthOptions{UserID: "myself", Password: "swordfish"}
+ scope := &Scope{}
+ authTokenPostErr(t, options, scope, false, ErrScopeEmpty{})
}
func TestGetRequest(t *testing.T) {