Skip to content

Commit

Permalink
Merge pull request davidallendj#10 from davidallendj/refactor-login
Browse files Browse the repository at this point in the history
Refactor login
  • Loading branch information
davidallendj authored Apr 23, 2024
2 parents 2e117be + 6d2f488 commit 5650cf6
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 154 deletions.
125 changes: 66 additions & 59 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ package cmd

import (
opaal "davidallendj/opaal/internal"
cache "davidallendj/opaal/internal/cache/sqlite"
"davidallendj/opaal/internal/oauth"
"davidallendj/opaal/internal/oidc"
"fmt"
"os"
"slices"

"github.com/spf13/cobra"
)
Expand All @@ -25,68 +22,68 @@ var loginCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
for {
// try and find client with valid identity provider config
var provider *oidc.IdentityProvider
if target != "" {
// only try to use client with name give
index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool {
return target == c.Name
})
if index < 0 {
fmt.Printf("could not find the target client listed by name")
os.Exit(1)
}
client := config.Authentication.Clients[index]
_, err := cache.GetIdentityProvider(config.Options.CachePath, client.Issuer)
if err != nil {
// var provider *oidc.IdentityProvider
// if target != "" {
// // only try to use client with name give
// index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool {
// return target == c.Name
// })
// if index < 0 {
// fmt.Printf("could not find the target client listed by name")
// os.Exit(1)
// }
// client := config.Authentication.Clients[index]
// _, err := cache.GetIdentityProvider(config.Options.CachePath, client.Issuer)
// if err != nil {

}
// }

} else if targetIndex >= 0 {
// only try to use client by index
targetCount := len(config.Authentication.Clients) - 1
if targetIndex > targetCount {
fmt.Printf("target index out of range (found %d)", targetCount)
}
client := config.Authentication.Clients[targetIndex]
_, err := cache.GetIdentityProvider(config.Options.CachePath, client.Issuer)
if err != nil {
// } else if targetIndex >= 0 {
// // only try to use client by index
// targetCount := len(config.Authentication.Clients) - 1
// if targetIndex > targetCount {
// fmt.Printf("target index out of range (found %d)", targetCount)
// }
// client := config.Authentication.Clients[targetIndex]
// _, err := cache.GetIdentityProvider(config.Options.CachePath, client.Issuer)
// if err != nil {

}
} else {
for _, c := range config.Authentication.Clients {
// try to get identity provider info locally first
_, err := cache.GetIdentityProvider(config.Options.CachePath, c.Issuer)
if err != nil && !config.Options.CacheOnly {
fmt.Printf("fetching config from issuer: %v\n", c.Issuer)
// try to get info remotely by fetching
provider, err = oidc.FetchServerConfig(c.Issuer)
if err != nil {
fmt.Printf("failed to fetch server config: %v\n", err)
continue
}
client = c
// fetch the provider's JWKS
err := provider.FetchJwks()
if err != nil {
fmt.Printf("failed to fetch JWKS: %v\n", err)
}
break
}
// only test the first if --run-all flag is not set
if !config.Authentication.TestAllClients {
fmt.Printf("stopping after first test...\n\n\n")
break
}
}
}
// }
// } else {
// for _, c := range config.Authentication.Clients {
// // try to get identity provider info locally first
// _, err := cache.GetIdentityProvider(config.Options.CachePath, c.Issuer)
// if err != nil && !config.Options.CacheOnly {
// fmt.Printf("fetching config from issuer: %v\n", c.Issuer)
// // try to get info remotely by fetching
// provider, err = oidc.FetchServerConfig(c.Issuer)
// if err != nil {
// fmt.Printf("failed to fetch server config: %v\n", err)
// continue
// }
// client = c
// // fetch the provider's JWKS
// err := provider.FetchJwks()
// if err != nil {
// fmt.Printf("failed to fetch JWKS: %v\n", err)
// }
// break
// }
// // only test the first if --run-all flag is not set
// if !config.Authentication.TestAllClients {
// fmt.Printf("stopping after first test...\n\n\n")
// break
// }
// }
// }

if provider == nil {
fmt.Printf("failed to retrieve provider config\n")
os.Exit(1)
}
// if provider == nil {
// fmt.Printf("failed to retrieve provider config\n")
// os.Exit(1)
// }

