Skip to content

Commit

Permalink
feat: update clients from files through the CLI (#3874)
Browse files Browse the repository at this point in the history
  • Loading branch information
zepatrik authored Nov 4, 2024
1 parent 3164970 commit f777fd1
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 25 deletions.
6 changes: 2 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ linters:
- goimports
disable:
- ineffassign
- deadcode
- unused
- structcheck

run:
skip-files:
issues:
exclude-files:
- ".+_test.go"
- ".+_test_.+.go"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"client_name": "updated through file from disk",
"client_secret_expires_at": 0,
"client_uri": "",
"grant_types": [
"implicit"
],
"jwks": {},
"logo_uri": "",
"metadata": {},
"owner": "",
"policy_uri": "",
"request_object_signing_alg": "RS256",
"response_types": [
"code"
],
"scope": "offline_access offline openid",
"skip_consent": false,
"skip_logout_consent": false,
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"tos_uri": "",
"userinfo_signed_response_alg": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"client_name": "updated through file stdin",
"client_secret_expires_at": 0,
"client_uri": "",
"grant_types": [
"implicit"
],
"jwks": {},
"logo_uri": "",
"metadata": {},
"owner": "",
"policy_uri": "",
"request_object_signing_alg": "RS256",
"response_types": [
"code"
],
"scope": "offline_access offline openid",
"skip_consent": false,
"skip_logout_consent": false,
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"tos_uri": "",
"userinfo_signed_response_alg": "none"
}
7 changes: 6 additions & 1 deletion cmd/cmd_create_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
)

