Commit de1cf9c

mo khan <mo@mokhan.ca>
2025-03-11 01:11:44
test: use golang oauth client to test IdP
1 parent 2a9b689
Changed files (2)
bin/idp
@@ -170,6 +170,7 @@ class IdentityProvider
         return [200, { 'Content-Type' => "application/json" }, [JSON.pretty_generate({
           access_token: JWT.new(sub: SecureRandom.uuid, iat: Time.now.to_i).to_jwt,
           token_type: "Bearer",
+          issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
           expires_in: 3600,
           refresh_token: SecureRandom.hex(32)
         })]]
test/e2e_test.go
@@ -1,25 +1,23 @@
 package main
 
 import (
+	"bytes"
+	"context"
 	"net/http"
+	"net/url"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/playwright-community/playwright-go"
 	"github.com/stretchr/testify/assert"
 	"github.com/xlgmokha/x/pkg/env"
 	"github.com/xlgmokha/x/pkg/serde"
 	"github.com/xlgmokha/x/pkg/x"
+	"golang.org/x/oauth2"
 )
 
-type OAuthTokens struct {
-	AccessToken  string `json:"access_token"`
-	TokenType    string `json:"token_type"`
-	ExpiresIn    uint64 `json:"expires_in"`
-	RefreshToken string `json:"refresh_token"`
-}
-
-func TestHelloWorld(t *testing.T) {
+func TestAuthx(t *testing.T) {
 	_ = playwright.Install()
 
 	pw := x.Must(playwright.Run())
@@ -29,6 +27,8 @@ func TestHelloWorld(t *testing.T) {
 	}))
 	page := x.Must(browser.NewPage())
 
+	client := &http.Client{Timeout: 2 * time.Second}
+
 	defer func() {
 		x.Check(browser.Close())
 		x.Check(pw.Stop())
@@ -36,19 +36,19 @@ func TestHelloWorld(t *testing.T) {
 
 	t.Run("SAML", func(t *testing.T) {
 		t.Run("IdP", func(t *testing.T) {
-			t.Run("provides metadata", func(t *testing.T) {
+			t.Run("metadata.xml", func(t *testing.T) {
 				response := x.Must(http.Get("http://idp.example.com:8080/saml/metadata.xml"))
 				assert.Equal(t, http.StatusOK, response.StatusCode)
 			})
 		})
 
-		t.Run("Service provider", func(t *testing.T) {
-			t.Run("provides metadata", func(t *testing.T) {
+		t.Run("SP", func(t *testing.T) {
+			t.Run("metadata.xml", func(t *testing.T) {
 				response := x.Must(http.Get("http://ui.example.com:8080/saml/metadata.xml"))
 				assert.Equal(t, http.StatusOK, response.StatusCode)
 			})
 
-			t.Run("starts a new session with the IdP", func(t *testing.T) {
+			t.Run("ACS", func(t *testing.T) {
 				x.Must(page.Goto("http://ui.example.com:8080/saml/new"))
 				action := x.Must(page.Locator("#idp-form").GetAttribute("action"))
 				assert.Equal(t, "http://idp.example.com:8080/saml/new", action)
@@ -63,19 +63,82 @@ func TestHelloWorld(t *testing.T) {
 	})
 
 	t.Run("OIDC", func(t *testing.T) {
-		t.Run("Performs an OIDC login", func(t *testing.T) {
+		t.Run("login", func(t *testing.T) {
 			x.Must(page.Goto("http://ui.example.com:8080/oidc/new"))
 			assert.Contains(t, page.URL(), "http://idp.example.com:8080/oauth/authorize")
 			assert.NoError(t, page.Locator("#submit-button").Click())
 
 			assert.Contains(t, page.URL(), "http://ui.example.com:8080/oauth/callback")
 			content := x.Must(page.Locator("pre").First().InnerText())
-			item := x.Must(serde.FromJSON[OAuthTokens](strings.NewReader(content)))
+			item := x.Must(serde.FromJSON[oauth2.Token](strings.NewReader(content)))
 			assert.NotEmpty(t, item.AccessToken)
 			assert.Equal(t, "Bearer", item.TokenType)
 			assert.NotEmpty(t, item.RefreshToken)
-			// header = { 'Authorization' => "Bearer #{token}" }
-			// http.Get("http://api.example.com:8080/projects.json")
+
+			response := x.Must(http.Get("http://api.example.com:8080/projects.json"))
+			assert.Equal(t, http.StatusOK, response.StatusCode)
+			projects := x.Must(serde.FromJSON[[]map[string]string](response.Body))
+			assert.NotNil(t, projects)
+
+			io := bytes.NewBuffer(nil)
+			assert.NoError(t, serde.ToJSON(io, map[string]string{"name": "example"}))
+			request := x.Must(http.NewRequestWithContext(t.Context(), "POST", "http://api.example.com:8080/projects", io))
+			request.Header.Add("Authorization", "Bearer "+item.AccessToken)
+			response = x.Must(client.Do(request))
+			assert.Equal(t, http.StatusCreated, response.StatusCode)
+			project := x.Must(serde.FromJSON[map[string]string](response.Body))
+			assert.Equal(t, "example", project["name"])
+		})
+	})
+
+	t.Run("OAuth", func(t *testing.T) {
+		conf := &oauth2.Config{
+			ClientID:     "client_id",
+			ClientSecret: "client_secret",
+			Scopes:       []string{"openid"},
+			Endpoint: oauth2.Endpoint{
+				TokenURL: "http://idp.example.com:8080/oauth/token",
+				AuthURL:  "http://idp.example.com:8080/oauth/authorize",
+			},
+		}
+
+		t.Run("authorization code grant", func(t *testing.T) {
+			authURL := conf.AuthCodeURL(
+				"state",
+				oauth2.SetAuthURLParam("client_id", "client_id"),
+				oauth2.SetAuthURLParam("scope", "openid"),
+				oauth2.SetAuthURLParam("redirect_uri", "http://example.org/oauth/callback"),
+				oauth2.SetAuthURLParam("response_type", "code"),
+				oauth2.SetAuthURLParam("response_mode", "fragment"),
+			)
+			x.Must(page.Goto(authURL))
+			assert.NoError(t, page.Locator("#submit-button").Click())
+
+			uri := x.Must(url.Parse(page.URL()))
+			values := x.Must(url.ParseQuery(uri.Fragment))
+			code := values.Get("code")
+
+			ctx := t.Context()
+			ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
+			credentials := x.Must(conf.Exchange(ctx, code))
+			assert.NotEmpty(t, credentials.AccessToken)
+			assert.Equal(t, "Bearer", credentials.TokenType)
+			assert.NotEmpty(t, credentials.RefreshToken)
+
+			t.Run("token is usable against REST API", func(t *testing.T) {
+				client := conf.Client(ctx, credentials)
+				response := x.Must(client.Get("http://api.example.com:8080/projects.json"))
+				assert.Equal(t, http.StatusOK, response.StatusCode)
+				projects := x.Must(serde.FromJSON[[]map[string]string](response.Body))
+				assert.NotNil(t, projects)
+
+				io := bytes.NewBuffer(nil)
+				assert.NoError(t, serde.ToJSON(io, map[string]string{"name": "foo"}))
+				response = x.Must(client.Post("http://api.example.com:8080/projects", "application/json", io))
+				assert.Equal(t, http.StatusCreated, response.StatusCode)
+				project := x.Must(serde.FromJSON[map[string]string](response.Body))
+				assert.Equal(t, "foo", project["name"])
+			})
 		})
 	})
 }