Skip to content

Commit

Permalink
feat: apps --reveal flag to expose secrets (#301)
Browse files Browse the repository at this point in the history
* feat: apps --reveal flag to expose secrets

* hide client secret from ls

* fix: signing keys only available if --reveal

* remove redundant type from array (simplifycompositelit)

* Update internal/cli/apps.go

Co-authored-by: Cyril David <[email protected]>

* Update internal/cli/apps.go

Co-authored-by: Rita Zerrizuela <[email protected]>

Co-authored-by: Cyril David <[email protected]>
Co-authored-by: Rita Zerrizuela <[email protected]>
  • Loading branch information
3 people authored May 21, 2021
1 parent cb3a6d4 commit 67a94e6
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 89 deletions.
34 changes: 24 additions & 10 deletions internal/cli/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ var (
Help: "List of grant types supported for this application. Can include code, implicit, refresh-token, credentials, password, password-realm, mfa-oob, mfa-otp, mfa-recovery-code, and device-code.",
IsRequired: false,
}
reveal = Flag{
Name: "Reveal",
LongForm: "reveal",
ShortForm: "r",
Help: "Display the Client Secret as part of the command output.",
}
)

func appsCmd(cli *cli) *cobra.Command {
Expand Down Expand Up @@ -179,6 +185,9 @@ func useAppCmd(cli *cli) *cobra.Command {
}

func listAppsCmd(cli *cli) *cobra.Command {
var inputs struct{
Reveal bool
}
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Expand All @@ -189,6 +198,7 @@ auth0 apps create`,
Example: `auth0 apps list
auth0 apps ls`,
RunE: func(cmd *cobra.Command, args []string) error {

var list *management.ClientList

if err := ansi.Waiting(func() error {
Expand All @@ -199,17 +209,20 @@ auth0 apps ls`,
return fmt.Errorf("An unexpected error occurred: %w", err)
}

cli.renderer.ApplicationList(list.Clients)
cli.renderer.ApplicationList(list.Clients, inputs.Reveal)
return nil
},
}

reveal.RegisterBool(cmd, &inputs.Reveal, false)

return cmd
}

func showAppCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
ID string
Reveal bool
}

cmd := &cobra.Command{
Expand Down Expand Up @@ -239,12 +252,13 @@ auth0 apps show <id>`,
return fmt.Errorf("Unable to load application. The Id %v specified doesn't exist", inputs.ID)
}

revealClientSecret := auth0.StringValue(a.AppType) != "native" && auth0.StringValue(a.AppType) != "spa"
cli.renderer.ApplicationShow(a, revealClientSecret)
cli.renderer.ApplicationShow(a, inputs.Reveal)
return nil
},
}

reveal.RegisterBool(cmd, &inputs.Reveal, false)

return cmd
}

Expand Down Expand Up @@ -302,6 +316,7 @@ func createAppCmd(cli *cli) *cobra.Command {
AllowedLogoutURLs []string
AuthMethod string
Grants []string
Reveal bool
}
var oidcConformant = true
var algorithm = "RS256"
Expand Down Expand Up @@ -418,9 +433,7 @@ auth0 apps create -n myapp -t [native|spa|regular|m2m] --description <descriptio
}

// Render result
// note: a is populated with the rest of the client fields by the API during creation.
revealClientSecret := auth0.StringValue(a.AppType) != "native" && auth0.StringValue(a.AppType) != "spa"
cli.renderer.ApplicationCreate(a, revealClientSecret)
cli.renderer.ApplicationCreate(a, inputs.Reveal)

return nil
},
Expand All @@ -435,6 +448,7 @@ auth0 apps create -n myapp -t [native|spa|regular|m2m] --description <descriptio
appLogoutURLs.RegisterStringSlice(cmd, &inputs.AllowedLogoutURLs, nil)
appAuthMethod.RegisterString(cmd, &inputs.AuthMethod, "")
appGrants.RegisterStringSlice(cmd, &inputs.Grants, nil)
reveal.RegisterBool(cmd, &inputs.Reveal, false)

return cmd
}
Expand All @@ -451,6 +465,7 @@ func updateAppCmd(cli *cli) *cobra.Command {
AllowedLogoutURLs []string
AuthMethod string
Grants []string
Reveal bool
}

cmd := &cobra.Command{
Expand Down Expand Up @@ -621,8 +636,7 @@ auth0 apps update <id> -n myapp --type [native|spa|regular|m2m]`,
}

// Render result
revealClientSecret := auth0.StringValue(a.AppType) != appTypeNative && auth0.StringValue(a.AppType) != appTypeSPA
cli.renderer.ApplicationUpdate(a, revealClientSecret)
cli.renderer.ApplicationUpdate(a, inputs.Reveal)

