Skip to content

Commit

Permalink
feat(cli): significantly improved create client
Browse files Browse the repository at this point in the history
This patch adds output formats to `hydra create client` and makes all client fields configurable as flags.

Closes #3091
  • Loading branch information
aeneasr committed Jun 27, 2022
1 parent 25e55ad commit 35ba1e6
Show file tree
Hide file tree
Showing 30 changed files with 641 additions and 262 deletions.
2 changes: 2 additions & 0 deletions .schema/openapi/patches/common.yaml
@@ -1,2 +1,4 @@
- op: remove
path: /components/schemas/jsonPatch/properties/value/type
- op: remove
path: /components/schemas/JSONRawMessage/type
21 changes: 21 additions & 0 deletions cmd/.snapshots/TestCreateClient-case=creates_successfully.json
@@ -0,0 +1,21 @@
{
"client_name": "",
"client_secret_expires_at": 0,
"client_uri": "",
"grant_types": [
"authorization_code"
],
"logo_uri": "",
"metadata": {},
"owner": "",
"policy_uri": "",
"request_object_signing_alg": "RS256",
"response_types": [
"code"
],
"scope": "offline_access offline openid",
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"tos_uri": "",
"userinfo_signed_response_alg": "none"
}
27 changes: 27 additions & 0 deletions cmd/.snapshots/TestCreateClient-case=supports_encryption.json
@@ -0,0 +1,27 @@
{
"audience": [
"https://www.ory.sh/audience1",
"https://www.ory.sh/audience2"
],
"client_name": "",
"client_secret_expires_at": 0,
"client_uri": "",
"grant_types": [
"authorization_code"
],
"logo_uri": "",
"metadata": {
"foo": "bar"
},
"owner": "",
"policy_uri": "",
"request_object_signing_alg": "RS256",
"response_types": [
"code"
],
"scope": "offline_access offline openid",
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"tos_uri": "",
"userinfo_signed_response_alg": "none"
}
27 changes: 27 additions & 0 deletions cmd/.snapshots/TestCreateClient-case=supports_setting_flags.json
@@ -0,0 +1,27 @@
{
"audience": [
"https://www.ory.sh/audience1",
"https://www.ory.sh/audience2"
],
"client_name": "",
"client_secret_expires_at": 0,
"client_uri": "",
"grant_types": [
"authorization_code"
],
"logo_uri": "",
"metadata": {
"foo": "bar"
},
"owner": "",
"policy_uri": "",
"request_object_signing_alg": "RS256",
"response_types": [
"code"
],
"scope": "offline_access offline openid",
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"tos_uri": "",
"userinfo_signed_response_alg": "none"
}
2 changes: 1 addition & 1 deletion cmd/cli/error.go
Expand Up @@ -5,7 +5,7 @@ import (
"encoding/json"
)