const (
flagFile = "file"

flagClientAccessTokenStrategy = "access-token-strategy"
flagClientAllowedCORSOrigin = "allowed-cors-origin"
flagClientAudience = "audience"
Expand Down Expand Up @@ -87,7 +89,10 @@ To encrypt an auto-generated OAuth2 Client Secret, use flags ` + "`--pgp-key`" +
}

secret := flagx.MustGetString(cmd, flagClientSecret)
cl := clientFromFlags(cmd)
cl, err := clientFromFlags(cmd)
if err != nil {
return err
}
cl.ClientId = pointerx.Ptr(flagx.MustGetString(cmd, flagClientId))

//nolint:bodyclose
Expand Down
25 changes: 23 additions & 2 deletions cmd/cmd_helper_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package cmd

import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
Expand All @@ -16,7 +18,24 @@ import (
"github.com/ory/x/pointerx"
)

func clientFromFlags(cmd *cobra.Command) hydra.OAuth2Client {
func clientFromFlags(cmd *cobra.Command) (hydra.OAuth2Client, error) {
if filename := flagx.MustGetString(cmd, flagFile); filename != "" {
src := cmd.InOrStdin()
if filename != "-" {
f, err := os.Open(filename)
if err != nil {
return hydra.OAuth2Client{}, fmt.Errorf("unable to open file %q: %w", filename, err)
}
defer f.Close()
src = f
}
client := hydra.OAuth2Client{}
if err := json.NewDecoder(src).Decode(&client); err != nil {
return hydra.OAuth2Client{}, fmt.Errorf("unable to decode JSON: %w", err)
}
return client, nil
}

return hydra.OAuth2Client{
AccessTokenStrategy: pointerx.Ptr(flagx.MustGetString(cmd, flagClientAccessTokenStrategy)),
AllowedCorsOrigins: flagx.MustGetStringSlice(cmd, flagClientAllowedCORSOrigin),
Expand Down Expand Up @@ -47,7 +66,7 @@ func clientFromFlags(cmd *cobra.Command) hydra.OAuth2Client {
SubjectType: pointerx.Ptr(flagx.MustGetString(cmd, flagClientSubjectType)),
TokenEndpointAuthMethod: pointerx.Ptr(flagx.MustGetString(cmd, flagClientTokenEndpointAuthMethod)),
TosUri: pointerx.Ptr(flagx.MustGetString(cmd, flagClientTOSURI)),
}
}, nil
}

func registerEncryptFlags(flags *pflag.FlagSet) {
Expand All @@ -58,6 +77,8 @@ func registerEncryptFlags(flags *pflag.FlagSet) {
}

func registerClientFlags(flags *pflag.FlagSet) {
flags.String(flagFile, "", "Read a JSON file representing a client from this location. If set, the other client flags are ignored.")

flags.String(flagClientMetadata, "{}", "Metadata is an arbitrary JSON String of your choosing.")
flags.String(flagClientOwner, "", "The owner of this client, typically email addresses or a user ID.")
flags.StringSlice(flagClientContact, nil, "A list representing ways to contact people responsible for this client, typically email addresses.")
Expand Down
19 changes: 9 additions & 10 deletions cmd/cmd_import_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -23,23 +24,21 @@ import (

func writeTempFile(t *testing.T, contents interface{}) string {
t.Helper()
ij, err := json.Marshal(contents)
require.NoError(t, err)
f, err := os.CreateTemp(t.TempDir(), "")
require.NoError(t, err)
_, err = f.Write(ij)
fn := filepath.Join(t.TempDir(), "content.json")
f, err := os.Create(fn)
require.NoError(t, err)
require.NoError(t, json.NewEncoder(f).Encode(contents))
require.NoError(t, f.Close())
return f.Name()
return fn
}

func TestImportClient(t *testing.T) {
ctx := context.Background()
c := cmd.NewImportClientCmd()
reg := setup(t, c)

file1 := writeTempFile(t, []hydra.OAuth2Client{{Scope: pointerx.String("foo")}, {Scope: pointerx.String("bar"), ClientSecret: pointerx.String("some-secret")}})
file2 := writeTempFile(t, []hydra.OAuth2Client{{Scope: pointerx.String("baz")}, {Scope: pointerx.String("zab"), ClientSecret: pointerx.String("some-secret")}})
file1 := writeTempFile(t, []hydra.OAuth2Client{{Scope: pointerx.Ptr("foo")}, {Scope: pointerx.Ptr("bar"), ClientSecret: pointerx.Ptr("some-secret")}})
file2 := writeTempFile(t, []hydra.OAuth2Client{{Scope: pointerx.Ptr("baz")}, {Scope: pointerx.Ptr("zab"), ClientSecret: pointerx.Ptr("some-secret")}})

t.Run("case=imports clients from single file", func(t *testing.T) {
actual := gjson.Parse(cmdx.ExecNoErr(t, c, file1))
Expand Down Expand Up @@ -77,7 +76,7 @@ func TestImportClient(t *testing.T) {

t.Run("case=imports clients from multiple files and stdin", func(t *testing.T) {
var stdin bytes.Buffer
require.NoError(t, json.NewEncoder(&stdin).Encode([]hydra.OAuth2Client{{Scope: pointerx.String("oof")}, {Scope: pointerx.String("rab"), ClientSecret: pointerx.String("some-secret")}}))
require.NoError(t, json.NewEncoder(&stdin).Encode([]hydra.OAuth2Client{{Scope: pointerx.Ptr("oof")}, {Scope: pointerx.Ptr("rab"), ClientSecret: pointerx.Ptr("some-secret")}}))

stdout, _, err := cmdx.Exec(t, c, &stdin, file1, file2)
require.NoError(t, err)
Expand All @@ -93,7 +92,7 @@ func TestImportClient(t *testing.T) {
})

t.Run("case=performs appropriate error reporting", func(t *testing.T) {
file3 := writeTempFile(t, []hydra.OAuth2Client{{ClientSecret: pointerx.String("short")}})
file3 := writeTempFile(t, []hydra.OAuth2Client{{ClientSecret: pointerx.Ptr("short")}})
stdout, stderr, err := cmdx.Exec(t, c, nil, file1, file3)
require.Error(t, err)
actual := gjson.Parse(stdout)
Expand Down
9 changes: 6 additions & 3 deletions cmd/cmd_update_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@ To encrypt an auto-generated OAuth2 Client Secret, use flags ` + "`--pgp-key`" +
}

id := args[0]
cc := clientFromFlags(cmd)
cc, err := clientFromFlags(cmd)
if err != nil {
return err
}

