From f490eb4fc4d58d89e04f83cc5a14b010486e0b32 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 29 Feb 2024 20:14:53 -0700 Subject: [PATCH] Refactored and added client credentials flow --- cmd/login.go | 5 ++-- cmd/root.go | 24 +++++++-------- config.yaml | 2 ++ internal/client.go | 22 ++++++++++---- internal/config.go | 13 ++++++--- .../{login.go => flows/authorization_code.go} | 15 +++------- internal/flows/client_credentials.go | 29 +++++++++++++++++++ internal/flows/login.go | 28 ++++++++++++++++++ internal/opaal.go | 16 +++++----- 9 files changed, 113 insertions(+), 41 deletions(-) rename internal/{login.go => flows/authorization_code.go} (96%) create mode 100644 internal/flows/client_credentials.go create mode 100644 internal/flows/login.go diff --git a/cmd/login.go b/cmd/login.go index 1817a7c..43cdd36 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -1,7 +1,7 @@ package cmd import ( - opaal "davidallendj/opaal/internal" + "davidallendj/opaal/internal/flows" "fmt" "os" @@ -13,7 +13,7 @@ var loginCmd = &cobra.Command{ Short: "Start the login flow", Run: func(cmd *cobra.Command, args []string) { for { - err := opaal.Login(&config) + err := flows.Login(&config) if err != nil { fmt.Printf("%v\n", err) os.Exit(1) @@ -37,5 +37,6 @@ func init() { loginCmd.Flags().BoolVar(&config.DecodeIdToken, "decode-id-token", config.DecodeIdToken, "decode and print ID token from identity provider") loginCmd.Flags().BoolVar(&config.DecodeAccessToken, "decore-access-token", config.DecodeAccessToken, "decode and print access token from authorization server") loginCmd.Flags().BoolVar(&config.RunOnce, "once", config.RunOnce, "set whether to run login once and exit") + loginCmd.Flags().StringVar(&config.GrantType, "grant-type", config.GrantType, "set the grant-type/authorization flow") rootCmd.AddCommand(loginCmd) } diff --git a/cmd/root.go b/cmd/root.go index e6b5245..eace300 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,18 @@ var rootCmd = &cobra.Command{ }, } +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "failed to start CLI: %s", err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "set the config path") +} + func initConfig() { // load config if found or create a new one if configPath != "" { @@ -35,15 +47,3 @@ func initConfig() { } } } - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "failed to start CLI: %s", err) - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "set the config path") -} diff --git a/config.yaml b/config.yaml index 992ac3b..c2530ec 100755 --- a/config.yaml +++ b/config.yaml @@ -16,6 +16,8 @@ urls: jwks_uri: http://git.towk.local:3000/login/oauth/keys login: http://127.0.0.1:4433/self-service/login/api login-flow-id: http://127.0.0.1:4433/self-service/login/flows?id={id} + register-client: http://127.0.0.1:4445/clients + authorize-client: http://127.0.0.1:4444/oauth2/authorize state: "" response-type: code decode-id-token: true diff --git a/internal/client.go b/internal/client.go index a0324df..48bb689 100644 --- a/internal/client.go +++ b/internal/client.go @@ -129,12 +129,17 @@ func (client *Client) FetchCSRFToken(flowUrl string) error { func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl string, state string) ([]byte, error) { data := url.Values{ "grant_type": {"authorization_code"}, - "code": {code}, "client_id": {client.Id}, "client_secret": {client.Secret}, - "state": {state}, "redirect_uri": {strings.Join(client.RedirectUris, ",")}, } + // add optional params if valid + if code != "" { + data["code"] = []string{code} + } + if state != "" { + data["state"] = []string{state} + } res, err := http.PostForm(remoteUrl, data) if err != nil { return nil, fmt.Errorf("failed to get ID token: %s", err) @@ -151,9 +156,16 @@ func (client *Client) FetchTokenFromAuthorizationServer(remoteUrl string, jwt st // hydra endpoint: /oauth/token data := "grant_type=" + url.QueryEscape("urn:ietf:params:oauth:grant-type:jwt-bearer") + "&client_id=" + client.Id + - "&client_secret=" + client.Secret + - "&scope=" + strings.Join(scope, "+") + - "&assertion=" + jwt + "&client_secret=" + client.Secret + + // add optional params if valid + if jwt != "" { + data += "&assertion=" + jwt + } + if scope != nil || len(scope) > 0 { + data += "&scope=" + strings.Join(scope, "+") + } + fmt.Printf("encoded params: %v\n\n", data) req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer([]byte(data))) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") diff --git a/internal/config.go b/internal/config.go index d887ed9..f0f5d49 100644 --- a/internal/config.go +++ b/internal/config.go @@ -44,10 +44,15 @@ func NewConfig() Config { ResponseType: "code", Scope: []string{"openid", "profile", "email"}, ActionUrls: ActionUrls{ - Identities: "", - AccessToken: "", - TrustedIssuers: "", - ServerConfig: "", + Identities: "", + AccessToken: "", + TrustedIssuers: "", + ServerConfig: "", + JwksUri: "", + Login: "", + LoginFlowId: "", + RegisterClient: "", + AuthorizeClient: "", }, OpenBrowser: false, DecodeIdToken: false, diff --git a/internal/login.go b/internal/flows/authorization_code.go similarity index 96% rename from internal/login.go rename to internal/flows/authorization_code.go index 048b262..cf5d5a3 100644 --- a/internal/login.go +++ b/internal/flows/authorization_code.go @@ -1,6 +1,7 @@ -package opaal +package flows import ( + opaal "davidallendj/opaal/internal" "davidallendj/opaal/internal/oidc" "encoding/json" "errors" @@ -12,15 +13,7 @@ import ( "github.com/davidallendj/go-utils/util" ) -func Login(config *Config) error { - if config == nil { - return fmt.Errorf("config is not valid") - } - - // initialize client that will be used throughout login flow - server := NewServerWithConfig(config) - client := NewClientWithConfig(config) - +func AuthorizationCode(config *opaal.Config, server *opaal.Server, client *opaal.Client) error { // initiate the login flow and get a flow ID and CSRF token { err := client.InitiateLoginFlow(config.ActionUrls.Login) @@ -49,7 +42,7 @@ func Login(config *Config) error { } // check if all appropriate parameters are set in config - if !HasRequiredParams(config) { + if !opaal.HasRequiredParams(config) { return fmt.Errorf("client ID must be set") } diff --git a/internal/flows/client_credentials.go b/internal/flows/client_credentials.go new file mode 100644 index 0000000..6c59037 --- /dev/null +++ b/internal/flows/client_credentials.go @@ -0,0 +1,29 @@ +package flows + +import ( + opaal "davidallendj/opaal/internal" + "fmt" +) + +func ClientCredentials(config *opaal.Config, server *opaal.Server, client *opaal.Client) error { + // register a new OAuth 2 client with authorization srever + _, err := client.RegisterOAuthClient(config.ActionUrls.RegisterClient, nil) + if err != nil { + return fmt.Errorf("failed to register OAuth client: %v", err) + } + + // authorize the client + _, err = client.AuthorizeClient(config.ActionUrls.AuthorizeClient) + if err != nil { + return fmt.Errorf("failed to authorize client: %v", err) + } + + // request a token from the authorization server + res, err := client.FetchTokenFromAuthorizationServer(config.ActionUrls.AccessToken, "", nil) + if err != nil { + return fmt.Errorf("failed to fetch token from authorization server: %v", err) + } + + fmt.Printf("token: %v\n", string(res)) + return nil +} diff --git a/internal/flows/login.go b/internal/flows/login.go new file mode 100644 index 0000000..1ee953b --- /dev/null +++ b/internal/flows/login.go @@ -0,0 +1,28 @@ +package flows + +import ( + opaal "davidallendj/opaal/internal" + "fmt" +) + +func Login(config *opaal.Config) error { + if config == nil { + return fmt.Errorf("config is not valid") + } + + // initialize client that will be used throughout login flow + server := opaal.NewServerWithConfig(config) + client := opaal.NewClientWithConfig(config) + + fmt.Printf("grant type: %v\n", config.GrantType) + + if config.GrantType == "authorization_code" { + AuthorizationCode(config, server, client) + } else if config.GrantType == "client_credentials" { + ClientCredentials(config, server, client) + } else { + return fmt.Errorf("invalid grant type") + } + + return nil +} diff --git a/internal/opaal.go b/internal/opaal.go index 67dcf81..ee9cb11 100644 --- a/internal/opaal.go +++ b/internal/opaal.go @@ -1,13 +1,15 @@ package opaal type ActionUrls struct { - Identities string `yaml:"identities"` - TrustedIssuers string `yaml:"trusted-issuers"` - AccessToken string `yaml:"access-token"` - ServerConfig string `yaml:"server-config"` - JwksUri string `yaml:"jwks_uri"` - Login string `yaml:"login"` - LoginFlowId string `yaml:"login-flow-id"` + Identities string `yaml:"identities"` + TrustedIssuers string `yaml:"trusted-issuers"` + AccessToken string `yaml:"access-token"` + ServerConfig string `yaml:"server-config"` + JwksUri string `yaml:"jwks_uri"` + Login string `yaml:"login"` + LoginFlowId string `yaml:"login-flow-id"` + RegisterClient string `yaml:"register-client"` + AuthorizeClient string `yaml:"authorize-client"` } func HasRequiredParams(config *Config) bool {