1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
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/stretchr/testify/require"
"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) {
for _, url := range []string{"http://idp.example.com:8080/saml/metadata.xml", "http://ui.example.com:8080/saml/metadata.xml"} {
t.Run("GET "+url, func(t *testing.T) {
response := x.Must(http.Get(url))
assert.Equal(t, http.StatusOK, response.StatusCode)
})
}
t.Run("GET http://ui.example.com:8080/saml/new", func(t *testing.T) {
assert.NoError(t, page.Context().ClearCookies())
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())
page.Locator("#username").Fill("username1")
page.Locator("#password").Fill("password1")
assert.NoError(t, page.Locator("#login-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("GET http://ui.example.com:8080/oidc/new", func(t *testing.T) {
assert.NoError(t, page.Context().ClearCookies())
x.Must(page.Goto("http://ui.example.com:8080/oidc/new"))
assert.Contains(t, page.URL(), "http://idp.example.com:8080/sessions/new")
page.Locator("#username").Fill("username1")
page.Locator("#password").Fill("password1")
assert.NoError(t, page.Locator("#login-button").Click())
assert.Contains(t, page.URL(), "http://idp.example.com:8080/oauth/authorize/continue")
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)))
require.NotEmpty(t, item.AccessToken)
require.Equal(t, "Bearer", item.TokenType)
require.NotEmpty(t, item.RefreshToken)
t.Run("GET http://api.example.com:8080/organizations.json", func(t *testing.T) {
response := x.Must(http.Get("http://api.example.com:8080/organizations.json"))
assert.Equal(t, http.StatusForbidden, response.StatusCode)
})
t.Run("GET http://api.example.com:8080/organizations.json with Authorization", func(t *testing.T) {
request := x.Must(http.NewRequestWithContext(t.Context(), "GET", "http://api.example.com:8080/organizations.json", nil))
request.Header.Add("Authorization", "Bearer "+item.AccessToken)
response := x.Must(client.Do(request))
require.Equal(t, http.StatusOK, response.StatusCode)
organizations := x.Must(serde.FromJSON[[]map[string]string](response.Body))
assert.NotNil(t, organizations)
})
t.Run("GET http://api.example.com:8080/projects.json", func(t *testing.T) {
response := x.Must(http.Get("http://api.example.com:8080/projects.json"))
assert.Equal(t, http.StatusForbidden, response.StatusCode)
})
t.Run("GET http://api.example.com:8080/projects.json with Authorization", func(t *testing.T) {
request := x.Must(http.NewRequestWithContext(t.Context(), "GET", "http://api.example.com:8080/projects.json", nil))
request.Header.Add("Authorization", "Bearer "+item.AccessToken)
response := x.Must(client.Do(request))
require.Equal(t, http.StatusOK, response.StatusCode)
projects := x.Must(serde.FromJSON[[]map[string]string](response.Body))
assert.NotNil(t, projects)
})
t.Run("creates a new project", func(t *testing.T) {
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))
require.Equal(t, http.StatusCreated, response.StatusCode)
project := x.Must(serde.FromJSON[map[string]string](response.Body))
assert.Equal(t, "example", project["name"])
})
t.Run("creates another project", func(t *testing.T) {
io := bytes.NewBuffer(nil)
assert.NoError(t, serde.ToJSON(io, map[string]string{"name": "example2"}))
request := x.Must(http.NewRequestWithContext(t.Context(), "POST", "http://api.example.com:8080/projects.json", io))
request.Header.Add("Authorization", "Bearer "+item.AccessToken)
response := x.Must(client.Do(request))
require.Equal(t, http.StatusCreated, response.StatusCode)
project := x.Must(serde.FromJSON[map[string]string](response.Body))
assert.Equal(t, "example2", 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"))
require.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("GET /.well-known/openid-configuration", func(t *testing.T) {
response := x.Must(client.Get("http://idp.example.com:8080/.well-known/openid-configuration"))
require.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["userinfo_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["acr_values_supported"])
assert.NotEmpty(t, metadata["subject_types_supported"])
assert.NotEmpty(t, metadata["id_token_signing_alg_values_supported"])
// assert.NotEmpty(t, metadata["id_token_encryption_alg_values_supported"])
// assert.NotEmpty(t, metadata["id_token_encryption_enc_values_supported"])
assert.NotEmpty(t, metadata["userinfo_signing_alg_values_supported"])
// assert.NotEmpty(t, metadata["userinfo_encryption_alg_values_supported"])
// assert.NotEmpty(t, metadata["userinfo_encryption_enc_values_supported"])
assert.NotEmpty(t, metadata["request_object_signing_alg_values_supported"])
// assert.NotEmpty(t, metadata["request_object_encryption_alg_values_supported"])
// assert.NotEmpty(t, metadata["request_object_encryption_enc_values_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["display_values_supported"])
assert.NotEmpty(t, metadata["claim_types_supported"])
assert.NotEmpty(t, metadata["claims_supported"])
// assert.NotEmpty(t, metadata["service_documentation"])
// assert.NotEmpty(t, metadata["claims_locales_supported"])
assert.NotEmpty(t, metadata["ui_locales_supported"])
// assert.True(t, metadata["claims_parameter_supported"])
// assert.True(t, metadata["request_parameter_supported"])
// assert.True(t, metadata["request_uri_parameter_supported"])
// assert.True(t, metadata["require_request_uri_registration"])
// assert.NotEmpty(t, metadata["op_policy_uri"])
// assert.NotEmpty(t, metadata["op_tos_uri"])
})
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"),
)
assert.NoError(t, page.Context().ClearCookies())
x.Must(page.Goto(authURL))
page.Locator("#username").Fill("username1")
page.Locator("#password").Fill("password1")
assert.NoError(t, page.Locator("#login-button").Click())
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"))
require.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))
require.Equal(t, http.StatusCreated, response.StatusCode)
project := x.Must(serde.FromJSON[map[string]string](response.Body))
assert.Equal(t, "foo", project["name"])
})
})
})
}
|