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" ) func TestAuthx(t *testing.T) { _ = playwright.Install() pw := x.Must(playwright.Run()) browser := x.Must(pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ Headless: playwright.Bool(env.Fetch("HEADLESS", "true") == "true"), SlowMo: playwright.Float(1000), })) page := x.Must(browser.NewPage()) client := &http.Client{Timeout: 2 * time.Second} defer func() { x.Check(browser.Close()) x.Check(pw.Stop()) }() t.Run("SAML", func(t *testing.T) { t.Run("IdP", 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("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("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) assert.NoError(t, page.Locator("#submit-button").Click()) action = x.Must(page.Locator("#postback-form").GetAttribute("action")) assert.Equal(t, "http://ui.example.com:8080/saml/assertions", action) assert.NoError(t, page.Locator("#submit-button").Click()) assert.Contains(t, x.Must(page.Content()), "Received SAML Response") }) }) }) t.Run("OIDC", 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[oauth2.Token](strings.NewReader(content))) assert.NotEmpty(t, item.AccessToken) assert.Equal(t, "Bearer", item.TokenType) assert.NotEmpty(t, item.RefreshToken) 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) { t.Run("GET /.well-known/oauth-authorization-server", func(t *testing.T) { response := x.Must(client.Get("http://idp.example.com:8080/.well-known/oauth-authorization-server")) assert.Equal(t, http.StatusOK, response.StatusCode) metadata := x.Must(serde.FromJSON[map[string]interface{}](response.Body)) assert.Equal(t, "http://idp.example.com:8080/.well-known/oauth-authorization-server", metadata["issuer"]) assert.Equal(t, "http://idp.example.com:8080/oauth/authorize", metadata["authorization_endpoint"]) assert.Equal(t, "http://idp.example.com:8080/oauth/token", metadata["token_endpoint"]) // assert.NotEmpty(t, metadata["jwks_uri"]) // assert.NotEmpty(t, metadata["registration_endpoint"]) assert.NotEmpty(t, metadata["scopes_supported"]) assert.NotEmpty(t, metadata["response_types_supported"]) assert.NotEmpty(t, metadata["response_modes_supported"]) assert.NotEmpty(t, metadata["grant_types_supported"]) assert.NotEmpty(t, metadata["token_endpoint_auth_methods_supported"]) assert.NotEmpty(t, metadata["token_endpoint_auth_signing_alg_values_supported"]) // assert.NotEmpty(t, metadata["service_documentation"]) assert.NotEmpty(t, metadata["ui_locales_supported"]) // assert.NotEmpty(t, metadata["op_policy_uri"]) // assert.NotEmpty(t, metadata["op_tos_uri"]) assert.NotEmpty(t, metadata["revocation_endpoint"]) assert.NotEmpty(t, metadata["revocation_endpoint_auth_methods_supported"]) assert.NotEmpty(t, metadata["revocation_endpoint_auth_signing_alg_values_supported"]) assert.NotEmpty(t, metadata["introspection_endpoint"]) assert.NotEmpty(t, metadata["introspection_endpoint_auth_methods_supported"]) assert.NotEmpty(t, metadata["introspection_endpoint_auth_signing_alg_values_supported"]) // assert.NotEmpty(t, metadata["code_challenge_methods_supported"]) }) t.Run("authorization code grant", 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", }, } 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"]) }) }) }) }