Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v1] authentication: Add WithContext functions #2893

Merged
merged 2 commits into from
Feb 5, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
123 changes: 70 additions & 53 deletions openstack/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package openstack

import (
"context"
"fmt"
"reflect"
"strings"
Expand All @@ -23,20 +24,18 @@ const (
v3 = "v3"
)

/*
NewClient prepares an unauthenticated ProviderClient instance.
Most users will probably prefer using the AuthenticatedClient function
instead.

This is useful if you wish to explicitly control the version of the identity
service that's used for authentication explicitly, for example.

A basic example of using this would be:

ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.NewClient(ao.IdentityEndpoint)
client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
*/
// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function
// instead.
//
// This is useful if you wish to explicitly control the version of the identity
// service that's used for authentication explicitly, for example.
//
// A basic example of using this would be:
//
// ao, err := openstack.AuthOptionsFromEnv()
// provider, err := openstack.NewClient(ao.IdentityEndpoint)
// client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
base, err := utils.BaseEndpoint(endpoint)
if err != nil {
Expand All @@ -54,42 +53,45 @@ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
return p, nil
}

/*
AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
specified by the options, acquires a token, and returns a Provider Client
instance that's ready to operate.

If the full path to a versioned identity endpoint was specified (example:
http://example.com:5000/v3), that path will be used as the endpoint to query.

If a versionless endpoint was specified (example: http://example.com:5000/),
the endpoint will be queried to determine which versions of the identity service
are available, then chooses the most recent or most supported version.

Example:

ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(ao)
client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
*/
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
// AuthenticatedClientWithContext logs in to an OpenStack cloud found at the identity endpoint
// specified by the options, acquires a token, and returns a Provider Client
// instance that's ready to operate.
//
// If the full path to a versioned identity endpoint was specified (example:
// http://example.com:5000/v3), that path will be used as the endpoint to query.
//
// If a versionless endpoint was specified (example: http://example.com:5000/),
// the endpoint will be queried to determine which versions of the identity service
// are available, then chooses the most recent or most supported version.
//
// Example:
//
// ao, err := openstack.AuthOptionsFromEnv()
// provider, err := openstack.AuthenticatedClientWithContext(ctx, ao)
// client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
// Region: os.Getenv("OS_REGION_NAME"),
// })
func AuthenticatedClientWithContext(ctx context.Context, options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
client, err := NewClient(options.IdentityEndpoint)
if err != nil {
return nil, err
}

err = Authenticate(client, options)
err = AuthenticateWithContext(ctx, client, options)
if err != nil {
return nil, err
}
return client, nil
}

// Authenticate or re-authenticate against the most recent identity service
// supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
// AuthenticatedClient is a compatibility wrapper around AuthenticatedClientWithContext
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
return AuthenticatedClientWithContext(context.Background(), options)
}

// AuthenticateWithContext authenticates or re-authenticates against the most
// recent identity service supported at the provided endpoint.
func AuthenticateWithContext(ctx context.Context, client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
{ID: v2, Priority: 20, Suffix: "/v2.0/"},
{ID: v3, Priority: 30, Suffix: "/v3/"},
Expand All @@ -102,21 +104,31 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp

switch chosen.ID {
case v2:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
return v2auth(ctx, client, endpoint, options, gophercloud.EndpointOpts{})
case v3:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
return v3auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{})
default:
// The switch statement must be out of date from the versions list.
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
}
}

// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
// Authenticate is a compatibility wrapper around AuthenticateWithContext.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
return AuthenticateWithContext(context.Background(), client, options)
}

// AuthenticateV2WithContext explicitly authenticates against the identity v2 endpoint.
func AuthenticateV2WithContext(ctx context.Context, client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
return v2auth(ctx, client, "", options, eo)
}

// AuthenticateV2 is a compatibility wrapper around AuthenticateV2WithContext.
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
return v2auth(client, "", options, eo)
return AuthenticateV2WithContext(context.Background(), client, options, eo)
}

func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
func v2auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
v2Client, err := NewIdentityV2(client, eo)
if err != nil {
return err
Expand All @@ -136,7 +148,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
TokenID: options.TokenID,
}

result := tokens2.Create(v2Client, v2Opts)
result := tokens2.CreateWithContext(ctx, v2Client, v2Opts)

err = client.SetTokenAndAuthResult(result)
if err != nil {
Expand All @@ -159,7 +171,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
tao := options
tao.AllowReauth = false
client.ReauthFunc = func() error {
err := v2auth(&tac, endpoint, tao, eo)
err := v2auth(ctx, &tac, endpoint, tao, eo)
if err != nil {
return err
}
Expand All @@ -174,12 +186,17 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
return nil
}

// AuthenticateV3 explicitly authenticates against the identity v3 service.
// AuthenticateV3WithContext explicitly authenticates against the identity v3 service.
func AuthenticateV3WithContext(ctx context.Context, client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
return v3auth(ctx, client, "", options, eo)
}

// AuthenticateV3 is a compatibility wrapper around AuthenticateV3WithContext
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
return v3auth(client, "", options, eo)
return AuthenticateV3WithContext(context.Background(), client, options, eo)
}

