@@ -0,0 +1,189 @@
1+package providers
2+
3+import (
4+ "context"
5+ "encoding/base64"
6+ "fmt"
7+ "net/http/httptest"
8+ "net/url"
9+
10+ "github.com/coreos/go-oidc/v3/oidc"
11+
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13+ . "github.com/onsi/ginkgo"
14+ . "github.com/onsi/gomega"
15+)
16+
17+const (
18+ accessTokenHeader = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9"
19+ accessTokenPayload = "eyJyZWFsbV9hY2Nlc3MiOiB7InJvbGVzIjogWyJ3cml0ZSJdfSwgInJlc291cmNlX2FjY2VzcyI6IHsiZGVmYXVsdCI6IHsicm9sZXMiOiBbInJlYWQiXX19fQ"
20+ accessTokenSignature = "dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao"
21+)
22+
23+type DummyKeySet struct{}
24+
25+func (DummyKeySet) VerifySignature(_ context.Context, _ string) (payload []byte, err error) {
26+ p, _ := base64.RawURLEncoding.DecodeString(accessTokenPayload)
27+ return p, nil
28+}
29+
30+func getAccessToken() string {
31+ return fmt.Sprintf("%s.%s.%s", accessTokenHeader, accessTokenPayload, accessTokenSignature)
32+}
33+
34+func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
35+ redeemURL, server := newOIDCServer([]byte(fmt.Sprintf(`{"email": "new@thing.com", "expires_in": 300, "access_token": "%v"}`, getAccessToken())))
36+ provider := newKeycloakOIDCProvider(redeemURL)
37+ return server, provider
38+}
39+
40+func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
41+ p := NewKeycloakOIDCProvider(
42+ &ProviderData{
43+ LoginURL: &url.URL{
44+ Scheme: "https",
45+ Host: "keycloak-oidc.com",
46+ Path: "/oauth/auth"},
47+ RedeemURL: &url.URL{
48+ Scheme: "https",
49+ Host: "keycloak-oidc.com",
50+ Path: "/oauth/token"},
51+ ProfileURL: &url.URL{
52+ Scheme: "https",
53+ Host: "keycloak-oidc.com",
54+ Path: "/api/v3/user"},
55+ ValidateURL: &url.URL{
56+ Scheme: "https",
57+ Host: "keycloak-oidc.com",
58+ Path: "/api/v3/user"},
59+ Scope: "openid email profile"})
60+
61+ if serverURL != nil {
62+ p.RedeemURL.Scheme = serverURL.Scheme
63+ p.RedeemURL.Host = serverURL.Host
64+ }
65+
66+ keyset := DummyKeySet{}
67+ p.Verifier = oidc.NewVerifier("", keyset, &oidc.Config{
68+ ClientID: "client",
69+ SkipIssuerCheck: true,
70+ SkipClientIDCheck: true,
71+ SkipExpiryCheck: true,
72+ })
73+ p.EmailClaim = "email"
74+ p.GroupsClaim = "groups"
75+ return p
76+}
77+
78+var _ = Describe("Keycloak OIDC Provider Tests", func() {
79+ Context("New Provider Init", func() {
80+ It("creates new keycloak oidc provider with expected defaults", func() {
81+ p := newKeycloakOIDCProvider(nil)
82+ providerData := p.Data()
83+ Expect(providerData.ProviderName).To(Equal(keycloakOIDCProviderName))
84+ Expect(providerData.LoginURL.String()).To(Equal("https://keycloak-oidc.com/oauth/auth"))
85+ Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak-oidc.com/oauth/token"))
86+ Expect(providerData.ProfileURL.String()).To(Equal("https://keycloak-oidc.com/api/v3/user"))
87+ Expect(providerData.ValidateURL.String()).To(Equal("https://keycloak-oidc.com/api/v3/user"))
88+ Expect(providerData.Scope).To(Equal("openid email profile"))
89+ })
90+ })
91+
92+ Context("Allowed Roles", func() {
93+ It("should prefix allowed roles and add them to groups", func() {
94+ p := newKeycloakOIDCProvider(nil)
95+ p.AddAllowedRoles([]string{"admin", "editor"})
96+ Expect(p.AllowedGroups).To(HaveKey("role:admin"))
97+ Expect(p.AllowedGroups).To(HaveKey("role:editor"))
98+ })
99+ })
100+
101+ Context("Enrich Session", func() {
102+ It("should not fail when groups are not assigned", func() {
103+ server, provider := newTestKeycloakOIDCSetup()
104+ url, err := url.Parse(server.URL)
105+ Expect(err).To(BeNil())
106+ defer server.Close()
107+
108+ provider.ProfileURL = url
109+
110+ existingSession := &sessions.SessionState{
111+ User: "already",
112+ Email: "a@b.com",
113+ Groups: nil,
114+ IDToken: idToken,
115+ AccessToken: getAccessToken(),
116+ RefreshToken: refreshToken,
117+ }
118+ expectedSession := &sessions.SessionState{
119+ User: "already",
120+ Email: "a@b.com",
121+ Groups: []string{"role:write", "role:default:read"},
122+ IDToken: idToken,
123+ AccessToken: getAccessToken(),
124+ RefreshToken: refreshToken,
125+ }
126+
127+ err = provider.EnrichSession(context.Background(), existingSession)
128+ Expect(err).To(BeNil())
129+ Expect(existingSession).To(Equal(expectedSession))
130+ })
131+
132+ It("should add roles to existing groups", func() {
133+ server, provider := newTestKeycloakOIDCSetup()
134+ url, err := url.Parse(server.URL)
135+ Expect(err).To(BeNil())
136+ defer server.Close()
137+
138+ provider.ProfileURL = url
139+
140+ existingSession := &sessions.SessionState{
141+ User: "already",
142+ Email: "a@b.com",
143+ Groups: []string{"existing", "group"},
144+ IDToken: idToken,
145+ AccessToken: getAccessToken(),
146+ RefreshToken: refreshToken,
147+ }
148+ expectedSession := &sessions.SessionState{
149+ User: "already",
150+ Email: "a@b.com",
151+ Groups: []string{"existing", "group", "role:write", "role:default:read"},
152+ IDToken: idToken,
153+ AccessToken: getAccessToken(),
154+ RefreshToken: refreshToken,
155+ }
156+
157+ err = provider.EnrichSession(context.Background(), existingSession)
158+ Expect(err).To(BeNil())
159+ Expect(existingSession).To(Equal(expectedSession))
160+ })
161+ })
162+
163+ Context("Refresh Session", func() {
164+ It("should refresh session and extract roles again", func() {
165+ server, provider := newTestKeycloakOIDCSetup()
166+ url, err := url.Parse(server.URL)
167+ Expect(err).To(BeNil())
168+ defer server.Close()
169+
170+ provider.ProfileURL = url
171+
172+ existingSession := &sessions.SessionState{
173+ User: "already",
174+ Email: "a@b.com",
175+ Groups: nil,
176+ IDToken: idToken,
177+ AccessToken: getAccessToken(),
178+ RefreshToken: refreshToken,
179+ }
180+
181+ refreshed, err := provider.RefreshSession(context.Background(), existingSession)
182+ Expect(err).To(BeNil())
183+ Expect(refreshed).To(BeTrue())
184+ Expect(existingSession.ExpiresOn).ToNot(BeNil())
185+ Expect(existingSession.CreatedAt).ToNot(BeNil())
186+ Expect(existingSession.Groups).To(BeEquivalentTo([]string{"role:write", "role:default:read"}))
187+ })
188+ })
189+})