git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #1210 from pb82/keycloak-oidc-provider
526aff8Nick Meves
CHANGELOG.md+3
@@ -8,6 +8,8 @@
8 deserialization from v6.0.0 (only) has been removed to improve performance. If you are on v6.0.0, either upgrade
8 deserialization from v6.0.0 (only) has been removed to improve performance. If you are on v6.0.0, either upgrade
9 to a version before this first and allow legacy sessions to expire gracefully or change your `cookie-secret`
9 to a version before this first and allow legacy sessions to expire gracefully or change your `cookie-secret`
10 value and force all sessions to reauthenticate.
10 value and force all sessions to reauthenticate.
11+
12+- [#1210](https://github.com/oauth2-proxy/oauth2-proxy/pull/1210) A new `keycloak-oidc` provider has been added with support for role based authentication. The existing keycloak auth provider will eventually be deprecated and removed. Please switch to the new provider `keycloak-oidc`.
11
13
12## Breaking Changes
14## Breaking Changes
13
15
@@ -27,6 +29,7 @@
27- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
29- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
28- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
30- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
29- [#1207](https://github.com/oauth2-proxy/oauth2-proxy/pull/1207) Fix URI fragment handling on sign-in page, regression introduced in 7.1.0 (@tarvip)
31- [#1207](https://github.com/oauth2-proxy/oauth2-proxy/pull/1207) Fix URI fragment handling on sign-in page, regression introduced in 7.1.0 (@tarvip)
32+- [#1210](https://github.com/oauth2-proxy/oauth2-proxy/pull/1210) New Keycloak OIDC Provider (@pb82)
30- [#1244](https://github.com/oauth2-proxy/oauth2-proxy/pull/1244) Update Alpine image version to 3.14 (@ahovgaard)
33- [#1244](https://github.com/oauth2-proxy/oauth2-proxy/pull/1244) Update Alpine image version to 3.14 (@ahovgaard)
31
34
32
35
docs/docs/configuration/alpha_config.md+1
@@ -250,6 +250,7 @@ make up the header value
250| Field | Type | Description |
250| Field | Type | Description |
251| ----- | ---- | ----------- |
251| ----- | ---- | ----------- |
252| `groups` | _[]string_ | Group enables to restrict login to members of indicated group |
252| `groups` | _[]string_ | Group enables to restrict login to members of indicated group |
253+| `roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) |
253
254
254### LoginGovOptions
255### LoginGovOptions
255
256
docs/docs/configuration/auth.md+24−1
@@ -146,12 +146,15 @@ If you are using GitHub enterprise, make sure you set the following to the appro
146
146
147### Keycloak Auth Provider
147### Keycloak Auth Provider
148
148
149−1. Create new client in your Keycloak with **Access Type** 'confidental' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
149+1. Create new client in your Keycloak realm with **Access Type** 'confidental' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
1502. Take note of the Secret in the credential tab of the client
1502. Take note of the Secret in the credential tab of the client
1513. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
1513. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
152
152
153+:::note this is the legacy Keycloak Auth Prodiver, use `keycloak-oidc` if possible. :::
154+
153Make sure you set the following to the appropriate url:
155Make sure you set the following to the appropriate url:
154
156
157+```
155 --provider=keycloak
158 --provider=keycloak
156 --client-id=<client you have created>
159 --client-id=<client you have created>
157 --client-secret=<your client's secret>
160 --client-secret=<your client's secret>
@@ -161,6 +164,7 @@ Make sure you set the following to the appropriate url:
161 --validate-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/userinfo"
164 --validate-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/userinfo"
162 --keycloak-group=<first_allowed_user_group>
165 --keycloak-group=<first_allowed_user_group>
163 --keycloak-group=<second_allowed_user_group>
166 --keycloak-group=<second_allowed_user_group>
167+```
164
168
165For group based authorization, the optional `--keycloak-group` (legacy) or `--allowed-group` (global standard)
169For group based authorization, the optional `--keycloak-group` (legacy) or `--allowed-group` (global standard)
166flags can be used to specify which groups to limit access to.
170flags can be used to specify which groups to limit access to.
@@ -172,6 +176,25 @@ Keycloak userinfo endpoint response.
172The group management in keycloak is using a tree. If you create a group named admin in keycloak
176The group management in keycloak is using a tree. If you create a group named admin in keycloak
173you should define the 'keycloak-group' value to /admin.
177you should define the 'keycloak-group' value to /admin.
174
178
179+### Keycloak OIDC Auth Provider
180+
181+1. Create new client in your Keycloak realm with **Access Type** 'confidental', **Client protocol** 'openid-connect' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
182+2. Take note of the Secret in the credential tab of the client
183+3. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
184+4. Create a mapper with **Mapper Type** 'Audience' and **Included Client Audience** and **Included Custom Audience** set to your client name.
185+
186+Make sure you set the following to the appropriate url:
187+
188+```
189+ --provider=keycloak-oidc
190+ --client-id=<your client's id>
191+ --client-secret=<your client's secret>
192+ --redirect-url=https://myapp.com/oauth2/callback
193+ --oidc-issuer-url=https://<keycloak host>/auth/<your realm>/basic
194+ --allowed-role=<realm role name> // Optional, required realm role
195+ --allowed-role=<client id>:<client role name> // Optional, required client role
196+```
197+
175### GitLab Auth Provider
198### GitLab Auth Provider
176
199
177This auth provider has been tested against Gitlab version 12.X. Due to Gitlab API changes, it may not work for version prior to 12.X (see [994](https://github.com/oauth2-proxy/oauth2-proxy/issues/994)).
200This auth provider has been tested against Gitlab version 12.X. Due to Gitlab API changes, it may not work for version prior to 12.X (see [994](https://github.com/oauth2-proxy/oauth2-proxy/issues/994)).
docs/docs/configuration/overview.md+1
@@ -192,6 +192,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
192| `--tls-key-file` | string | path to private key file | |
192| `--tls-key-file` | string | path to private key file | |
193| `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
193| `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
194| `--allowed-group` | string \| list | restrict logins to members of this group (may be given multiple times) | |
194| `--allowed-group` | string \| list | restrict logins to members of this group (may be given multiple times) | |
195+| `--allowed-role` | string \| list | restrict logins to users with this role (may be given multiple times). Only works with the keycloak-oidc provider. | |
195| `--validate-url` | string | Access token validation endpoint | |
196| `--validate-url` | string | Access token validation endpoint | |
196| `--version` | n/a | print version string | |
197| `--version` | n/a | print version string | |
197| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`)&nbsp;\[[2](#footnote2)\] | |
198| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`)&nbsp;\[[2](#footnote2)\] | |
pkg/apis/options/legacy_options.go+7
@@ -508,6 +508,7 @@ type LegacyProvider struct {
508 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
508 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
509 UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
509 UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
510 AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
510 AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
511+ AllowedRoles []string `flag:"allowed-role" cfg:"allowed_roles"`
511
512
512 AcrValues string `flag:"acr-values" cfg:"acr_values"`
513 AcrValues string `flag:"acr-values" cfg:"acr_values"`
513 JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
514 JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
@@ -563,6 +564,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
563
564
564 flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
565 flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
565 flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
566 flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
567+ flagSet.StringSlice("allowed-role", []string{}, "(keycloak-oidc) restrict logins to members of these roles (may be given multiple times)")
566
568
567 return flagSet
569 return flagSet
568}
570}
@@ -656,6 +658,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
656 Token: l.GitHubToken,
658 Token: l.GitHubToken,
657 Users: l.GitHubUsers,
659 Users: l.GitHubUsers,
658 }
660 }
661+ case "keycloak-oidc":
662+ provider.KeycloakConfig = KeycloakOptions{
663+ Groups: l.KeycloakGroups,
664+ Roles: l.AllowedRoles,
665+ }
659 case "keycloak":
666 case "keycloak":
660 provider.KeycloakConfig = KeycloakOptions{
667 provider.KeycloakConfig = KeycloakOptions{
661 Groups: l.KeycloakGroups,
668 Groups: l.KeycloakGroups,
pkg/apis/options/providers.go+3
@@ -78,6 +78,9 @@ type Provider struct {
78type KeycloakOptions struct {
78type KeycloakOptions struct {
79 // Group enables to restrict login to members of indicated group
79 // Group enables to restrict login to members of indicated group
80 Groups []string `json:"groups,omitempty"`
80 Groups []string `json:"groups,omitempty"`
81+
82+ // Role enables to restrict login to users with role (only available when using the keycloak-oidc provider)
83+ Roles []string `json:"roles,omitempty"`
81}
84}
82
85
83type AzureOptions struct {
86type AzureOptions struct {
pkg/validation/options.go+5
@@ -247,6 +247,11 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
247 if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
247 if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
248 p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
248 p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
249 }
249 }
250+ case *providers.KeycloakOIDCProvider:
251+ if p.Verifier == nil {
252+ msgs = append(msgs, "keycloak-oidc provider requires an oidc issuer URL")
253+ }
254+ p.AddAllowedRoles(o.Providers[0].KeycloakConfig.Roles)
250 case *providers.GoogleProvider:
255 case *providers.GoogleProvider:
251 if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
256 if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
252 file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
257 file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
providers/keycloak_oidc.gonew file+142
@@ -0,0 +1,142 @@
1+package providers
2+
3+import (
4+ "context"
5+ "fmt"
6+
7+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
8+)
9+
10+const keycloakOIDCProviderName = "Keycloak OIDC"
11+
12+// KeycloakOIDCProvider creates a Keycloak provider based on OIDCProvider
13+type KeycloakOIDCProvider struct {
14+ *OIDCProvider
15+}
16+
17+// NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
18+func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider {
19+ p.ProviderName = keycloakOIDCProviderName
20+ return &KeycloakOIDCProvider{
21+ OIDCProvider: &OIDCProvider{
22+ ProviderData: p,
23+ },
24+ }
25+}
26+
27+var _ Provider = (*KeycloakOIDCProvider)(nil)
28+
29+// AddAllowedRoles sets Keycloak roles that are authorized.
30+// Assumes `SetAllowedGroups` is already called on groups and appends to that
31+// with `role:` prefixed roles.
32+func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) {
33+ if p.AllowedGroups == nil {
34+ p.AllowedGroups = make(map[string]struct{})
35+ }
36+ for _, role := range roles {
37+ p.AllowedGroups[formatRole(role)] = struct{}{}
38+ }
39+}
40+
41+// EnrichSession is called after Redeem to allow providers to enrich session fields
42+// such as User, Email, Groups with provider specific API calls.
43+func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
44+ err := p.OIDCProvider.EnrichSession(ctx, s)
45+ if err != nil {
46+ return fmt.Errorf("could not enrich oidc session: %v", err)
47+ }
48+ return p.extractRoles(ctx, s)
49+}
50+
51+// RefreshSession adds role extraction logic to the refresh flow
52+func (p *KeycloakOIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
53+ refreshed, err := p.OIDCProvider.RefreshSession(ctx, s)
54+
55+ // Refresh could have failed or there was not session to refresh (with no error raised)
56+ if err != nil || !refreshed {
57+ return refreshed, err
58+ }
59+
60+ return true, p.extractRoles(ctx, s)
61+}
62+
63+func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
64+ claims, err := p.getAccessClaims(ctx, s)
65+ if err != nil {
66+ return err
67+ }
68+
69+ var roles []string
70+ roles = append(roles, claims.RealmAccess.Roles...)
71+ roles = append(roles, getClientRoles(claims)...)
72+
73+ // Add to groups list with `role:` prefix to distinguish from groups
74+ for _, role := range roles {
75+ s.Groups = append(s.Groups, formatRole(role))
76+ }
77+ return nil
78+}
79+
80+type realmAccess struct {
81+ Roles []string `json:"roles"`
82+}
83+
84+type accessClaims struct {
85+ RealmAccess realmAccess `json:"realm_access"`
86+ ResourceAccess map[string]interface{} `json:"resource_access"`
87+}
88+
89+func (p *KeycloakOIDCProvider) getAccessClaims(ctx context.Context, s *sessions.SessionState) (*accessClaims, error) {
90+ // HACK: This isn't an ID Token, but has similar structure & signing
91+ token, err := p.Verifier.Verify(ctx, s.AccessToken)
92+ if err != nil {
93+ return nil, err
94+ }
95+
96+ var claims *accessClaims
97+ if err = token.Claims(&claims); err != nil {
98+ return nil, err
99+ }
100+ return claims, nil
101+}
102+
103+// getClientRoles extracts client roles from the `resource_access` claim with
104+// the format `client:role`.
105+//
106+// ResourceAccess format:
107+// "resource_access": {
108+// "clientA": {
109+// "roles": [
110+// "roleA"
111+// ]
112+// },
113+// "clientB": {
114+// "roles": [
115+// "roleA",
116+// "roleB",
117+// "roleC"
118+// ]
119+// }
120+// }
121+func getClientRoles(claims *accessClaims) []string {
122+ var clientRoles []string
123+ for clientName, access := range claims.ResourceAccess {
124+ accessMap, ok := access.(map[string]interface{})
125+ if !ok {
126+ continue
127+ }
128+
129+ var roles interface{}
130+ if roles, ok = accessMap["roles"]; !ok {
131+ continue
132+ }
133+ for _, role := range roles.([]interface{}) {
134+ clientRoles = append(clientRoles, fmt.Sprintf("%s:%s", clientName, role))
135+ }
136+ }
137+ return clientRoles
138+}
139+
140+func formatRole(role string) string {
141+ return fmt.Sprintf("role:%s", role)
142+}
providers/keycloak_oidc_test.gonew file+189
@@ -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+})
providers/providers.go+2
@@ -31,6 +31,8 @@ func New(provider string, p *ProviderData) Provider {
31 return NewGitHubProvider(p)
31 return NewGitHubProvider(p)
32 case "keycloak":
32 case "keycloak":
33 return NewKeycloakProvider(p)
33 return NewKeycloakProvider(p)
34+ case "keycloak-oidc":
35+ return NewKeycloakOIDCProvider(p)
34 case "azure":
36 case "azure":
35 return NewAzureProvider(p)
37 return NewAzureProvider(p)
36 case "adfs":
38 case "adfs":