Skip to content

Commit

Permalink
refactor: hydra token introspect command
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The `hydra token introspect` command has been renamed to `hydra introspect token` and now supports structured output (JSON, tables, ...).
  • Loading branch information
aeneasr committed Sep 7, 2022
1 parent aa338e1 commit da3e2b4
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 104 deletions.
15 changes: 4 additions & 11 deletions cmd/cmd_delete_tokens_test.go
Expand Up @@ -2,28 +2,21 @@ package cmd_test

import (
"fmt"
"github.com/gofrs/uuid"
"github.com/ory/hydra/client"
"strings"
"testing"

"github.com/ory/hydra/cmd"
"github.com/ory/x/cmdx"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"

"github.com/ory/hydra/cmd"
"github.com/ory/x/cmdx"
)

func TestDeleteAccessTokensCmd(t *testing.T) {
c := cmd.NewDeleteAccessTokensCmd(new(cobra.Command))

reg := setup(t, c)

expected := createClient(t, reg, &client.Client{
GrantTypes: []string{"client_credentials"},
TokenEndpointAuthMethod: "client_secret_post",
Secret: uuid.Must(uuid.NewV4()).String()},
)

expected := createClientCredentialsClient(t, reg)
t.Run("case=deletes tokens", func(t *testing.T) {
stdout := cmdx.ExecNoErr(t, c, expected.GetID())
assert.Equal(t, fmt.Sprintf(`"%s"`, expected.GetID()), strings.TrimSpace(stdout))
Expand Down
1 change: 0 additions & 1 deletion cmd/cmd_get_client_test.go
Expand Up @@ -21,7 +21,6 @@ func TestGetClient(t *testing.T) {
reg := setup(t, c)

expected := createClient(t, reg, nil)

t.Run("case=gets client", func(t *testing.T) {
actual := gjson.Parse(cmdx.ExecNoErr(t, c, expected.ID.String()))
assert.NotEmpty(t, actual.Get("client_id").String())
Expand Down
8 changes: 8 additions & 0 deletions cmd/cmd_helper_test.go
Expand Up @@ -62,6 +62,14 @@ var snapshotExcludedClientFields = []snapshotx.ExceptOpt{
snapshotx.ExceptNestedKeys("updated_at"),
}

func createClientCredentialsClient(t *testing.T, reg driver.Registry) *client.Client {
return createClient(t, reg, &client.Client{
GrantTypes: []string{"client_credentials"},
TokenEndpointAuthMethod: "client_secret_basic",
Secret: uuid.Must(uuid.NewV4()).String(),
})
}

func createClient(t *testing.T, reg driver.Registry, c *client.Client) *client.Client {
if c == nil {
c = &client.Client{TokenEndpointAuthMethod: "client_secret_post", Secret: uuid.Must(uuid.NewV4()).String()}
Expand Down
19 changes: 19 additions & 0 deletions cmd/cmd_introspect.go
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/spf13/cobra"

"github.com/ory/x/cmdx"
)

func NewIntrospectCmd(root *cobra.Command) *cobra.Command {
var cmd = &cobra.Command{
Use: "introspect",
Aliases: []string{"ls"},
Short: "Introspect resources",
}
cmd.AddCommand(NewIntrospectTokenCmd(root))
cmdx.RegisterHTTPClientFlags(cmd.PersistentFlags())
cmdx.RegisterFormatFlags(cmd.PersistentFlags())
return cmd
}
59 changes: 59 additions & 0 deletions cmd/cmd_introspect_token.go
@@ -0,0 +1,59 @@
/*
* Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Aeneas Rekkas <aeneas+oss@aeneas.io>
* @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
* @license Apache-2.0
*/

package cmd

import (
"fmt"
"strings"

"github.com/ory/hydra/cmd/cliclient"
"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"

"github.com/spf13/cobra"
)

func NewIntrospectTokenCmd(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "token the-token",
Args: cobra.ExactArgs(1),
Example: fmt.Sprintf(`%s introspect token --client-id a0184d6c-b313-4e70-a0b9-905b581e9218 --client-secret Hh1BjioNNm ciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNT`, parent.Use),
Short: "Introspect an OAuth 2.0 Access or Refresh Token",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := cliclient.NewClient(cmd)
if err != nil {
return err
}

result, _, err := client.AdminApi.IntrospectOAuth2Token(cmd.Context()).
Token(args[0]).
Scope(strings.Join(flagx.MustGetStringSlice(cmd, "scope"), " ")).Execute()
if err != nil {
return cmdx.PrintOpenAPIError(cmd, err)
}

cmdx.PrintRow(cmd, outputOAuth2TokenIntrospection(*result))
return nil
},
}
cmd.Flags().StringSlice("scope", []string{}, "Additionally check if the scope was granted.")
return cmd
}
47 changes: 47 additions & 0 deletions cmd/cmd_introspect_token_test.go
@@ -0,0 +1,47 @@
package cmd_test

import (
"context"
"testing"

"golang.org/x/oauth2/clientcredentials"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

"github.com/ory/hydra/cmd"
"github.com/ory/x/cmdx"
)

func TestIntrospectToken(t *testing.T) {
c := cmd.NewIntrospectTokenCmd(new(cobra.Command))
public, admin, reg := setupRoutes(t, c)
require.NoError(t, c.Flags().Set(cmdx.FlagEndpoint, admin.URL))

expected := createClientCredentialsClient(t, reg)
cc := clientcredentials.Config{
ClientID: expected.GetID(),
ClientSecret: expected.Secret,
TokenURL: public.URL + "/oauth2/token",
Scopes: []string{},
}

t.Run("case=checks valid token", func(t *testing.T) {
token, err := cc.Token(context.Background())
require.NoError(t, err)

actual := gjson.Parse(cmdx.ExecNoErr(t, c, token.AccessToken))
assert.Equal(t, expected.GetID(), actual.Get("sub").String())
assert.Equal(t, expected.GetID(), actual.Get("client_id").String())
assert.True(t, actual.Get("active").Bool())
})

t.Run("case=checks invalid token", func(t *testing.T) {
actual := gjson.Parse(cmdx.ExecNoErr(t, c, "invalid-token"))
assert.Empty(t, actual.Get("sub").String())
assert.Empty(t, actual.Get("client_id").String())
assert.False(t, actual.Get("active").Bool())
})
}
10 changes: 1 addition & 9 deletions cmd/cmd_perform_client_credentials_test.go
Expand Up @@ -3,11 +3,8 @@ package cmd_test
import (
"testing"

"github.com/gofrs/uuid"
"github.com/stretchr/testify/require"

"github.com/ory/hydra/client"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
Expand All @@ -21,12 +18,7 @@ func TestPerformClientCredentialsGrant(t *testing.T) {
public, _, reg := setupRoutes(t, c)
require.NoError(t, c.Flags().Set(cmdx.FlagEndpoint, public.URL))

expected := createClient(t, reg, &client.Client{
GrantTypes: []string{"client_credentials"},
TokenEndpointAuthMethod: "client_secret_post",
Secret: uuid.Must(uuid.NewV4()).String()},
)

expected := createClientCredentialsClient(t, reg)
t.Run("case=exchanges for access token", func(t *testing.T) {
result := cmdx.ExecNoErr(t, c, "--client-id", expected.ID.String(), "--client-secret", expected.Secret)
actual := gjson.Parse(result)
Expand Down
1 change: 0 additions & 1 deletion cmd/cmd_update_client_test.go
Expand Up @@ -21,7 +21,6 @@ func TestUpdateClient(t *testing.T) {
reg := setup(t, c)

original := createClient(t, reg, nil)

t.Run("case=creates successfully", func(t *testing.T) {
actual := gjson.Parse(cmdx.ExecNoErr(t, c, "--grant-type", "implicit", original.ID.String()))
expected, err := reg.ClientManager().GetClient(ctx, actual.Get("client_id").String())
Expand Down
32 changes: 32 additions & 0 deletions cmd/output_introspection.go
@@ -0,0 +1,32 @@
package cmd

import (
"fmt"
"time"

hydra "github.com/ory/hydra-client-go"
)

type (
outputOAuth2TokenIntrospection hydra.OAuth2TokenIntrospection
)

func (_ outputOAuth2TokenIntrospection) Header() []string {
return []string{"ACTIVE", "SUBJECT", "CLIENT ID", "SCOPE", "EXPIRY", "TOKEN USE"}
}

func (i outputOAuth2TokenIntrospection) Columns() []string {
return []string{
fmt.Sprintf("%v", i.Active),
fmt.Sprintf("%v", i.Sub),
fmt.Sprintf("%v", i.ClientId),
fmt.Sprintf("%v", i.Scope),
fmt.Sprintf("%v", i.Scope),
fmt.Sprintf("%v", i.TokenUse),
fmt.Sprintf("%v", time.Unix(*i.Exp, 0).String()),
}
}

func (i outputOAuth2TokenIntrospection) Interface() interface{} {
return i
}
2 changes: 1 addition & 1 deletion cmd/output_token.go
Expand Up @@ -11,7 +11,7 @@ type (
)

func (_ outputOAuth2Token) Header() []string {
return []string{"ACCESS TOKEN", "REFRESH TOKEN", "ID TOKEN", "EXPIRES IN"}
return []string{"ACCESS TOKEN", "REFRESH TOKEN", "ID TOKEN", "EXPIRY"}
}

func (i outputOAuth2Token) Columns() []string {
Expand Down
11 changes: 6 additions & 5 deletions cmd/root.go
Expand Up @@ -71,6 +71,12 @@ func RegisterCommandRecursive(parent *cobra.Command) {
performCmd.AddCommand(NewPerformClientCredentialsCmd(parent))
performCmd.AddCommand(NewPerformAuthorizationCodeCmd(parent))

revokeCmd := NewRevokeCmd(parent)
revokeCmd.AddCommand(NewRevokeTokenCmd(parent))

introspectCmd := NewIntrospectCmd(parent)
introspectCmd.AddCommand(NewIntrospectTokenCmd(parent))

parent.AddCommand(NewJanitorCmd())

migrateCmd := NewMigrateCmd()
Expand All @@ -84,11 +90,6 @@ func RegisterCommandRecursive(parent *cobra.Command) {
serveCmd.AddCommand(NewServePublicCmd())
serveCmd.AddCommand(NewServeAllCmd())

tokenCmd := NewTokenCmd()
parent.AddCommand(tokenCmd)
tokenCmd.AddCommand(NewTokenIntrospectCmd())
tokenCmd.AddCommand(NewTokenRevokeCmd())

parent.AddCommand(NewVersionCmd())
}

Expand Down
33 changes: 0 additions & 33 deletions cmd/token.go

This file was deleted.

43 changes: 0 additions & 43 deletions cmd/token_introspect.go

This file was deleted.

0 comments on commit da3e2b4

Please sign in to comment.