Skip to content

Commit

Permalink
Merge pull request #15 from gildas/release/0.9.1
Browse files Browse the repository at this point in the history
Merge release/0.9.1
  • Loading branch information
gildas authored Jun 13, 2024
2 parents 16de361 + 5727628 commit 0ae6a51
Show file tree
Hide file tree
Showing 18 changed files with 191 additions and 101 deletions.
99 changes: 98 additions & 1 deletion access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import (
"time"

"github.com/gildas/go-core"
"github.com/gildas/go-errors"
"github.com/gildas/go-logger"
"github.com/google/uuid"
"github.com/gorilla/securecookie"
)

// AccessToken is used to consume the GCloud API
//
// It must be obtained via an AuthorizationGrant
type AccessToken struct {
ID uuid.UUID `json:"id" db:"key"`
Type string `json:"tokenType"`
Token string `json:"token"`
ExpiresOn time.Time `json:"tokenExpires"` // UTC!
ExpiresOn time.Time `json:"expiresOn"` // UTC!
}

// UpdatedAccessToken describes an updated Access Token
Expand All @@ -32,6 +36,46 @@ var (
secureCookie = securecookie.New(hashKey, blockKey)
)

// NewAccessToken creates a new AccessToken
func NewAccessToken(token string, expiresOn time.Time) *AccessToken {
return &AccessToken{
ID: uuid.New(),
Type: "Bearer",
Token: token,
ExpiresOn: expiresOn,
}
}

// NewAccessTokenWithType creates a new AccessToken with a type
func NewAccessTokenWithType(tokenType, token string, expiresOn time.Time) *AccessToken {
return &AccessToken{
ID: uuid.New(),
Type: tokenType,
Token: token,
ExpiresOn: expiresOn,
}
}

// NewAccessTokenWithDuration creates a new AccessToken that expires in a given duration
func NewAccessTokenWithDuration(token string, expiresIn time.Duration) *AccessToken {
return &AccessToken{
ID: uuid.New(),
Type: "Bearer",
Token: token,
ExpiresOn: time.Now().UTC().Add(expiresIn),
}
}

// NewAccessTokenWithDurationAndType creates a new AccessToken with a type and that expires in a given duration
func NewAccessTokenWithDurationAndType(tokenType, token string, expiresIn time.Duration) *AccessToken {
return &AccessToken{
ID: uuid.New(),
Type: tokenType,
Token: token,
ExpiresOn: time.Now().UTC().Add(expiresIn),
}
}

