@@ -2,17 +2,33 @@ package providers
6 "net/http/httptest"
8 "net/http/httptest"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13+ . "github.com/onsi/ginkgo"
14+ . "github.com/onsi/ginkgo/extensions/table"
11 . "github.com/onsi/gomega"
15 . "github.com/onsi/gomega"
12 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/assert"
20+ keycloakAccessToken = "eyJKeycloak.eyJAccess.Token"
21+ keycloakUserinfoPath = "/api/v3/user"
23+ // Userinfo Test Cases
24+ tcUIStandard = "userinfo-standard"
25+ tcUIFail = "userinfo-fail"
26+ tcUISingleGroup = "userinfo-single-group"
27+ tcUIMissingEmail = "userinfo-missing-email"
28+ tcUIMissingGroups = "userinfo-missing-groups"
15−func testKeycloakProvider(hostname, group string) *KeycloakProvider {
31+func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) {
16 p := NewKeycloakProvider(
32 p := NewKeycloakProvider(
17 &ProviderData{
33 &ProviderData{
18 ProviderName: "",
34 ProviderName: "",
@@ -22,38 +38,165 @@ func testKeycloakProvider(hostname, group string) *KeycloakProvider {
22 ValidateURL: &url.URL{},
38 ValidateURL: &url.URL{},
23 Scope: ""})
39 Scope: ""})
25− if group != "" {
41+ if backend != nil {
26− p.SetGroup(group)
42+ bURL, err := url.Parse(backend.URL)
46+ hostname := bURL.Host
30 updateURL(p.Data().LoginURL, hostname)
48 updateURL(p.Data().LoginURL, hostname)
31 updateURL(p.Data().RedeemURL, hostname)
49 updateURL(p.Data().RedeemURL, hostname)
32 updateURL(p.Data().ProfileURL, hostname)
50 updateURL(p.Data().ProfileURL, hostname)
33 updateURL(p.Data().ValidateURL, hostname)
51 updateURL(p.Data().ValidateURL, hostname)
38−func testKeycloakBackend(payload string) *httptest.Server {
54+ return p, nil
39− path := "/api/v3/user"
55+}
57+func testKeycloakBackend() *httptest.Server {
41 return httptest.NewServer(http.HandlerFunc(
58 return httptest.NewServer(http.HandlerFunc(
42 func(w http.ResponseWriter, r *http.Request) {
59 func(w http.ResponseWriter, r *http.Request) {
43− url := r.URL
60+ if r.URL.Path != keycloakUserinfoPath {
44− if url.Path != path {
45 w.WriteHeader(404)
61 w.WriteHeader(404)
46− } else if !IsAuthorizedInHeader(r.Header) {
62+ }
65+ switch r.URL.Query().Get("testcase") {
47− w.WriteHeader(403)
67+ w.WriteHeader(200)
48− } else {
68+ _, err = w.Write([]byte(`
70+ "email": "michael.bland@gsa.gov",
79+ case tcUISingleGroup:
81+ _, err = w.Write([]byte(`
83+ "email": "michael.bland@gsa.gov",
84+ "groups": ["test-grp1"]
87+ case tcUIMissingEmail:
89+ _, err = w.Write([]byte(`
97+ case tcUIMissingGroups:
49 w.WriteHeader(200)
98 w.WriteHeader(200)
50− w.Write([]byte(payload))
99+ _, err = w.Write([]byte(`
101+ "email": "michael.bland@gsa.gov"
113+var _ = Describe("Keycloak Provider Tests", func() {
114+ var p *KeycloakProvider
115+ var b *httptest.Server
118+ b = testKeycloakBackend()
121+ p, err = testKeycloakProvider(b)
122+ Expect(err).To(BeNil())
129+ Context("EnrichSession", func() {
130+ type enrichSessionTableInput struct {
133+ expectedEmail string
134+ expectedGroups []string
137+ DescribeTable("should return expected results",
138+ func(in enrichSessionTableInput) {
140+ p.ValidateURL, err = url.Parse(
141+ fmt.Sprintf("%s%s?testcase=%s", b.URL, keycloakUserinfoPath, in.testcase),
143+ Expect(err).To(BeNil())
145+ session := &sessions.SessionState{AccessToken: keycloakAccessToken}
146+ err = p.EnrichSession(context.Background(), session)
148+ if in.expectedError != nil {
149+ Expect(err).To(Equal(in.expectedError))
151+ Expect(err).To(BeNil())
154+ Expect(session.Email).To(Equal(in.expectedEmail))
156+ if in.expectedGroups != nil {
157+ Expect(session.Groups).To(Equal(in.expectedGroups))
159+ Expect(session.Groups).To(BeNil())
162+ Entry("email and multiple groups", enrichSessionTableInput{
163+ testcase: tcUIStandard,
165+ expectedEmail: "michael.bland@gsa.gov",
166+ expectedGroups: []string{"test-grp1", "test-grp2"},
168+ Entry("email and single group", enrichSessionTableInput{
169+ testcase: tcUISingleGroup,
171+ expectedEmail: "michael.bland@gsa.gov",
172+ expectedGroups: []string{"test-grp1"},
174+ Entry("email and no groups", enrichSessionTableInput{
175+ testcase: tcUIMissingGroups,
177+ expectedEmail: "michael.bland@gsa.gov",
178+ expectedGroups: nil,
180+ Entry("missing email", enrichSessionTableInput{
181+ testcase: tcUIMissingEmail,
182+ expectedError: errors.New(
183+ "unable to extract email from userinfo endpoint: type assertion to string failed"),
185+ expectedGroups: []string{"test-grp1", "test-grp2"},
187+ Entry("request failure", enrichSessionTableInput{
189+ expectedError: errors.New(`unexpected status "500": `),
191+ expectedGroups: nil,
55func TestKeycloakProviderDefaults(t *testing.T) {
197func TestKeycloakProviderDefaults(t *testing.T) {
56− p := testKeycloakProvider("", "")
198+ p, err := testKeycloakProvider(nil)
199+ assert.NoError(t, err)
57 assert.NotEqual(t, nil, p)
200 assert.NotEqual(t, nil, p)
58 assert.Equal(t, "Keycloak", p.Data().ProviderName)
201 assert.Equal(t, "Keycloak", p.Data().ProviderName)
59 assert.Equal(t, "https://keycloak.org/oauth/authorize",
202 assert.Equal(t, "https://keycloak.org/oauth/authorize",
@@ -104,60 +247,3 @@ func TestKeycloakProviderOverrides(t *testing.T) {
104 p.Data().ValidateURL.String())
247 p.Data().ValidateURL.String())
105 assert.Equal(t, "profile", p.Data().Scope)
248 assert.Equal(t, "profile", p.Data().Scope)
108−func TestKeycloakProviderGetEmailAddress(t *testing.T) {
109− b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}")
112− bURL, _ := url.Parse(b.URL)
113− p := testKeycloakProvider(bURL.Host, "")
115− session := CreateAuthorizedSession()
116− email, err := p.GetEmailAddress(context.Background(), session)
117− assert.Equal(t, nil, err)
118− assert.Equal(t, "michael.bland@gsa.gov", email)
121−func TestKeycloakProviderGetEmailAddressAndGroup(t *testing.T) {
122− b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}")
125− bURL, _ := url.Parse(b.URL)
126− p := testKeycloakProvider(bURL.Host, "test-grp1")
128− session := CreateAuthorizedSession()
129− email, err := p.GetEmailAddress(context.Background(), session)
130− assert.Equal(t, nil, err)
131− assert.Equal(t, "michael.bland@gsa.gov", email)
134−// Note that trying to trigger the "failed building request" case is not
135−// practical, since the only way it can fail is if the URL fails to parse.
136−func TestKeycloakProviderGetEmailAddressFailedRequest(t *testing.T) {
137− b := testKeycloakBackend("unused payload")
140− bURL, _ := url.Parse(b.URL)
141− p := testKeycloakProvider(bURL.Host, "")
143− // We'll trigger a request failure by using an unexpected access
144− // token. Alternatively, we could allow the parsing of the payload as
146− session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
147− email, err := p.GetEmailAddress(context.Background(), session)
148− assert.NotEqual(t, nil, err)
149− assert.Equal(t, "", email)
152−func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
153− b := testKeycloakBackend("{\"foo\": \"bar\"}")
156− bURL, _ := url.Parse(b.URL)
157− p := testKeycloakProvider(bURL.Host, "")
159− session := CreateAuthorizedSession()
160− email, err := p.GetEmailAddress(context.Background(), session)
161− assert.NotEqual(t, nil, err)
162− assert.Equal(t, "", email)