@@ -2,17 +2,24 @@ package providers
6 "net/http/httptest"
8 "net/http/httptest"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12+ . "github.com/onsi/ginkgo"
13+ . "github.com/onsi/ginkgo/extensions/table"
11 . "github.com/onsi/gomega"
14 . "github.com/onsi/gomega"
12− "github.com/stretchr/testify/assert"
18+ keycloakAccessToken = "eyJKeycloak.eyJAccess.Token"
19+ keycloakUserinfoPath = "/api/v3/user"
15−func testKeycloakProvider(hostname, group string) *KeycloakProvider {
22+func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) {
16 p := NewKeycloakProvider(
23 p := NewKeycloakProvider(
17 &ProviderData{
24 &ProviderData{
18 ProviderName: "",
25 ProviderName: "",
@@ -22,142 +29,182 @@ func testKeycloakProvider(hostname, group string) *KeycloakProvider {
22 ValidateURL: &url.URL{},
29 ValidateURL: &url.URL{},
23 Scope: ""})
30 Scope: ""})
25− if group != "" {
32+ if backend != nil {
26− p.SetGroup(group)
33+ bURL, err := url.Parse(backend.URL)
37+ hostname := bURL.Host
30 updateURL(p.Data().LoginURL, hostname)
39 updateURL(p.Data().LoginURL, hostname)
31 updateURL(p.Data().RedeemURL, hostname)
40 updateURL(p.Data().RedeemURL, hostname)
32 updateURL(p.Data().ProfileURL, hostname)
41 updateURL(p.Data().ProfileURL, hostname)
33 updateURL(p.Data().ValidateURL, hostname)
42 updateURL(p.Data().ValidateURL, hostname)
38−func testKeycloakBackend(payload string) *httptest.Server {
45+ return p, nil
39− path := "/api/v3/user"
41− return httptest.NewServer(http.HandlerFunc(
42− func(w http.ResponseWriter, r *http.Request) {
44− if url.Path != path {
46− } else if !IsAuthorizedInHeader(r.Header) {
50− w.Write([]byte(payload))
55−func TestKeycloakProviderDefaults(t *testing.T) {
48+var _ = Describe("Keycloak Provider Tests", func() {
56− p := testKeycloakProvider("", "")
49+ Context("New Provider Init", func() {
57− assert.NotEqual(t, nil, p)
50+ It("uses defaults", func() {
58− assert.Equal(t, "Keycloak", p.Data().ProviderName)
59− assert.Equal(t, "https://keycloak.org/oauth/authorize",
60− p.Data().LoginURL.String())
61− assert.Equal(t, "https://keycloak.org/oauth/token",
62− p.Data().RedeemURL.String())
63− assert.Equal(t, "https://keycloak.org/api/v3/user",
64− p.Data().ValidateURL.String())
65− assert.Equal(t, "api", p.Data().Scope)
68−func TestNewKeycloakProvider(t *testing.T) {
71− // Test that defaults are set when calling for a new provider with nothing set
72− providerData := NewKeycloakProvider(&ProviderData{}).Data()
51+ providerData := NewKeycloakProvider(&ProviderData{}).Data()
73− g.Expect(providerData.ProviderName).To(Equal("Keycloak"))
52+ Expect(providerData.ProviderName).To(Equal("Keycloak"))
74− g.Expect(providerData.LoginURL.String()).To(Equal("https://keycloak.org/oauth/authorize"))
53+ Expect(providerData.LoginURL.String()).To(Equal("https://keycloak.org/oauth/authorize"))
75− g.Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak.org/oauth/token"))
54+ Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak.org/oauth/token"))
76− g.Expect(providerData.ProfileURL.String()).To(Equal(""))
55+ Expect(providerData.ProfileURL.String()).To(Equal(""))
77− g.Expect(providerData.ValidateURL.String()).To(Equal("https://keycloak.org/api/v3/user"))
56+ Expect(providerData.ValidateURL.String()).To(Equal("https://keycloak.org/api/v3/user"))
78− g.Expect(providerData.Scope).To(Equal("api"))
57+ Expect(providerData.Scope).To(Equal("api"))
81−func TestKeycloakProviderOverrides(t *testing.T) {
60+ It("overrides defaults", func() {
82− p := NewKeycloakProvider(
61+ p := NewKeycloakProvider(
83− &ProviderData{
62+ &ProviderData{
84− LoginURL: &url.URL{
63+ LoginURL: &url.URL{
85− Scheme: "https",
64+ Scheme: "https",
86− Host: "example.com",
65+ Host: "example.com",
87− Path: "/oauth/auth"},
66+ Path: "/oauth/auth"},
88− RedeemURL: &url.URL{
67+ RedeemURL: &url.URL{
89− Scheme: "https",
68+ Scheme: "https",
90− Host: "example.com",
69+ Host: "example.com",
91− Path: "/oauth/token"},
70+ Path: "/oauth/token"},
92− ValidateURL: &url.URL{
71+ ProfileURL: &url.URL{
93− Scheme: "https",
72+ Scheme: "https",
94− Host: "example.com",
73+ Host: "example.com",
95− Path: "/api/v3/user"},
74+ Path: "/api/v3/user"},
96− Scope: "profile"})
75+ ValidateURL: &url.URL{
97− assert.NotEqual(t, nil, p)
76+ Scheme: "https",
98− assert.Equal(t, "Keycloak", p.Data().ProviderName)
77+ Host: "example.com",
78+ Path: "/api/v3/user"},
80+ providerData := p.Data()
82+ Expect(providerData.ProviderName).To(Equal("Keycloak"))
99− assert.Equal(t, "https://example.com/oauth/auth",
83+ Expect(providerData.LoginURL.String()).To(Equal("https://example.com/oauth/auth"))
100− p.Data().LoginURL.String())
101− assert.Equal(t, "https://example.com/oauth/token",
84+ Expect(providerData.RedeemURL.String()).To(Equal("https://example.com/oauth/token"))
102− p.Data().RedeemURL.String())
103− assert.Equal(t, "https://example.com/api/v3/user",
85+ Expect(providerData.ProfileURL.String()).To(Equal("https://example.com/api/v3/user"))
104− p.Data().ValidateURL.String())
86+ Expect(providerData.ValidateURL.String()).To(Equal("https://example.com/api/v3/user"))
105− assert.Equal(t, "profile", p.Data().Scope)
87+ Expect(providerData.Scope).To(Equal("profile"))
108−func TestKeycloakProviderGetEmailAddress(t *testing.T) {
90+
109− b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}")
91+ Context("EnrichSession", func() {
110− defer b.Close()
92+ type enrichSessionTableInput struct {
111−
93+ backendHandler http.HandlerFunc
112− bURL, _ := url.Parse(b.URL)
94+ expectedError error
113− p := testKeycloakProvider(bURL.Host, "")
95+ expectedEmail string
114−
96+ expectedGroups []string
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) {
99+ DescribeTable("should return expected results",
122− b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}")
100+ func(in enrichSessionTableInput) {
123− defer b.Close()
101+ backend := httptest.NewServer(in.backendHandler)
124−
102+ p, err := testKeycloakProvider(backend)
125− bURL, _ := url.Parse(b.URL)
103+ Expect(err).To(BeNil())
126− p := testKeycloakProvider(bURL.Host, "test-grp1")
104+
127−
105+ p.ProfileURL, err = url.Parse(
128− session := CreateAuthorizedSession()
106+ fmt.Sprintf("%s%s", backend.URL, keycloakUserinfoPath),
129− email, err := p.GetEmailAddress(context.Background(), session)
107+ )
130− assert.Equal(t, nil, err)
108+ Expect(err).To(BeNil())
131− assert.Equal(t, "michael.bland@gsa.gov", email)
109+
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"}
110+ session := &sessions.SessionState{AccessToken: keycloakAccessToken}
147− email, err := p.GetEmailAddress(context.Background(), session)
111+ err = p.EnrichSession(context.Background(), session)
148− assert.NotEqual(t, nil, err)
112+
149− assert.Equal(t, "", email)
113+ if in.expectedError != nil {
114+ Expect(err).To(Equal(in.expectedError))
116+ Expect(err).To(BeNil())
152−func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
119+ Expect(session.Email).To(Equal(in.expectedEmail))
153− b := testKeycloakBackend("{\"foo\": \"bar\"}")
120+
154− defer b.Close()
121+ if in.expectedGroups != nil {
155−
122+ Expect(session.Groups).To(Equal(in.expectedGroups))
156− bURL, _ := url.Parse(b.URL)
123+ } else {
157− p := testKeycloakProvider(bURL.Host, "")
124+ Expect(session.Groups).To(BeNil())
159− session := CreateAuthorizedSession()
126+ },
160− email, err := p.GetEmailAddress(context.Background(), session)
127+ Entry("email and multiple groups", enrichSessionTableInput{
161− assert.NotEqual(t, nil, err)
128+ backendHandler: func(w http.ResponseWriter, _ *http.Request) {
162− assert.Equal(t, "", email)
129+ w.WriteHeader(200)
130+ _, err := w.Write([]byte(`
132+ "email": "michael.bland@gsa.gov",
144+ expectedEmail: "michael.bland@gsa.gov",
145+ expectedGroups: []string{"test-grp1", "test-grp2"},
147+ Entry("email and single group", enrichSessionTableInput{
148+ backendHandler: func(w http.ResponseWriter, _ *http.Request) {
150+ _, err := w.Write([]byte(`
152+ "email": "michael.bland@gsa.gov",
153+ "groups": ["test-grp1"]
161+ expectedEmail: "michael.bland@gsa.gov",
162+ expectedGroups: []string{"test-grp1"},
164+ Entry("email and no groups", enrichSessionTableInput{
165+ backendHandler: func(w http.ResponseWriter, _ *http.Request) {
167+ _, err := w.Write([]byte(`
169+ "email": "michael.bland@gsa.gov"
177+ expectedEmail: "michael.bland@gsa.gov",
178+ expectedGroups: nil,
180+ Entry("missing email", enrichSessionTableInput{
181+ backendHandler: func(w http.ResponseWriter, _ *http.Request) {
183+ _, err := w.Write([]byte(`
195+ expectedError: errors.New(
196+ "unable to extract email from userinfo endpoint: type assertion to string failed"),
198+ expectedGroups: []string{"test-grp1", "test-grp2"},
200+ Entry("request failure", enrichSessionTableInput{
201+ backendHandler: func(w http.ResponseWriter, _ *http.Request) {
204+ expectedError: errors.New(`unexpected status "500": `),
206+ expectedGroups: nil,