Skip to content

Commit

Permalink
engflow_auth: Add export subcommand
Browse files Browse the repository at this point in the history
This change adds an `export` subcommand which prints the
`(*oauth2.Token).AccessToken` field for the specified cluster to stdout,
when:
* a token is stored for the specified cluster
* the token is not expired

This isn't any less secure than using `engflow_auth get`, which is
another variation on the same intent; since the token storage currently
used is backed by the OS keychain, only the current user should be able
to fetch tokens they've stored.

Tested: unit tests added
Bug: linear/CUS-367
  • Loading branch information
minor-fixes committed Jul 30, 2024
1 parent c50599b commit 5507d13
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 1 deletion.
59 changes: 58 additions & 1 deletion cmd/engflow_auth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ type appState struct {
tokenStore oauthtoken.LoadStorer
}

type ExportedToken struct {
// OAuth2 token as returned from EngFlow auth endpoints
Token *oauth2.Token `json:"token"`
// Hostname of the cluster token was issued from
ClusterHost string `json:"cluster_host"`
// List of alternative hostnames for this cluster, for which this token
// should also apply
Aliases []string `json:"aliases,omitempty"`
}

func (r *appState) get(cliCtx *cli.Context) error {
ctx := cliCtx.Context

Expand Down Expand Up @@ -64,7 +74,42 @@ func (r *appState) get(cliCtx *cli.Context) error {
Expires: &token.Expiry,
}
if err := json.NewEncoder(cliCtx.App.Writer).Encode(res); err != nil {
return autherr.CodedErrorf(autherr.CodeAuthFailure, "failed to marshal GetCredentialsResponse to JSON: %w", err)
return autherr.CodedErrorf(autherr.CodeBadParams, "expected exactly 1 positional argument, a cluster host name; got %d", cliCtx.NArg())
}
return nil
}

func (r *appState) export(cliCtx *cli.Context) error {
ctx := cliCtx.Context

if cliCtx.NArg() != 1 {
return autherr.CodedErrorf(autherr.CodeBadParams, "expected exactly 1 positional argument, a cluster host name; got %d arguments", cliCtx.NArg())
}
clusterURL, err := sanitizedURL(cliCtx.Args().Get(0))
if err != nil {
return autherr.CodedErrorf(autherr.CodeBadParams, "invalid cluster: %w", err)
}

token, err := r.tokenStore.Load(ctx, clusterURL.Host)
if err != nil {
if reauthErr := (*autherr.CodedError)(nil); errors.As(err, &reauthErr) && reauthErr.Code == autherr.CodeReauthRequired {
return reauthErr
}
return autherr.CodedErrorf(autherr.CodeTokenStoreFailure, "failed to fetch token for cluster %q: %w", clusterURL.Host, err)
}

if time.Now().After(token.Expiry) {
return autherr.ReauthRequired(clusterURL.Host)
}

export := &ExportedToken{
Token: token,
ClusterHost: clusterURL.Host,
Aliases: cliCtx.StringSlice("alias"),
}

if err := json.NewEncoder(cliCtx.App.Writer).Encode(export); err != nil {
return autherr.CodedErrorf(autherr.CodeAuthFailure, "failed to marshal token info: %w", err)
}
return nil
}
Expand Down Expand Up @@ -172,6 +217,18 @@ This command should only be used by tools that understand the Bazel
credential helper protocol.`),
Action: root.get,
},
{
Name: "export",
Usage: "Prints the currently-stored token for the specified cluster to stdout",
ArgsUsage: " CLUSTER_URL",
Action: root.export,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "alias",
Usage: "Comma-separated list of alias hostnames for this cluster",
},
},
},
{
Name: "login",
Usage: "Generate and store credentials for a particular cluster",
Expand Down
78 changes: 78 additions & 0 deletions cmd/engflow_auth/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/url"
"strings"
Expand Down Expand Up @@ -337,6 +338,83 @@ func TestRun(t *testing.T) {
wantCode: autherr.CodeTokenStoreFailure,
wantErr: "token_store_fail",
},
{
desc: "export with no args",
args: []string{"export"},
wantCode: autherr.CodeBadParams,
wantErr: "expected exactly 1 positional argument",
},
{
desc: "export with invalid cluster URL",
args: []string{"export", "grpcs://cluster.example.com:8080"},
wantCode: autherr.CodeBadParams,
wantErr: "illegal scheme",
},
{
desc: "export when token not found",
args: []string{"export", "https://cluster.example.com"},
tokenStore: &fakeStore{
loadToken: nil,
loadErr: autherr.ReauthRequired("https://cluster.example.com"),
},
wantCode: autherr.CodeReauthRequired,
wantErr: "expired credentials for cluster",
},
{
desc: "export when token store fails",
args: []string{"export", "https://cluster.example.com"},
tokenStore: &fakeStore{
loadToken: nil,
loadErr: fmt.Errorf("token_load_error"),
},
wantCode: autherr.CodeTokenStoreFailure,
wantErr: "token_load_error",
},
{
desc: "export when token expired",
args: []string{"export", "https://cluster.example.com"},
tokenStore: &fakeStore{
loadToken: &oauth2.Token{
AccessToken: "access_token",
Expiry: time.Date(2024, 1, 2, 3, 4, 5, 6, time.UTC),
},
},
wantCode: autherr.CodeReauthRequired,
wantErr: "Please refresh credentials",
},
{
desc: "export token",
args: []string{"export", "https://cluster.example.com"},
tokenStore: &fakeStore{
loadToken: &oauth2.Token{
AccessToken: "token_data",
Expiry: expiresInFuture,
},
loadErr: nil,
},
wantStdoutContaining: []string{
`{"token":{"access_token":"token_data",`, // Should have top-level token element
`,"expiry":"`, // Should have token expiry
`,"cluster_host":"cluster.example.com"`, // Should have hostname
},
},
{
desc: "export token with alias",
args: []string{"export", "--alias", "cluster.local.example.com:8080", "https://cluster.example.com"},
tokenStore: &fakeStore{
loadToken: &oauth2.Token{
AccessToken: "token_data",
Expiry: expiresInFuture,
},
loadErr: nil,
},
wantStdoutContaining: []string{
`{"token":{"access_token":"token_data",`, // Should have top-level token element
`,"expiry":"`, // Should have token expiry
`,"cluster_host":"cluster.example.com"`, // Should have hostname
`,"aliases":["cluster.local.example.com:8080"]`, // Should have aliases
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
Expand Down

0 comments on commit 5507d13

Please sign in to comment.