summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorLeandro Alves <leandroalves@yahooinc.com>2021-11-16 14:45:05 +0100
committerLeandro Alves <leandroalves@yahooinc.com>2021-11-17 13:30:02 +0100
commit6e4ad0a351a6e7113ce72a3dffaa31f2aece8b22 (patch)
tree977a939cb68b32dc4d9847ca5c5101e1dbae3044 /client
parent1a1f46e024fccc5c783c93074e79e33279c97428 (diff)
remove unused code and rename vars and methods
Diffstat (limited to 'client')
-rw-r--r--client/go/auth/auth.go36
-rw-r--r--client/go/auth/token.go10
-rw-r--r--client/go/cli/cli.go186
-rw-r--r--client/go/cmd/helpers.go10
-rw-r--r--client/go/cmd/login.go2
-rw-r--r--client/go/vespa/target.go14
-rw-r--r--client/go/vespa/target_test.go13
7 files changed, 108 insertions, 163 deletions
diff --git a/client/go/auth/auth.go b/client/go/auth/auth.go
index 397e410924d..100af7ea1d2 100644
--- a/client/go/auth/auth.go
+++ b/client/go/auth/auth.go
@@ -4,7 +4,6 @@ package auth
import (
"context"
- "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -15,7 +14,6 @@ import (
)
const (
- audiencePath = "/api/v2/"
waitThresholdInSeconds = 3
// SecretsNamespace namespace used to set/get values from the keychain
SecretsNamespace = "vespa-cli"
@@ -39,8 +37,6 @@ type SecretStore interface {
}
type Result struct {
- Tenant string
- Domain string
RefreshToken string
AccessToken string
ExpiresIn int64
@@ -114,17 +110,10 @@ func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) {
return Result{}, errors.New(res.ErrorDescription)
}
- ten, domain, err := parseTenant(res.AccessToken)
- if err != nil {
- return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err)
- }
-
return Result{
RefreshToken: res.RefreshToken,
AccessToken: res.AccessToken,
ExpiresIn: res.ExpiresIn,
- Tenant: ten,
- Domain: domain,
}, nil
}
}
@@ -148,28 +137,3 @@ func (a *Authenticator) getDeviceCode(ctx context.Context) (State, error) {
}
return res, nil
}
-
-func parseTenant(accessToken string) (tenant, domain string, err error) {
- parts := strings.Split(accessToken, ".")
- v, err := base64.RawURLEncoding.DecodeString(parts[1])
- if err != nil {
- return "", "", err
- }
- var payload struct {
- AUDs []string `json:"aud"`
- }
- if err := json.Unmarshal(v, &payload); err != nil {
- return "", "", err
- }
- for _, aud := range payload.AUDs {
- u, err := url.Parse(aud)
- if err != nil {
- return "", "", err
- }
- if u.Path == audiencePath {
- parts := strings.Split(u.Host, ".")
- return parts[0], u.Host, nil
- }
- }
- return "", "", fmt.Errorf("audience not found for %s", audiencePath)
-}
diff --git a/client/go/auth/token.go b/client/go/auth/token.go
index e9b90b8994e..120c2602bad 100644
--- a/client/go/auth/token.go
+++ b/client/go/auth/token.go
@@ -25,16 +25,16 @@ type TokenRetriever struct {
Client *http.Client
}
-// Delete deletes the given tenant from the secrets' storage.
-func (t *TokenRetriever) Delete(tenant string) error {
- return t.Secrets.Delete(SecretsNamespace, tenant)
+// Delete deletes the given system from the secrets' storage.
+func (t *TokenRetriever) Delete(system string) error {
+ return t.Secrets.Delete(SecretsNamespace, system)
}
// Refresh gets a new access token from the provided refresh token,
// The request is used the default client_id and endpoint for device authentication.
-func (t *TokenRetriever) Refresh(ctx context.Context, tenant string) (TokenResponse, error) {
+func (t *TokenRetriever) Refresh(ctx context.Context, system string) (TokenResponse, error) {
// get stored refresh token:
- refreshToken, err := t.Secrets.Get(SecretsNamespace, tenant)
+ refreshToken, err := t.Secrets.Get(SecretsNamespace, system)
if err != nil {
return TokenResponse{}, fmt.Errorf("cannot get the stored refresh token: %w", err)
}
diff --git a/client/go/cli/cli.go b/client/go/cli/cli.go
index e1dde387b89..22c40f195b4 100644
--- a/client/go/cli/cli.go
+++ b/client/go/cli/cli.go
@@ -7,9 +7,6 @@ import (
"encoding/json"
"errors"
"fmt"
- "github.com/joeshaw/envdecode"
- "github.com/pkg/browser"
- "github.com/vespa-engine/vespa/client/go/util"
"io/ioutil"
"net/http"
"os"
@@ -19,8 +16,11 @@ import (
"sync"
"time"
+ "github.com/joeshaw/envdecode"
"github.com/lestrrat-go/jwx/jwt"
+ "github.com/pkg/browser"
"github.com/vespa-engine/vespa/client/go/auth"
+ "github.com/vespa-engine/vespa/client/go/util"
)
const accessTokenExpThreshold = 5 * time.Minute
@@ -28,55 +28,24 @@ const accessTokenExpThreshold = 5 * time.Minute
var errUnauthenticated = errors.New("not logged in. Try 'vespa login'")
type config struct {
- InstallID string `json:"install_id,omitempty"`
- DefaultTenant string `json:"default_tenant"`
- Tenants map[string]Tenant `json:"tenants"`
+ Systems map[string]System `json:"systems"`
}
-// Tenant is an auth0 Tenant.
-type Tenant struct {
- Name string `json:"name"`
- Domain string `json:"domain"`
- AccessToken string `json:"access_token,omitempty"`
- Scopes []string `json:"scopes,omitempty"`
- ExpiresAt time.Time `json:"expires_at"`
- ClientID string `json:"client_id"`
- ClientSecret string `json:"client_secret"`
+type System struct {
+ AccessToken string `json:"access_token,omitempty"`
+ Scopes []string `json:"scopes,omitempty"`
+ ExpiresAt time.Time `json:"expires_at"`
}
type Cli struct {
Authenticator *auth.Authenticator
- tenant string
+ system string
initOnce sync.Once
errOnce error
Path string
config config
}
-// IsLoggedIn encodes the domain logic for determining whether we're
-// logged in. This might check our config storage, or just in memory.
-func (c *Cli) IsLoggedIn() bool {
- // No need to check errors for initializing context.
- _ = c.init()
-
- if c.tenant == "" {
- return false
- }
-
- // Parse the access token for the tenant.
- t, err := jwt.ParseString(c.config.Tenants[c.tenant].AccessToken)
- if err != nil {
- return false
- }
-
- // Check if token is valid.
- if err = jwt.Validate(t, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
- return false
- }
-
- return true
-}
-
// default to vespa-cd.auth0.com
var (
authCfg struct {
@@ -99,11 +68,12 @@ func ContextWithCancel() context.Context {
return ctx
}
-// Setup will try to initialize the config context, as well as figure out if
-// there's a readily available tenant.
-func GetCli(configPath string) (*Cli, error) {
+// GetCli will try to initialize the config context, as well as figure out if
+// there's a readily available system.
+func GetCli(configPath string, systemName string) (*Cli, error) {
c := Cli{}
c.Path = configPath
+ c.system = systemName
if err := envdecode.StrictDecode(&authCfg); err != nil {
return nil, fmt.Errorf("could not decode env: %w", err)
}
@@ -116,29 +86,49 @@ func GetCli(configPath string) (*Cli, error) {
return &c, nil
}
-// prepareTenant loads the Tenant, refreshing its token if necessary.
-// The Tenant access token needs a refresh if:
-// 1. the Tenant scopes are different from the currently required scopes.
-// 2. the access token is expired.
-func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) {
- if err := c.init(); err != nil {
- return Tenant{}, err
+// IsLoggedIn encodes the domain logic for determining whether we're
+// logged in. This might check our config storage, or just in memory.
+func (c *Cli) IsLoggedIn() bool {
+ // No need to check errors for initializing context.
+ _ = c.init()
+
+ if c.system == "" {
+ return false
}
- t, err := c.getTenant()
+
+ // Parse the access token for the system.
+ token, err := jwt.ParseString(c.config.Systems[c.system].AccessToken)
if err != nil {
- return Tenant{}, err
+ return false
}
- if t.ClientID != "" && t.ClientSecret != "" {
- return t, nil
+ // Check if token is valid.
+ if err = jwt.Validate(token, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
+ return false
}
- if t.AccessToken == "" || scopesChanged(t) {
- t, err = RunLogin(ctx, c, true)
+ return true
+}
+
+// PrepareSystem loads the System, refreshing its token if necessary.
+// The System access token needs a refresh if:
+// 1. the System scopes are different from the currently required scopes - (auth0 changes).
+// 2. the access token is expired.
+func (c *Cli) PrepareSystem(ctx context.Context) (System, error) {
+ if err := c.init(); err != nil {
+ return System{}, err
+ }
+ s, err := c.getSystem()
+ if err != nil {
+ return System{}, err
+ }
+
+ if s.AccessToken == "" || scopesChanged(s) {
+ s, err = RunLogin(ctx, c, true)
if err != nil {
- return Tenant{}, err
+ return System{}, err
}
- } else if isExpired(t.ExpiresAt, accessTokenExpThreshold) {
+ } else if isExpired(s.ExpiresAt, accessTokenExpThreshold) {
// check if the stored access token is expired:
// use the refresh token to get a new access token:
tr := &auth.TokenRetriever{
@@ -147,29 +137,29 @@ func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) {
Client: http.DefaultClient,
}
- res, err := tr.Refresh(ctx, t.Domain)
+ res, err := tr.Refresh(ctx, c.system)
if err != nil {
// ask and guide the user through the login process:
fmt.Println(fmt.Errorf("failed to renew access token, %s", err))
- t, err = RunLogin(ctx, c, true)
+ s, err = RunLogin(ctx, c, true)
if err != nil {
- return Tenant{}, err
+ return System{}, err
}
} else {
- // persist the updated tenant with renewed access token
- t.AccessToken = res.AccessToken
- t.ExpiresAt = time.Now().Add(
+ // persist the updated system with renewed access token
+ s.AccessToken = res.AccessToken
+ s.ExpiresAt = time.Now().Add(
time.Duration(res.ExpiresIn) * time.Second,
)
- err = c.AddTenant(t)
+ err = c.AddSystem(s)
if err != nil {
- return Tenant{}, err
+ return System{}, err
}
}
}
- return t, nil
+ return s, nil
}
// isExpired is true if now() + a threshold is after the given date
@@ -177,11 +167,11 @@ func isExpired(t time.Time, threshold time.Duration) bool {
return time.Now().Add(threshold).After(t)
}
-// scopesChanged compare the Tenant scopes
+// scopesChanged compare the System scopes
// with the currently required scopes.
-func scopesChanged(t Tenant) bool {
+func scopesChanged(s System) bool {
want := auth.RequiredScopes()
- got := t.Scopes
+ got := s.Scopes
sort.Strings(want)
sort.Strings(got)
@@ -194,7 +184,7 @@ func scopesChanged(t Tenant) bool {
return true
}
- for i := range t.Scopes {
+ for i := range s.Scopes {
if want[i] != got[i] {
return true
}
@@ -203,34 +193,30 @@ func scopesChanged(t Tenant) bool {
return false
}
-func (c *Cli) getTenant() (Tenant, error) {
+func (c *Cli) getSystem() (System, error) {
if err := c.init(); err != nil {
- return Tenant{}, err
+ return System{}, err
}
- t, ok := c.config.Tenants[c.tenant]
+ s, ok := c.config.Systems[c.system]
if !ok {
- return Tenant{}, fmt.Errorf("unable to find tenant: %s; run 'vespa login' to configure a new tenant", c.tenant)
+ return System{}, fmt.Errorf("unable to find system: %s; run 'vespa login' to configure a new system", c.system)
}
- return t, nil
+ return s, nil
}
-// AddTenant assigns an existing, or new Tenant. This is expected to be called
+// AddSystem assigns an existing, or new System. This is expected to be called
// after a login has completed.
-func (c *Cli) AddTenant(ten Tenant) error {
+func (c *Cli) AddSystem(s System) error {
_ = c.init()
- if c.config.DefaultTenant == "" {
- c.config.DefaultTenant = ten.Domain
- }
-
// If we're dealing with an empty file, we'll need to initialize this map.
- if c.config.Tenants == nil {
- c.config.Tenants = map[string]Tenant{}
+ if c.config.Systems == nil {
+ c.config.Systems = map[string]System{}
}
- c.config.Tenants[ten.Domain] = ten
+ c.config.Systems[c.system] = s
if err := c.persistConfig(); err != nil {
return fmt.Errorf("unexpected error persisting config: %w", err)
@@ -282,14 +268,6 @@ func (c *Cli) initContext() (err error) {
return err
}
- if c.tenant == "" && c.config.DefaultTenant == "" {
- return errUnauthenticated
- }
-
- if c.tenant == "" {
- c.tenant = c.config.DefaultTenant
- }
-
return nil
}
@@ -297,14 +275,14 @@ func (c *Cli) initContext() (err error) {
// by showing the login instructions, opening the browser.
// Use `expired` to run the login from other commands setup:
// this will only affect the messages.
-func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) {
+func RunLogin(ctx context.Context, c *Cli, expired bool) (System, error) {
if expired {
fmt.Println("Please sign in to re-authorize the CLI.")
}
- state, err := cli.Authenticator.Start(ctx)
+ state, err := c.Authenticator.Start(ctx)
if err != nil {
- return Tenant{}, fmt.Errorf("could not start the authentication process: %w", err)
+ return System{}, fmt.Errorf("could not start the authentication process: %w", err)
}
fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)
@@ -319,12 +297,12 @@ func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) {
var res auth.Result
err = util.Spinner("Waiting for login to complete in browser", func() error {
- res, err = cli.Authenticator.Wait(ctx, state)
+ res, err = c.Authenticator.Wait(ctx, state)
return err
})
if err != nil {
- return Tenant{}, fmt.Errorf("login error: %w", err)
+ return System{}, fmt.Errorf("login error: %w", err)
}
fmt.Print("\n")
@@ -333,23 +311,21 @@ func RunLogin(ctx context.Context, cli *Cli, expired bool) (Tenant, error) {
// store the refresh token
secretsStore := &auth.Keyring{}
- err = secretsStore.Set(auth.SecretsNamespace, res.Domain, res.RefreshToken)
+ err = secretsStore.Set(auth.SecretsNamespace, c.system, res.RefreshToken)
if err != nil {
// log the error but move on
fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
}
- t := Tenant{
- Name: res.Tenant,
- Domain: res.Domain,
+ s := System{
AccessToken: res.AccessToken,
ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
Scopes: auth.RequiredScopes(),
}
- err = cli.AddTenant(t)
+ err = c.AddSystem(s)
if err != nil {
- return Tenant{}, fmt.Errorf("could not add tenant to config: %w", err)
+ return System{}, fmt.Errorf("could not add system to config: %w", err)
}
- return t, nil
+ return s, nil
}
diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go
index 702f50ce9f5..084148f9bdc 100644
--- a/client/go/cmd/helpers.go
+++ b/client/go/cmd/helpers.go
@@ -144,6 +144,13 @@ func getService(service string, sessionOrRunID int64) *vespa.Service {
func getSystem() string { return os.Getenv("VESPA_CLI_CLOUD_SYSTEM") }
+func getSystemName() string {
+ if getSystem() == "publiccd" {
+ return "publiccd"
+ }
+ return "public"
+}
+
func getConsoleURL() string {
if getSystem() == "publiccd" {
return "https://console-cd.vespa.oath.cloud"
@@ -205,7 +212,8 @@ func getTarget() vespa.Target {
Writer: stdout,
Level: vespa.LogLevel(logLevelArg),
},
- cfg.AuthConfigPath())
+ cfg.AuthConfigPath(),
+ getSystemName())
}
fatalErrHint(fmt.Errorf("Invalid target: %s", targetType), "Valid targets are 'local', 'cloud' or an URL")
return nil
diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go
index 767d462b0be..415d44b75db 100644
--- a/client/go/cmd/login.go
+++ b/client/go/cmd/login.go
@@ -24,7 +24,7 @@ var loginCmd = &cobra.Command{
if err != nil {
return err
}
- c, err := cli.GetCli(cfg.AuthConfigPath())
+ c, err := cli.GetCli(cfg.AuthConfigPath(), getSystemName())
if err != nil {
return err
}
diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go
index f497bf5b3cd..367685df34d 100644
--- a/client/go/vespa/target.go
+++ b/client/go/vespa/target.go
@@ -1,4 +1,5 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
package vespa
import (
@@ -6,7 +7,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
- "github.com/vespa-engine/vespa/client/go/cli"
"io"
"io/ioutil"
"math"
@@ -18,6 +18,7 @@ import (
"strings"
"time"
+ "github.com/vespa-engine/vespa/client/go/cli"
"github.com/vespa-engine/vespa/client/go/util"
)
@@ -211,6 +212,7 @@ type cloudTarget struct {
queryURL string
documentURL string
authConfigPath string
+ systemName string
}
func (t *cloudTarget) Type() string { return t.targetType }
@@ -253,12 +255,12 @@ func (t *cloudTarget) PrepareApiRequest(req *http.Request, sigKeyId string) erro
}
func (t *cloudTarget) addAuth0AccessToken(request *http.Request) error {
- c, err := cli.GetCli(t.authConfigPath)
- tenant, err := c.PrepareTenant(cli.ContextWithCancel())
+ c, err := cli.GetCli(t.authConfigPath, t.systemName)
+ system, err := c.PrepareSystem(cli.ContextWithCancel())
if err != nil {
return err
}
- request.Header.Set("Authorization", "Bearer "+tenant.AccessToken)
+ request.Header.Set("Authorization", "Bearer "+system.AccessToken)
return nil
}
@@ -446,8 +448,7 @@ func CustomTarget(baseURL string) Target {
}
// CloudTarget creates a Target for the Vespa Cloud platform.
-func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions,
- authConfigPath string) Target {
+func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions, authConfigPath string, systemName string) Target {
return &cloudTarget{
apiURL: apiURL,
targetType: cloudTargetType,
@@ -456,6 +457,7 @@ func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions
tlsOptions: tlsOptions,
logOptions: logOptions,
authConfigPath: authConfigPath,
+ systemName: systemName,
}
}
diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go
index d4d23901513..62bde3044bf 100644
--- a/client/go/vespa/target_test.go
+++ b/client/go/vespa/target_test.go
@@ -149,15 +149,10 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
}
assert.Nil(t, err)
- target := CloudTarget(
- "https://example.com",
- Deployment{
- Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"},
- Zone: ZoneID{Environment: "dev", Region: "us-north-1"},
- },
- apiKey,
- TLSOptions{KeyPair: x509KeyPair},
- LogOptions{Writer: logWriter}, "")
+ target := CloudTarget("https://example.com", Deployment{
+ Application: ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"},
+ Zone: ZoneID{Environment: "dev", Region: "us-north-1"},
+ }, apiKey, TLSOptions{KeyPair: x509KeyPair}, LogOptions{Writer: logWriter}, "", "")
if ct, ok := target.(*cloudTarget); ok {
ct.apiURL = url
} else {