// start the listener
err := opaal.Login(&config, &client, provider)
err := opaal.Login(&config)
if err != nil {
fmt.Printf("%v\n", err)
os.Exit(1)
Expand Down Expand Up @@ -115,3 +112,13 @@ func init() {
loginCmd.MarkFlagsMutuallyExclusive("target.name", "target.index")
rootCmd.AddCommand(loginCmd)
}

func MakeButton(url string, text string) string {
// check if we have http:// a
html := "<input type=\"button\" "
html += "class=\"button\" "
html += fmt.Sprintf("onclick=\"window.location.href='%s';\" ", url)
html += fmt.Sprintf("value=\"%s\"", text)
return html
// return "<a href=\"" + url + "\"> " + text + "</a>"
}
36 changes: 20 additions & 16 deletions internal/flows/jwt_bearer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/rand"
"crypto/rsa"
"davidallendj/opaal/internal/oauth"
"davidallendj/opaal/internal/oidc"
"encoding/json"
"fmt"
"os"
Expand All @@ -19,14 +18,14 @@ import (
)

type JwtBearerFlowParams struct {
AccessToken string
IdToken string
IdentityProvider *oidc.IdentityProvider
TrustedIssuer *oauth.TrustedIssuer
Client *oauth.Client
Refresh bool
Verbose bool
KeyPath string
AccessToken string
IdToken string
// IdentityProvider *oidc.IdentityProvider
TrustedIssuer *oauth.TrustedIssuer
Client *oauth.Client
Refresh bool
Verbose bool
KeyPath string
}

type JwtBearerFlowEndpoints struct {
Expand All @@ -39,22 +38,27 @@ type JwtBearerFlowEndpoints struct {
func NewJwtBearerFlow(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) (string, error) {
// 1. verify that the JWT from the issuer is valid using all keys
var (
idp = params.IdentityProvider
// idp = params.IdentityProvider
accessToken = params.AccessToken
idToken = params.IdToken
client = params.Client
trustedIssuer = params.TrustedIssuer
verbose = params.Verbose
)

// pre-condition checks to make sure certain variables are set
if client == nil {
return "", fmt.Errorf("invalid client (client is nil)")
}
if accessToken != "" {
_, err := jws.Verify([]byte(accessToken), jws.WithKeySet(idp.KeySet), jws.WithValidateKey(true))
_, err := jws.Verify([]byte(accessToken), jws.WithKeySet(client.Provider.KeySet), jws.WithValidateKey(true))
if err != nil {
return "", fmt.Errorf("failed to verify access token: %v", err)
}
}

if idToken != "" {
_, err := jws.Verify([]byte(idToken), jws.WithKeySet(idp.KeySet), jws.WithValidateKey(true))
_, err := jws.Verify([]byte(idToken), jws.WithKeySet(client.Provider.KeySet), jws.WithValidateKey(true))
if err != nil {
return "", fmt.Errorf("failed to verify ID token: %v", err)
}
Expand Down Expand Up @@ -126,7 +130,7 @@ func NewJwtBearerFlow(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) (s
// TODO: add trusted issuer to cache if successful

// 4. create a new JWT based on the claims from the identity provider and sign
parsedIdToken, err := jwt.ParseString(idToken, jwt.WithKeySet(idp.KeySet))
parsedIdToken, err := jwt.ParseString(idToken, jwt.WithKeySet(client.Provider.KeySet))
if err != nil {
return "", fmt.Errorf("failed to parse ID token: %v", err)
}
Expand Down Expand Up @@ -242,15 +246,15 @@ func ForwardToken(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) error
var (
client = params.Client
idToken = params.IdToken
idp = params.IdentityProvider
// idp = params.IdentityProvider
verbose = params.Verbose
)

// fetch JWKS and add issuer to authentication server to submit ID token
if verbose {
fmt.Printf("Fetching JWKS from authentication server for verification...\n")
}
err := idp.FetchJwks()
err := client.Provider.FetchJwks()
if err != nil {
return fmt.Errorf("failed to fetch JWK: %v", err)
} else {
Expand All @@ -260,7 +264,7 @@ func ForwardToken(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) error
}

ti := &oauth.TrustedIssuer{
Issuer: idp.Issuer,
Issuer: client.Provider.Issuer,
Subject: "1",
ExpiresAt: time.Now().Add(time.Second * 3600),
}
Expand Down
35 changes: 5 additions & 30 deletions internal/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,11 @@ import (
"time"
)

func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider) error {
func Login(config *Config) error {
if config == nil {
return fmt.Errorf("invalid config")
}

if client == nil {
return fmt.Errorf("invalid client")
}

if provider == nil {
return fmt.Errorf("invalid identity provider")
}

// make cache if it's not where expect
_, err := cache.CreateIdentityProvidersIfNotExists(config.Options.CachePath)
if err != nil {
Expand All @@ -39,18 +31,12 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
}

// print the authorization URL for sharing
var authorizationUrl = client.BuildAuthorizationUrl(provider.Endpoints.Authorization, state)
s := NewServerWithConfig(config)
fmt.Printf("Login with external identity provider:\n\n %s/login\n %s\n\n",
s.GetListenAddr(), authorizationUrl,
)
s.State = state

var button = MakeButton(authorizationUrl, "Login with "+client.Name)
var authzClient = oauth.NewClient()
authzClient.Scope = config.Authorization.Token.Scope

// authorize oauth client and listen for callback from provider
fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", s.GetListenAddr())
params := server.ServerParams{
Verbose: config.Options.Verbose,
AuthProvider: &oidc.IdentityProvider{
Expand All @@ -66,8 +52,7 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
Register: config.Authorization.Endpoints.Register,
},
JwtBearerParams: flows.JwtBearerFlowParams{
Client: authzClient,
IdentityProvider: provider,
Client: authzClient,
TrustedIssuer: &oauth.TrustedIssuer{
AllowAnySubject: false,
Issuer: s.Addr,
Expand All @@ -87,7 +72,7 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
Client: authzClient,
},
}
err = s.StartLogin(button, provider, client, params)
err = s.StartLogin(config.Authentication.Clients, params)
if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n")
} else if err != nil {
Expand All @@ -96,7 +81,7 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider

} else if config.Options.FlowType == "client_credentials" {
params := flows.ClientCredentialsFlowParams{
Client: client,
Client: nil, // # FIXME: need to do something about this being nil I think
}
_, err := NewClientCredentialsFlowWithConfig(config, params)
if err != nil {
Expand All @@ -108,13 +93,3 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider

return nil
}

func MakeButton(url string, text string) string {
// check if we have http:// a
html := "<input type=\"button\" "
html += "class=\"button\" "
html += fmt.Sprintf("onclick=\"window.location.href='%s';\" ", url)
html += fmt.Sprintf("value=\"%s\"", text)
return html
// return "<a href=\"" + url + "\"> " + text + "</a>"
}
4 changes: 2 additions & 2 deletions internal/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func NewClientWithConfig(config *Config) *oauth.Client {
Id: clients[0].Id,
Secret: clients[0].Secret,
Name: clients[0].Name,
Issuer: clients[0].Issuer,
Provider: clients[0].Provider,
Scope: clients[0].Scope,
RedirectUris: clients[0].RedirectUris,
}
Expand All @@ -53,7 +53,7 @@ func NewClientWithConfigByName(config *Config, name string) *oauth.Client {

func NewClientWithConfigByProvider(config *Config, issuer string) *oauth.Client {
index := slices.IndexFunc(config.Authentication.Clients, func(c oauth.Client) bool {
return c.Issuer == issuer
return c.Provider.Issuer == issuer
})

if index >= 0 {
Expand Down
13 changes: 8 additions & 5 deletions internal/oauth/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ func (client *Client) IsFlowInitiated() bool {
return client.FlowId != ""
}

func (client *Client) BuildAuthorizationUrl(issuer string, state string) string {
return issuer + "?" + "client_id=" + client.Id +
func (client *Client) BuildAuthorizationUrl(state string) string {
url := client.Provider.Endpoints.Authorization + "?client_id=" + client.Id +
"&redirect_uri=" + url.QueryEscape(strings.Join(client.RedirectUris, ",")) +
"&response_type=code" + // this has to be set to "code"
"&state=" + state +
"&scope=" + strings.Join(client.Scope, "+")
if state != "" {
url += "&state=" + state
}
return url
}

func (client *Client) InitiateLoginFlow(loginUrl string) error {
Expand Down Expand Up @@ -90,7 +93,7 @@ func (client *Client) FetchCSRFToken(flowUrl string) error {
return fmt.Errorf("failed to extract CSRF token: not found")
}

func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl string, state string) ([]byte, error) {
func (client *Client) FetchTokenFromAuthenticationServer(code string, state string) ([]byte, error) {
body := url.Values{
"grant_type": {"authorization_code"},
"client_id": {client.Id},
Expand All @@ -104,7 +107,7 @@ func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl
if state != "" {
body["state"] = []string{state}
}
res, err := http.PostForm(remoteUrl, body)
res, err := http.PostForm(client.Provider.Endpoints.Token, body)
if err != nil {
return nil, fmt.Errorf("failed to get ID token: %s", err)
}
Expand Down
Loading

0 comments on commit 5650cf6

Please sign in to comment.