return nil
},
Expand All @@ -637,7 +651,7 @@ auth0 apps update <id> -n myapp --type [native|spa|regular|m2m]`,
appLogoutURLs.RegisterStringSliceU(cmd, &inputs.AllowedLogoutURLs, nil)
appAuthMethod.RegisterStringU(cmd, &inputs.AuthMethod, "")
appGrants.RegisterStringSliceU(cmd, &inputs.Grants, nil)

reveal.RegisterBool(cmd, &inputs.Reveal, false)
return cmd
}

Expand Down
103 changes: 67 additions & 36 deletions internal/cli/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,77 @@ import (
)

func TestAppsListCmd(t *testing.T) {
// Step 1: Setup our client mock for this test. We only care about
// Clients so no need to bootstrap other bits.
ctrl := gomock.NewController(t)
defer ctrl.Finish()

clientAPI := auth0.NewMockClientAPI(ctrl)
clientAPI.EXPECT().
List(gomock.Any()).
Return(&management.ClientList{
Clients: []*management.Client{
{
Name: auth0.String("some-name"),
ClientID: auth0.String("some-id"),
Callbacks: stringToInterfaceSlice([]string{"http://localhost"}),
},
tests := []struct {
name string
assertOutput func(t testing.TB, out string)
args []string
}{
{
name: "happy path",
assertOutput: func(t testing.TB, out string) {
expectTable(t, out,
[]string{"CLIENT ID", "NAME", "TYPE"},
[][]string{
{"some-id", "some-name", "Generic"},
},
)
},
},
{
name: "reveal secrets",
args: []string{"--reveal"},
assertOutput: func(t testing.TB, out string) {
expectTable(t, out,
[]string{"CLIENT ID", "NAME", "TYPE", "CLIENT SECRET"},
[][]string{
{"some-id", "some-name", "Generic", "secret-here"},
},
)
},
}, nil)

stdout := &bytes.Buffer{}

// Step 2: Setup our cli context. The important bits are
// renderer and api.
cli := &cli{
renderer: &display.Renderer{
MessageWriter: ioutil.Discard,
ResultWriter: stdout,
},
api: &auth0.API{Client: clientAPI},
}

cmd := listAppsCmd(cli)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Step 1: Setup our client mock for this test. We only care about
// Clients so no need to bootstrap other bits.
ctrl := gomock.NewController(t)
defer ctrl.Finish()

if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
clientAPI := auth0.NewMockClientAPI(ctrl)
clientAPI.EXPECT().
List(gomock.Any()).
Return(&management.ClientList{
Clients: []*management.Client{
{
Name: auth0.String("some-name"),
ClientID: auth0.String("some-id"),
Callbacks: stringToInterfaceSlice([]string{"http://localhost"}),
ClientSecret: auth0.String("secret-here"),
},
},
}, nil)

expectTable(t, stdout.String(),
[]string{"CLIENT ID", "NAME", "TYPE"},
[][]string{
{"some-id", "some-name", "Generic"},
},
)
stdout := &bytes.Buffer{}

// Step 2: Setup our cli context. The important bits are
// renderer and api.
cli := &cli{
renderer: &display.Renderer{
MessageWriter: ioutil.Discard,
ResultWriter: stdout,
},
api: &auth0.API{Client: clientAPI},
}

cmd := listAppsCmd(cli)
cmd.SetArgs(test.args)

if err := cmd.Execute(); err != nil {
t.Fatal(err)
}

test.assertOutput(t, stdout.String())
})
}
}
78 changes: 52 additions & 26 deletions internal/display/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,36 +95,36 @@ func (v *applicationView) KeyValues() [][]string {

if v.revealSecret {
return [][]string{
[]string{"CLIENT ID", ansi.Faint(v.ClientID)},
[]string{"NAME", v.Name},
[]string{"DESCRIPTION", v.Description},
[]string{"TYPE", applyColor(v.Type)},
[]string{"CLIENT SECRET", ansi.Italic(v.ClientSecret)},
[]string{"CALLBACKS", callbacks},
[]string{"ALLOWED LOGOUT URLS", allowedLogoutURLs},
[]string{"ALLOWED ORIGINS", allowedOrigins},
[]string{"ALLOWED WEB ORIGINS", allowedWebOrigins},
[]string{"TOKEN ENDPOINT AUTH", v.AuthMethod},
[]string{"GRANTS", grants},
{"CLIENT ID", ansi.Faint(v.ClientID)},
{"NAME", v.Name},
{"DESCRIPTION", v.Description},
{"TYPE", applyColor(v.Type)},
{"CLIENT SECRET", ansi.Italic(v.ClientSecret)},
{"CALLBACKS", callbacks},
{"ALLOWED LOGOUT URLS", allowedLogoutURLs},
{"ALLOWED ORIGINS", allowedOrigins},
{"ALLOWED WEB ORIGINS", allowedWebOrigins},
{"TOKEN ENDPOINT AUTH", v.AuthMethod},
{"GRANTS", grants},
}
}

return [][]string{
[]string{"CLIENT ID", ansi.Faint(v.ClientID)},
[]string{"NAME", v.Name},
[]string{"DESCRIPTION", v.Description},
[]string{"TYPE", applyColor(v.Type)},
[]string{"CALLBACKS", callbacks},
[]string{"ALLOWED LOGOUT URLS", allowedLogoutURLs},
[]string{"ALLOWED ORIGINS", allowedOrigins},
[]string{"ALLOWED WEB ORIGINS", allowedWebOrigins},
[]string{"TOKEN ENDPOINT AUTH", v.AuthMethod},
[]string{"GRANTS", grants},
{"CLIENT ID", ansi.Faint(v.ClientID)},
{"NAME", v.Name},
{"DESCRIPTION", v.Description},
{"TYPE", applyColor(v.Type)},
{"CALLBACKS", callbacks},
{"ALLOWED LOGOUT URLS", allowedLogoutURLs},
{"ALLOWED ORIGINS", allowedOrigins},
{"ALLOWED WEB ORIGINS", allowedWebOrigins},
{"TOKEN ENDPOINT AUTH", v.AuthMethod},
{"GRANTS", grants},
}
}

func (v *applicationView) Object() interface{} {
return v.raw
return safeRaw(v.raw.(*management.Client), v.revealSecret)
}

// applicationListView is a slimmed down view of a client for displaying
Expand All @@ -133,13 +133,13 @@ type applicationListView struct {
Name string
Type string
ClientID string
ClientSecret string
ClientSecret string `json:"ClientSecret,omitempty"`
revealSecret bool
}

func (v *applicationListView) AsTableHeader() []string {
if v.revealSecret {
return []string{"ClientID", "Name", "Type", "Client Secret"}
return []string{"Client ID", "Name", "Type", "Client Secret"}
}
return []string{"Client ID", "Name", "Type"}
}
Expand All @@ -160,7 +160,7 @@ func (v *applicationListView) AsTableRow() []string {
}
}

func (r *Renderer) ApplicationList(clients []*management.Client) {
func (r *Renderer) ApplicationList(clients []*management.Client, revealSecrets bool) {
resource := "applications"

r.Heading(resource)
Expand All @@ -176,11 +176,19 @@ func (r *Renderer) ApplicationList(clients []*management.Client) {
if auth0.StringValue(c.Name) == deprecatedAppName {
continue
}

// in case of format=JSON:
clientSecret := ""
if revealSecrets {
clientSecret = auth0.StringValue(c.ClientSecret)
}

res = append(res, &applicationListView{
revealSecret: revealSecrets,
Name: auth0.StringValue(c.Name),
Type: appTypeFor(c.AppType),
ClientID: auth0.StringValue(c.ClientID),
ClientSecret: auth0.StringValue(c.ClientSecret),
ClientSecret: clientSecret,
})
}

Expand Down Expand Up @@ -212,6 +220,10 @@ func (r *Renderer) ApplicationShow(client *management.Client, revealSecrets bool
func (r *Renderer) ApplicationCreate(client *management.Client, revealSecrets bool) {
r.Heading("application created")

if !revealSecrets {
client.ClientSecret = auth0.String("")
}

v := &applicationView{
revealSecret: revealSecrets,
Name: auth0.StringValue(client.Name),
Expand Down Expand Up @@ -247,6 +259,10 @@ func (r *Renderer) ApplicationCreate(client *management.Client, revealSecrets bo
func (r *Renderer) ApplicationUpdate(client *management.Client, revealSecrets bool) {
r.Heading("application updated")

if !revealSecrets {
client.ClientSecret = auth0.String("")
}

v := &applicationView{
revealSecret: revealSecrets,
Name: auth0.StringValue(client.Name),
Expand Down Expand Up @@ -328,3 +344,13 @@ func applyColor(a string) string {
return a
}
}

func safeRaw(c *management.Client, revealSecrets bool) *management.Client {
if revealSecrets {
return c
}

c.ClientSecret = nil
c.SigningKeys = nil
return c
}
6 changes: 3 additions & 3 deletions internal/display/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func (v *roleView) AsTableRow() []string {

func (v *roleView) KeyValues() [][]string {
return [][]string{
[]string{"ID", ansi.Faint(v.ID)},
[]string{"NAME", v.Name},
[]string{"DESCRIPTION", v.Description},
{"ID", ansi.Faint(v.ID)},
{"NAME", v.Name},
{"DESCRIPTION", v.Description},
}
}

Expand Down
Loading

0 comments on commit 67a94e6

Please sign in to comment.