// Reset resets the Token so it is expired and empty
func (token *AccessToken) Reset() {
token.Type = ""
Expand Down Expand Up @@ -76,6 +120,59 @@ func (token AccessToken) ExpiresIn() time.Duration {
return token.ExpiresOn.Sub(time.Now().UTC())
}

// Redact redacts sensitive information
//
// implements logger.Redactable
func (token AccessToken) Redact() any {
redacted := token
if len(redacted.Token) > 0 {
redacted.Token = logger.RedactWithHash(token.Token)
}
return redacted
}

// String gets a string representation of this AccessToken
func (token AccessToken) String() string {
return token.Type + " " + token.Token
}

// MarshalJSON marshals this into JSON
//
// implements json.Marshaler
func (token AccessToken) MarshalJSON() ([]byte, error) {
type surrogate AccessToken

data, err := json.Marshal(struct {
ID core.UUID `json:"id"`
surrogate
ExpiresOn core.Time `json:"expiresOn"`
}{
ID: core.UUID(token.ID),
surrogate: surrogate(token),
ExpiresOn: core.Time(token.ExpiresOn),
})
return data, errors.JSONMarshalError.Wrap(err)
}

// UnmarshalJSON decodes JSON
//
// implements json.Unmarshaler
func (token *AccessToken) UnmarshalJSON(payload []byte) (err error) {
type surrogate AccessToken

var inner struct {
ID core.UUID `json:"id"`
surrogate
ExpiresOn core.Time `json:"expiresOn"`
}
if err = json.Unmarshal(payload, &inner); err != nil {
return errors.JSONUnmarshalError.Wrap(err)
}
*token = AccessToken(inner.surrogate)
token.ID = uuid.UUID(inner.ID)
token.ExpiresOn = inner.ExpiresOn.AsTime()
if token.ID == uuid.Nil {
token.ID = uuid.New()
}
return nil
}
37 changes: 10 additions & 27 deletions access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gcloudcx_test

import (
"encoding/json"
"fmt"
"testing"
"time"

Expand All @@ -11,53 +12,39 @@ import (
)

func TestCanMarshallAccessToken(t *testing.T) {
token := gcloudcx.AccessToken{
Type: "Bearer",
Token: "Very Long String",
ExpiresOn: time.Date(1996, 9, 23, 0, 0, 0, 0, time.UTC),
}

expected := `{"tokenType": "Bearer", "token": "Very Long String", "tokenExpires": "1996-09-23T00:00:00Z"}`
token := gcloudcx.NewAccessToken("Very Long String", time.Date(1996, 9, 23, 0, 0, 0, 0, time.UTC))
expected := fmt.Sprintf(`{"id": "%s", "tokenType": "Bearer", "token": "Very Long String", "expiresOn": "1996-09-23T00:00:00Z"}`, token.ID)
data, err := json.Marshal(token)
require.Nil(t, err, "Failed to marshall token")
require.NotEmpty(t, data, "Failed to marshall token")
assert.JSONEq(t, expected, string(data))
}

func TestCanUnmarshallAccessToken(t *testing.T) {
source := `{"tokenType": "Bearer", "token": "Very Long String", "tokenExpires": "1996-09-23T00:00:00Z"}`
source := `{"tokenType": "Bearer", "token": "Very Long String", "expiresOn": "1996-09-23T00:00:00Z"}`
token := gcloudcx.AccessToken{}

err := json.Unmarshal([]byte(source), &token)
require.Nil(t, err, "Failed to unmarshall token")
assert.NotNil(t, token.ID, "Token ID should not be nil")
assert.Equal(t, "Bearer", token.Type)
assert.Equal(t, "Very Long String", token.Token)
assert.Equal(t, time.Date(1996, 9, 23, 0, 0, 0, 0, time.UTC), token.ExpiresOn)
assert.True(t, token.IsExpired(), "Token should be expired!")
}

func TestCanTellExpirationOfAccessToken(t *testing.T) {
token := gcloudcx.AccessToken{
Type: "Bearer",
Token: "Very Long String",
ExpiresOn: time.Now().UTC().Add(2 * time.Hour),
}
token := gcloudcx.NewAccessTokenWithDurationAndType("Very Long String", "Bearer", 2*time.Hour)

assert.False(t, token.IsExpired(), "Token should not be expired")
assert.True(t, 1*time.Hour < token.ExpiresIn(), "Token should expire in an hour at least")

token.ExpiresOn = time.Now().UTC().AddDate(0, 0, -1)
token = gcloudcx.NewAccessTokenWithType("Bearer", "Very Long String", time.Date(1996, 9, 23, 0, 0, 0, 0, time.UTC))
assert.True(t, token.IsExpired(), "Token should be expired")
assert.True(t, time.Duration(0) == token.ExpiresIn(), "Token should expire in 0")
}

func TestCanResetAccessToken(t *testing.T) {
token := gcloudcx.AccessToken{
Type: "Bearer",
Token: "Very Long String",
ExpiresOn: time.Now().UTC().Add(2 * time.Hour),
}

token := gcloudcx.NewAccessTokenWithDuration("Very Long String", 2*time.Hour)
token.Reset()
assert.Empty(t, token.Token, "The Token string should be empty")
assert.Empty(t, token.Type, "The Token type should be empty")
Expand All @@ -66,12 +53,8 @@ func TestCanResetAccessToken(t *testing.T) {
}

func TestCanResetGrantAccessToken(t *testing.T) {
token := gcloudcx.AccessToken{
Type: "Bearer",
Token: "Very Long String",
ExpiresOn: time.Now().UTC().Add(2 * time.Hour),
}
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{}).SetAuthorizationGrant(&gcloudcx.ClientCredentialsGrant{Token: token})
token := gcloudcx.NewAccessTokenWithDuration("Very Long String", 2*time.Hour)
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{}).SetAuthorizationGrant(&gcloudcx.ClientCredentialsGrant{Token: *token})
assert.Equal(t, "Bearer", client.Grant.AccessToken().Type)
assert.Equal(t, "Very Long String", client.Grant.AccessToken().Token)

Expand Down
4 changes: 2 additions & 2 deletions auth_clientcredentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (grant *ClientCredentialsGrant) GetID() uuid.UUID {
//
// Implements Authorizable
func (grant *ClientCredentialsGrant) Authorize(context context.Context, client *Client) (err error) {
log := client.GetLogger(context).Child(nil, "authorize", "grant", "client_credentials")
log := client.GetLogger(context).Child("client", "authorize", "grant", "client_credentials", "token", grant.Token.ID)

log.Infof("Authenticating with %s using Client Credentials grant", client.Region)

Expand Down Expand Up @@ -78,7 +78,7 @@ func (grant *ClientCredentialsGrant) Authorize(context context.Context, client *

log.Debugf("New %s token expires on %s", grant.Token.Type, grant.Token.ExpiresOn)
if grant.TokenUpdated != nil {
log.Debugf("Sending new token to TokenUpdated chan")
log.Debugf("Sending new token to TokenUpdated Go channel")
grant.TokenUpdated <- UpdatedAccessToken{
AccessToken: grant.Token,
CustomData: grant.CustomData,
Expand Down
2 changes: 1 addition & 1 deletion biography.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ func (biography Biography) Redact() interface{} {
if len(biography.Spouse) > 0 {
redacted.Spouse = logger.RedactWithHash(biography.Spouse)
}
return &redacted
return redacted
}
4 changes: 4 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Client struct {

// ClientOptions contains the options to create a new Client
type ClientOptions struct {
Context context.Context
Region string
OrganizationID uuid.UUID
DeploymentID uuid.UUID
Expand All @@ -47,6 +48,9 @@ func NewClient(options *ClientOptions) *Client {
if options.RequestTimeout < 2*time.Second {
options.RequestTimeout = 10 * time.Second
}
if log, err := logger.FromContext(options.Context); err == nil && options.Logger == nil {
options.Logger = log
}
client := Client{
Proxy: options.Proxy,
DeploymentID: options.DeploymentID,
Expand Down
28 changes: 14 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/websocket v1.5.1
github.com/gorilla/websocket v1.5.2
github.com/joho/godotenv v1.5.1
github.com/matoous/go-nanoid/v2 v2.1.0
github.com/stretchr/testify v1.9.0
)

require (
cloud.google.com/go v0.113.0 // indirect
cloud.google.com/go/auth v0.4.2 // indirect
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.5.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/logging v1.10.0 // indirect
Expand All @@ -37,7 +37,7 @@ require (
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
Expand All @@ -50,18 +50,18 @@ require (
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.181.0 // indirect
google.golang.org/genproto v0.0.0-20240521202816-d264139d666e // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
google.golang.org/api v0.184.0 // indirect
google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 0ae6a51

Please sign in to comment.