client, _, err := m.OAuth2API.SetOAuth2Client(context.Background(), id).OAuth2Client(cc).Execute() //nolint:bodyclose
if err != nil {
return cmdx.PrintOpenAPIError(cmd, err)
}

if client.ClientSecret == nil && len(secret) > 0 {
client.ClientSecret = pointerx.String(secret)
client.ClientSecret = pointerx.Ptr(secret)
}

if encryptSecret && client.ClientSecret != nil {
Expand All @@ -60,7 +63,7 @@ To encrypt an auto-generated OAuth2 Client Secret, use flags ` + "`--pgp-key`" +
return cmdx.FailSilently(cmd)
}

client.ClientSecret = pointerx.String(enc.Base64Encode())
client.ClientSecret = pointerx.Ptr(enc.Base64Encode())
}

cmdx.PrintRow(cmd, (*outputOAuth2Client)(client))
Expand Down
52 changes: 47 additions & 5 deletions cmd/cmd_update_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
package cmd_test

import (
"bytes"
"context"
"encoding/json"
"testing"

"github.com/tidwall/sjson"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
Expand All @@ -25,11 +28,11 @@ func TestUpdateClient(t *testing.T) {
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.GetID()))
expected, err := reg.ClientManager().GetClient(ctx, actual.Get("client_id").String())
expected, err := reg.ClientManager().GetClient(ctx, actual.Get("client_id").Str)
require.NoError(t, err)

assert.Equal(t, expected.GetID(), actual.Get("client_id").String())
assert.Equal(t, "implicit", actual.Get("grant_types").Array()[0].String())
assert.Equal(t, expected.GetID(), actual.Get("client_id").Str)
assert.Equal(t, "implicit", actual.Get("grant_types").Array()[0].Str)
snapshotx.SnapshotT(t, json.RawMessage(actual.Raw), snapshotExcludedClientFields...)
})

Expand All @@ -39,9 +42,48 @@ func TestUpdateClient(t *testing.T) {
"--secret", "some-userset-secret",
"--pgp-key", base64EncodedPGPPublicKey(t),
))
assert.NotEmpty(t, actual.Get("client_id").String())
assert.NotEmpty(t, actual.Get("client_secret").String())
assert.Equal(t, original.ID, actual.Get("client_id").Str)
assert.NotEmpty(t, actual.Get("client_secret").Str)
assert.NotEqual(t, original.Secret, actual.Get("client_secret").Str)

snapshotx.SnapshotT(t, json.RawMessage(actual.Raw), snapshotExcludedClientFields...)
})

t.Run("case=updates from file", func(t *testing.T) {
original, err := reg.ClientManager().GetConcreteClient(ctx, original.GetID())
require.NoError(t, err)

raw, err := json.Marshal(original)
require.NoError(t, err)

t.Run("file=stdin", func(t *testing.T) {
raw, err = sjson.SetBytes(raw, "client_name", "updated through file stdin")
require.NoError(t, err)

stdout, stderr, err := cmdx.Exec(t, c, bytes.NewReader(raw), original.GetID(), "--file", "-")
require.NoError(t, err, stderr)

actual := gjson.Parse(stdout)
assert.Equal(t, original.ID, actual.Get("client_id").Str)
assert.Equal(t, "updated through file stdin", actual.Get("client_name").Str)

snapshotx.SnapshotT(t, json.RawMessage(actual.Raw), snapshotExcludedClientFields...)
})

t.Run("file=from disk", func(t *testing.T) {
raw, err = sjson.SetBytes(raw, "client_name", "updated through file from disk")
require.NoError(t, err)

fn := writeTempFile(t, json.RawMessage(raw))

stdout, stderr, err := cmdx.Exec(t, c, nil, original.GetID(), "--file", fn)
require.NoError(t, err, stderr)

actual := gjson.Parse(stdout)
assert.Equal(t, original.ID, actual.Get("client_id").Str)
assert.Equal(t, "updated through file from disk", actual.Get("client_name").Str)

snapshotx.SnapshotT(t, json.RawMessage(actual.Raw), snapshotExcludedClientFields...)
})
})
}

0 comments on commit f777fd1

Please sign in to comment.