func formatSwaggerError(err error) string {
func FormatSwaggerError(err error) string {
if err == nil {
return ""
}
Expand Down
102 changes: 12 additions & 90 deletions cmd/cli/handler_client.go
Expand Up @@ -23,15 +23,13 @@ package cli
import (
"encoding/json"
"fmt"
"github.com/go-openapi/strfmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/ory/hydra/internal/httpclient/client/admin"
"github.com/ory/hydra/internal/httpclient/models"
"github.com/ory/hydra/x"
"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"
"github.com/ory/x/pointerx"
Expand All @@ -45,9 +43,9 @@ func newClientHandler() *ClientHandler {

func (h *ClientHandler) ImportClients(cmd *cobra.Command, args []string) {
cmdx.MinArgs(cmd, args, 1)
m := configureClient(cmd)
m := ConfigureClient(cmd)

ek, encryptSecret, err := newEncryptionKey(cmd, nil)
ek, encryptSecret, err := NewEncryptionKey(cmd, nil)
cmdx.Must(err, "Failed to load encryption key: %s", err)

for _, path := range args {
Expand All @@ -59,7 +57,7 @@ func (h *ClientHandler) ImportClients(cmd *cobra.Command, args []string) {
cmdx.Must(err, "Could not parse JSON from file %s: %s", path, err)

response, err := m.Admin.CreateOAuth2Client(admin.NewCreateOAuth2ClientParams().WithBody(&c))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
result := response.Payload

if c.ClientSecret == "" {
Expand All @@ -82,85 +80,9 @@ func (h *ClientHandler) ImportClients(cmd *cobra.Command, args []string) {
}
}

func (h *ClientHandler) CreateClient(cmd *cobra.Command, args []string) {
var err error
m := configureClient(cmd)
secret := flagx.MustGetString(cmd, "secret")

var echoSecret bool
if secret == "" {
var secretb []byte
secretb, err = x.GenerateSecret(26)
cmdx.Must(err, "Could not generate OAuth 2.0 Client Secret: %s", err)
secret = string(secretb)

echoSecret = true
} else {
fmt.Println("You should not provide secrets using command line flags, the secret might leak to bash history and similar systems")
}

ek, encryptSecret, err := newEncryptionKey(cmd, nil)
cmdx.Must(err, "Failed to load encryption key: %s", err)

cc := models.OAuth2Client{
AllowedCorsOrigins: flagx.MustGetStringSlice(cmd, "allowed-cors-origin"),
Audience: flagx.MustGetStringSlice(cmd, "audience"),
BackchannelLogoutSessionRequired: flagx.MustGetBool(cmd, "backchannel-logout-session-required"),
BackchannelLogoutURI: flagx.MustGetString(cmd, "backchannel-logout-callback"),
ClientName: flagx.MustGetString(cmd, "name"),
ClientSecret: secret,
ClientURI: flagx.MustGetString(cmd, "client-uri"),
Contacts: flagx.MustGetStringSlice(cmd, "contact"),
FrontchannelLogoutSessionRequired: flagx.MustGetBool(cmd, "frontchannel-logout-session-required"),
FrontchannelLogoutURI: flagx.MustGetString(cmd, "frontchannel-logout-callback"),
GrantTypes: flagx.MustGetStringSlice(cmd, "grant-type"),
JwksURI: flagx.MustGetString(cmd, "jwks-uri"),
LogoURI: flagx.MustGetString(cmd, "logo-uri"),
Metadata: flagx.MustGetString(cmd, "metadata"),
Owner: flagx.MustGetString(cmd, "owner"),
PolicyURI: flagx.MustGetString(cmd, "policy-uri"),
PostLogoutRedirectUris: flagx.MustGetStringSlice(cmd, "post-logout-callback"),
RedirectUris: flagx.MustGetStringSlice(cmd, "redirect-uri"),
RequestObjectSigningAlg: flagx.MustGetString(cmd, "request-object-signing-alg"),
RequestUris: flagx.MustGetStringSlice(cmd, "request-uri"),
ResponseTypes: flagx.MustGetStringSlice(cmd, "response-type"),
Scope: strings.Join(flagx.MustGetStringSlice(cmd, "scope"), " "),
SectorIdentifierURI: "",
SubjectType: flagx.MustGetString(cmd, "subject-type"),
TokenEndpointAuthMethod: flagx.MustGetString(cmd, "token-endpoint-auth-method"),
TosURI: flagx.MustGetString(cmd, "tos-uri"),
UpdatedAt: strfmt.DateTime{},
}

response, err := m.Admin.CreateOAuth2Client(admin.NewCreateOAuth2ClientParams().WithBody(&cc))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
result := response.Payload

fmt.Printf("OAuth 2.0 Client ID: %s\n", result.ClientID)
if result.ClientSecret == "" {
fmt.Println("This OAuth 2.0 Client has no secret")
} else {
if echoSecret {
if encryptSecret {
enc, err := ek.Encrypt([]byte(result.ClientSecret))
if err == nil {
fmt.Printf("OAuth 2.0 Encrypted Client Secret: %s\n", enc.Base64Encode())
return
}

// executes this at last to print raw client secret
// because if executes immediately, nobody knows client secret
defer cmdx.Must(err, "Failed to encrypt client secret: %s", err)
}

fmt.Printf("OAuth 2.0 Client Secret: %s\n", result.ClientSecret)
}
}
}

func (h *ClientHandler) UpdateClient(cmd *cobra.Command, args []string) {
cmdx.ExactArgs(cmd, args, 1)
m := configureClient(cmd)
m := ConfigureClient(cmd)
newSecret := flagx.MustGetString(cmd, "secret")

var echoSecret bool
Expand All @@ -169,7 +91,7 @@ func (h *ClientHandler) UpdateClient(cmd *cobra.Command, args []string) {
fmt.Println("You should not provide secrets using command line flags, the secret might leak to bash history and similar systems")
}

ek, encryptSecret, err := newEncryptionKey(cmd, nil)
ek, encryptSecret, err := NewEncryptionKey(cmd, nil)
cmdx.Must(err, "Failed to load encryption key: %s", err)

id := args[0]
Expand Down Expand Up @@ -199,7 +121,7 @@ func (h *ClientHandler) UpdateClient(cmd *cobra.Command, args []string) {
}

response, err := m.Admin.UpdateOAuth2Client(admin.NewUpdateOAuth2ClientParams().WithID(id).WithBody(&cc))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
result := response.Payload
fmt.Printf("%s OAuth 2.0 Client updated\n", result.ClientID)

Expand All @@ -221,39 +143,39 @@ func (h *ClientHandler) UpdateClient(cmd *cobra.Command, args []string) {

func (h *ClientHandler) DeleteClient(cmd *cobra.Command, args []string) {
cmdx.MinArgs(cmd, args, 1)
m := configureClient(cmd)
m := ConfigureClient(cmd)

for _, c := range args {
_, err := m.Admin.DeleteOAuth2Client(admin.NewDeleteOAuth2ClientParams().WithID(c))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
}

fmt.Println("OAuth 2.0 Client(s) deleted")
}

func (h *ClientHandler) GetClient(cmd *cobra.Command, args []string) {

m := configureClient(cmd)
m := ConfigureClient(cmd)
if len(args) == 0 {
fmt.Print(cmd.UsageString())
return
}

response, err := m.Admin.GetOAuth2Client(admin.NewGetOAuth2ClientParams().WithID(args[0]))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
cl := response.Payload
fmt.Println(cmdx.FormatResponse(cl))
}

func (h *ClientHandler) ListClients(cmd *cobra.Command, args []string) {
m := configureClient(cmd)
m := ConfigureClient(cmd)

limit := flagx.MustGetInt(cmd, "limit")
page := flagx.MustGetInt(cmd, "page")
offset := (limit * page) - limit

response, err := m.Admin.ListOAuth2Clients(admin.NewListOAuth2ClientsParams().WithLimit(pointerx.Int64(int64(limit))).WithOffset(pointerx.Int64(int64(offset))))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
cls := response.Payload

table := newTable()
Expand Down
18 changes: 12 additions & 6 deletions cmd/cli/handler_helper.go
Expand Up @@ -38,7 +38,7 @@ import (
"github.com/ory/x/flagx"
)

func configureClient(cmd *cobra.Command) *hydra.OryHydra {
func ConfigureClient(cmd *cobra.Command) *hydra.OryHydra {
return configureClientBase(cmd, true)
}

Expand Down Expand Up @@ -128,15 +128,21 @@ func newTable() *tablewriter.Table {
return table
}

// newEncryptionKey for client secret
func newEncryptionKey(cmd *cobra.Command, client *http.Client) (ek encrypta.EncryptionKey, encryptSecret bool, err error) {
const (
FlagEncryptionPGPKey = "pgp-key"
FlagEncryptionPGPKeyURL = "pgp-key-url"
FlagEncryptionKeybase = "keybase"
)

// NewEncryptionKey for client secret
func NewEncryptionKey(cmd *cobra.Command, client *http.Client) (ek encrypta.EncryptionKey, encryptSecret bool, err error) {
if client == nil {
client = http.DefaultClient
}

pgpKey := flagx.MustGetString(cmd, "pgp-key")
pgpKeyURL := flagx.MustGetString(cmd, "pgp-key-url")
keybaseUsername := flagx.MustGetString(cmd, "keybase")
pgpKey := flagx.MustGetString(cmd, FlagEncryptionPGPKey)
pgpKeyURL := flagx.MustGetString(cmd, FlagEncryptionPGPKeyURL)
keybaseUsername := flagx.MustGetString(cmd, FlagEncryptionKeybase)

if pgpKey != "" {
ek, err = encrypta.NewPublicKeyFromBase64Encoded(pgpKey)
Expand Down
4 changes: 2 additions & 2 deletions cmd/cli/handler_introspection.go
Expand Up @@ -43,7 +43,7 @@ func newIntrospectionHandler() *IntrospectionHandler {

func (h *IntrospectionHandler) Introspect(cmd *cobra.Command, args []string) {
cmdx.ExactArgs(cmd, args, 1)
c := configureClient(cmd)
c := ConfigureClient(cmd)

if clientID, clientSecret := flagx.MustGetString(cmd, "client-id"), flagx.MustGetString(cmd, "client-secret"); clientID != "" || clientSecret != "" {
_, _ = fmt.Fprintf(os.Stderr, "Flags --client-id and --client-secret and environment variables OAUTH2_CLIENT_SECRET and OAUTH2_ACCESS_TOKEN are deprecated and have no longer any effect.")
Expand All @@ -53,6 +53,6 @@ func (h *IntrospectionHandler) Introspect(cmd *cobra.Command, args []string) {
WithToken(args[0]).
WithScope(pointerx.String(strings.Join(flagx.MustGetStringSlice(cmd, "scope"), " "))),
)
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
fmt.Println(formatResponse(result.Payload))
}
12 changes: 6 additions & 6 deletions cmd/cli/handler_jwk.go
Expand Up @@ -50,7 +50,7 @@ func newJWKHandler() *JWKHandler {

func (h *JWKHandler) CreateKeys(cmd *cobra.Command, args []string) {
cmdx.RangeArgs(cmd, args, []int{1, 2})
m := configureClient(cmd)
m := ConfigureClient(cmd)

var kid string
if len(args) == 2 {
Expand All @@ -62,7 +62,7 @@ func (h *JWKHandler) CreateKeys(cmd *cobra.Command, args []string) {
Kid: pointerx.String(kid),
Use: pointerx.String(flagx.MustGetString(cmd, "use")),
}))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
fmt.Println(formatResponse(res.Payload))
}

Expand Down Expand Up @@ -189,18 +189,18 @@ func (h *JWKHandler) ImportKeys(cmd *cobra.Command, args []string) {

func (h *JWKHandler) GetKeys(cmd *cobra.Command, args []string) {
cmdx.ExactArgs(cmd, args, 1)
m := configureClient(cmd)
m := ConfigureClient(cmd)

keys, err := m.Admin.GetJSONWebKeySet(admin.NewGetJSONWebKeySetParams().WithSet(args[0]))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
fmt.Printf("%s\n", formatResponse(keys))
}

func (h *JWKHandler) DeleteKeys(cmd *cobra.Command, args []string) {
cmdx.ExactArgs(cmd, args, 1)
m := configureClient(cmd)
m := ConfigureClient(cmd)

_, err := m.Admin.DeleteJSONWebKeySet(admin.NewDeleteJSONWebKeySetParams().WithSet(args[0]))
cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err))
cmdx.Must(err, "The request failed with the following error message:\n%s", FormatSwaggerError(err))
fmt.Printf("JSON Web Key Set deleted: %s\n", args[0])
}

0 comments on commit 35ba1e6

Please sign in to comment.