func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
// Override the generated service endpoint with the one returned by the version endpoint.
v3Client, err := NewIdentityV3(client, eo)
if err != nil {
Expand Down Expand Up @@ -229,11 +246,11 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
var result tokens3.CreateResult
switch opts.(type) {
case *ec2tokens.AuthOptions:
result = ec2tokens.Create(v3Client, opts)
result = ec2tokens.CreateWithContext(ctx, v3Client, opts)
case *oauth1.AuthOptions:
result = oauth1.Create(v3Client, opts)
result = oauth1.CreateWithContext(ctx, v3Client, opts)
default:
result = tokens3.Create(v3Client, opts)
result = tokens3.CreateWithContext(ctx, v3Client, opts)
}

err = client.SetTokenAndAuthResult(result)
Expand Down Expand Up @@ -277,7 +294,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
tao = opts
}
client.ReauthFunc = func() error {
err := v3auth(&tac, endpoint, tao, eo)
err := v3auth(ctx, &tac, endpoint, tao, eo)
if err != nil {
return err
}
Expand Down
26 changes: 20 additions & 6 deletions openstack/identity/v2/tokens/requests.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package tokens

import "github.com/gophercloud/gophercloud"
import (
"context"

"github.com/gophercloud/gophercloud"
)

// PasswordCredentialsV2 represents the required options to authenticate
// with a username and password.
Expand Down Expand Up @@ -77,29 +81,39 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
return b, nil
}

// Create authenticates to the identity service and attempts to acquire a Token.
// CreateWithContext authenticates to the identity service and attempts to acquire a Token.
// Generally, rather than interact with this call directly, end users should
// call openstack.AuthenticatedClient(), which abstracts all of the gory details
// about navigating service catalogs and such.
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
func CreateWithContext(ctx context.Context, client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
b, err := auth.ToTokenV2CreateMap()
if err != nil {
r.Err = err
return
}
resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
resp, err := client.PostWithContext(ctx, CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
OmitHeaders: []string{"X-Auth-Token"},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// Get validates and retrieves information for user's token.
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
// Create is a compatibility wrapper around CreateWithContext
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
return CreateWithContext(context.Background(), client, auth)
}

// GetWithContext validates and retrieves information for user's token.
func GetWithContext(ctx context.Context, client *gophercloud.ServiceClient, token string) (r GetResult) {
resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// Get is a compatibility wrapper around GetWithContext
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
return GetWithContext(context.Background(), client, token)
}
26 changes: 19 additions & 7 deletions openstack/identity/v3/extensions/ec2tokens/requests.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ec2tokens

import (
"context"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
Expand Down Expand Up @@ -287,8 +288,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]
return b, nil
}

// Create authenticates and either generates a new token from EC2 credentials
func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
// CreateWithContext authenticates and either generates a new token from EC2 credentials
func CreateWithContext(ctx context.Context, c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
b, err := opts.ToTokenV3CreateMap(nil)
if err != nil {
r.Err = err
Expand All @@ -298,17 +299,23 @@ func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tok
// delete "token" element, since it is used in s3tokens
deleteBodyElements(b, "token")

resp, err := c.Post(ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
resp, err := c.PostWithContext(ctx, ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// ValidateS3Token authenticates an S3 request using EC2 credentials. Doesn't
// generate a new token ID, but returns a tokens.CreateResult.
func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
// Create is a compatibility wrapper around CreateWithContext
func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
return CreateWithContext(context.Background(), c, opts)
}

// ValidateS3TokenWithContext authenticates an S3 request using EC2
// credentials. Doesn't generate a new token ID, but returns a
// tokens.CreateResult.
func ValidateS3TokenWithContext(ctx context.Context, c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
b, err := opts.ToTokenV3CreateMap(nil)
if err != nil {
r.Err = err
Expand All @@ -318,14 +325,19 @@ func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilde
// delete unused element, since it is used in ec2tokens only
deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb")

resp, err := c.Post(s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
resp, err := c.PostWithContext(ctx, s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// ValidateS3Token is a compatibility wrapper around ValidateS3TokenWithContext
func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
return ValidateS3TokenWithContext(context.Background(), c, opts)
}

// The following are small helper functions used to help build the signature.

// sumHMAC1 is a func to implement the HMAC SHA1 signature method.
Expand Down