From 32d93f7378b915d9344349d4215e30a799f76988 Mon Sep 17 00:00:00 2001 From: M Alvee Date: Mon, 7 Jul 2025 10:10:53 +0600 Subject: [PATCH 1/2] move common oidc tests utils to minio/pkg --- go.mod | 4 ++ go.sum | 8 +++ oidc/utils.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 oidc/utils.go diff --git a/go.mod b/go.mod index 58db081..75d88c7 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.24.4 require ( github.com/cheggaaa/pb v1.0.29 + github.com/coreos/go-oidc v2.3.0+incompatible github.com/fatih/color v1.18.0 github.com/fatih/structs v1.1.0 github.com/go-ldap/ldap/v3 v3.4.11 @@ -19,6 +20,7 @@ require ( github.com/zeebo/xxh3 v1.0.2 go.etcd.io/etcd/client/v3 v3.6.1 golang.org/x/crypto v0.39.0 + golang.org/x/oauth2 v0.30.0 golang.org/x/sys v0.33.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -44,6 +46,7 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/segmentio/asm v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.6.1 // indirect @@ -56,4 +59,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect ) diff --git a/go.sum b/go.sum index 8f1dc0f..c46161f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= +github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -92,6 +94,8 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= +github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -151,6 +155,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -188,6 +194,8 @@ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/oidc/utils.go b/oidc/utils.go new file mode 100644 index 0000000..584b07b --- /dev/null +++ b/oidc/utils.go @@ -0,0 +1,174 @@ +package oidc + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/coreos/go-oidc" + "golang.org/x/oauth2" +) + +/////////// Types and functions for OpenID IAM testing + +// OpenIDClientAppParams - contains openID client application params, used in +// testing. +type OpenIDClientAppParams struct { + ClientID, ClientSecret, ProviderURL, RedirectURL string + Transport http.RoundTripper +} + +// MockOpenIDTestUserInteraction - tries to login to dex using provided credentials. +// It performs the user's browser interaction to login and retrieves the auth +// code from dex and exchanges it for a JWT. +func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParams, username, password string) (string, string, string, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + if pro.Transport != nil { + ctx = oidc.ClientContext(ctx, &http.Client{Transport: pro.Transport}) + } + + provider, err := oidc.NewProvider(ctx, pro.ProviderURL) + if err != nil { + return "", "", "", fmt.Errorf("unable to create provider: %v", err) + } + + // Configure an OpenID Connect aware OAuth2 client. + oauth2Config := oauth2.Config{ + ClientID: pro.ClientID, + ClientSecret: pro.ClientSecret, + RedirectURL: pro.RedirectURL, + + // Discovery returns the OAuth2 endpoints. + Endpoint: provider.Endpoint(), + + // "openid" is a required scope for OpenID Connect flows. + Scopes: []string{oidc.ScopeOpenID, "groups", "offline_access"}, + } + + state := fmt.Sprintf("x%dx", time.Now().Unix()) + authCodeURL := oauth2Config.AuthCodeURL(state) + // fmt.Printf("authcodeurl: %s\n", authCodeURL) + + var lastReq *http.Request + checkRedirect := func(req *http.Request, via []*http.Request) error { + // fmt.Printf("CheckRedirect:\n") + // fmt.Printf("Upcoming: %s %s\n", req.Method, req.URL.String()) + // for i, c := range via { + // fmt.Printf("Sofar %d: %s %s\n", i, c.Method, c.URL.String()) + // } + // Save the last request in a redirect chain. + lastReq = req + // We do not follow redirect back to client application. + if req.URL.Path == "/oauth_callback" { + return http.ErrUseLastResponse + } + return nil + } + + dexClient := http.Client{ + CheckRedirect: checkRedirect, + Transport: pro.Transport, + } + + u, err := url.Parse(authCodeURL) + if err != nil { + return "", "", "", fmt.Errorf("url parse err: %v", err) + } + + // Start the user auth flow. This page would present the login with + // email or LDAP option. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return "", "", "", fmt.Errorf("new request err: %v", err) + } + resp, err := dexClient.Do(req) + // fmt.Printf("Do: %#v %#v\n", resp, err) + if err != nil { + return "", "", "", fmt.Errorf("auth url request err: %v", err) + } + if resp.StatusCode != http.StatusOK { + return "", "", "", fmt.Errorf("auth url request returned HTTP status: %d", resp.StatusCode) + } + + // Modify u to choose the ldap option + u.Path += "/ldap" + // fmt.Println(u) + + // Pick the LDAP login option. This would return a form page after + // following some redirects. `lastReq` would be the URL of the form + // page, where we need to POST (submit) the form. + req, err = http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return "", "", "", fmt.Errorf("new request err (/ldap): %v", err) + } + resp, err = dexClient.Do(req) + // fmt.Printf("Fetch LDAP login page: %#v %#v\n", resp, err) + if err != nil { + return "", "", "", fmt.Errorf("request err: %v", err) + } + if resp.StatusCode != http.StatusOK { + return "", "", "", fmt.Errorf("ew request (/ldap) returned HTTP status: %d", resp.StatusCode) + } + // { + // bodyBuf, err := io.ReadAll(resp.Body) + // if err != nil { + // return "", "", "", fmt.Errorf("Error reading body: %v", err) + // } + // fmt.Printf("bodyBuf (for LDAP login page): %s\n", string(bodyBuf)) + // } + + // Fill the login form with our test creds: + // fmt.Printf("login form url: %s\n", lastReq.URL.String()) + formData := url.Values{} + formData.Set("login", username) + formData.Set("password", password) + req, err = http.NewRequestWithContext(ctx, http.MethodPost, lastReq.URL.String(), strings.NewReader(formData.Encode())) + if err != nil { + return "", "", "", fmt.Errorf("new request err (/login): %v", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + _, err = dexClient.Do(req) + if err != nil { + return "", "", "", fmt.Errorf("post form err: %v", err) + } + // fmt.Printf("resp: %#v %#v\n", resp.StatusCode, resp.Header) + // bodyBuf, err := io.ReadAll(resp.Body) + // if err != nil { + // return "", "", "", fmt.Errorf("Error reading body: %v", err) + // } + // fmt.Printf("resp body: %s\n", string(bodyBuf)) + // fmt.Printf("lastReq: %#v\n", lastReq.URL.String()) + + // On form submission, the last redirect response contains the auth + // code, which we now have in `lastReq`. Exchange it for a JWT id_token. + q := lastReq.URL.Query() + // fmt.Printf("lastReq.URL: %#v q: %#v\n", lastReq.URL, q) + code := q.Get("code") + oauth2Token, err := oauth2Config.Exchange(ctx, code) + if err != nil { + return "", "", "", fmt.Errorf("unable to exchange code for id token: %v", err) + } + + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + return "", "", "", fmt.Errorf("id_token not found!") + } + + accessIDToken, ok := oauth2Token.Extra("access_token").(string) + if !ok { + return "", "", "", fmt.Errorf("access_token not found!") + } + + refreshToken, ok := oauth2Token.Extra("refresh_token").(string) + if !ok { + return "", "", "", fmt.Errorf("refresh_token not found!") + } + + // fmt.Printf("TOKEN: %s\n", rawIDToken) + return rawIDToken, accessIDToken, refreshToken, nil +} From 14e6329e39b9e3c909fe6212f016770638a22a6c Mon Sep 17 00:00:00 2001 From: M Alvee Date: Mon, 7 Jul 2025 10:41:34 +0600 Subject: [PATCH 2/2] change oidc import, debug flag & copyright header --- go.mod | 5 ++-- go.sum | 10 +++---- oidc/utils.go | 72 +++++++++++++++++++++++++++++---------------------- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 75d88c7..bc6d264 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.4 require ( github.com/cheggaaa/pb v1.0.29 - github.com/coreos/go-oidc v2.3.0+incompatible + github.com/coreos/go-oidc/v3 v3.14.1 github.com/fatih/color v1.18.0 github.com/fatih/structs v1.1.0 github.com/go-ldap/ldap/v3 v3.4.11 @@ -32,6 +32,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -46,7 +47,6 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/segmentio/asm v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.6.1 // indirect @@ -59,5 +59,4 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect ) diff --git a/go.sum b/go.sum index c46161f..d54be90 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= -github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= -github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= +github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -22,6 +22,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -94,8 +96,6 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= -github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -194,8 +194,6 @@ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/oidc/utils.go b/oidc/utils.go index 584b07b..3e787bc 100644 --- a/oidc/utils.go +++ b/oidc/utils.go @@ -1,14 +1,32 @@ +// Copyright (c) 2015-2025 MinIO, Inc. +// +// # This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package oidc import ( "context" "fmt" + "io" "net/http" "net/url" "strings" "time" - "github.com/coreos/go-oidc" + "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) @@ -19,12 +37,19 @@ import ( type OpenIDClientAppParams struct { ClientID, ClientSecret, ProviderURL, RedirectURL string Transport http.RoundTripper + Debug bool } // MockOpenIDTestUserInteraction - tries to login to dex using provided credentials. // It performs the user's browser interaction to login and retrieves the auth // code from dex and exchanges it for a JWT. func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParams, username, password string) (string, string, string, error) { + var debug bool + + debug = false + if pro.Debug { + debug = true + } ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() @@ -52,15 +77,9 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam state := fmt.Sprintf("x%dx", time.Now().Unix()) authCodeURL := oauth2Config.AuthCodeURL(state) - // fmt.Printf("authcodeurl: %s\n", authCodeURL) var lastReq *http.Request - checkRedirect := func(req *http.Request, via []*http.Request) error { - // fmt.Printf("CheckRedirect:\n") - // fmt.Printf("Upcoming: %s %s\n", req.Method, req.URL.String()) - // for i, c := range via { - // fmt.Printf("Sofar %d: %s %s\n", i, c.Method, c.URL.String()) - // } + checkRedirect := func(req *http.Request, _ []*http.Request) error { // Save the last request in a redirect chain. lastReq = req // We do not follow redirect back to client application. @@ -87,7 +106,7 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam return "", "", "", fmt.Errorf("new request err: %v", err) } resp, err := dexClient.Do(req) - // fmt.Printf("Do: %#v %#v\n", resp, err) + if err != nil { return "", "", "", fmt.Errorf("auth url request err: %v", err) } @@ -97,7 +116,6 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam // Modify u to choose the ldap option u.Path += "/ldap" - // fmt.Println(u) // Pick the LDAP login option. This would return a form page after // following some redirects. `lastReq` would be the URL of the form @@ -107,23 +125,14 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam return "", "", "", fmt.Errorf("new request err (/ldap): %v", err) } resp, err = dexClient.Do(req) - // fmt.Printf("Fetch LDAP login page: %#v %#v\n", resp, err) if err != nil { return "", "", "", fmt.Errorf("request err: %v", err) } if resp.StatusCode != http.StatusOK { return "", "", "", fmt.Errorf("ew request (/ldap) returned HTTP status: %d", resp.StatusCode) } - // { - // bodyBuf, err := io.ReadAll(resp.Body) - // if err != nil { - // return "", "", "", fmt.Errorf("Error reading body: %v", err) - // } - // fmt.Printf("bodyBuf (for LDAP login page): %s\n", string(bodyBuf)) - // } // Fill the login form with our test creds: - // fmt.Printf("login form url: %s\n", lastReq.URL.String()) formData := url.Values{} formData.Set("login", username) formData.Set("password", password) @@ -136,18 +145,20 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam if err != nil { return "", "", "", fmt.Errorf("post form err: %v", err) } - // fmt.Printf("resp: %#v %#v\n", resp.StatusCode, resp.Header) - // bodyBuf, err := io.ReadAll(resp.Body) - // if err != nil { - // return "", "", "", fmt.Errorf("Error reading body: %v", err) - // } - // fmt.Printf("resp body: %s\n", string(bodyBuf)) - // fmt.Printf("lastReq: %#v\n", lastReq.URL.String()) + + if debug { + fmt.Printf("resp: %#v %#v\n", resp.StatusCode, resp.Header) + bodyBuf, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", "", fmt.Errorf("Error reading body: %v", err) + } + fmt.Printf("resp body: %s\n", string(bodyBuf)) + fmt.Printf("lastReq: %#v\n", lastReq.URL.String()) + } // On form submission, the last redirect response contains the auth // code, which we now have in `lastReq`. Exchange it for a JWT id_token. q := lastReq.URL.Query() - // fmt.Printf("lastReq.URL: %#v q: %#v\n", lastReq.URL, q) code := q.Get("code") oauth2Token, err := oauth2Config.Exchange(ctx, code) if err != nil { @@ -156,19 +167,18 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { - return "", "", "", fmt.Errorf("id_token not found!") + return "", "", "", fmt.Errorf("id_token not found") } accessIDToken, ok := oauth2Token.Extra("access_token").(string) if !ok { - return "", "", "", fmt.Errorf("access_token not found!") + return "", "", "", fmt.Errorf("access_token not found") } refreshToken, ok := oauth2Token.Extra("refresh_token").(string) if !ok { - return "", "", "", fmt.Errorf("refresh_token not found!") + return "", "", "", fmt.Errorf("refresh_token not found") } - // fmt.Printf("TOKEN: %s\n", rawIDToken) return rawIDToken, accessIDToken, refreshToken, nil }