git.iko.soy/typeable/oauth2-proxy
on master help sign in
feat: add Cidaas provider (#2273)

* Add sensible logging flag to default setup for logger * Fix default value flag for sensitive logging * Remove sensitive logging changes * Add Cidaas provider * Update CHANGELOG.md * Add required groups scope to defaults * Fix tests * Remove if block with protected resource * Fix linting * Adjust provider sorting, fixes * Directly handle error return Co-authored-by: Jan Larwig <jan@larwig.com> * Use less deep nesting Co-authored-by: Jan Larwig <jan@larwig.com> * Directly handle returned error Co-authored-by: Jan Larwig <jan@larwig.com> * Pass provider options to Cidaas provider Co-authored-by: Jan Larwig <jan@larwig.com> * Add import for provider options * Fix tests * Fix linting * Add Cidaas doc page * Add Cidaas provider doc page to overview * Fix link in docs * Fix link in docs * Add link to Cidaas * fix provider order in docs and changelog position Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Teko012 <112829523+Teko012@users.noreply.github.com> Co-authored-by: Jan Larwig <jan@larwig.com> Co-authored-by: Kevin Kreitner <kevinkreitner@gmail.com>

commit4c86a4d574952ffa829dbe1f5a32c48b3f2e1ddc authorKevin Kreitner <kevin.kreitner@real-digital.de> dateTue 12 Aug 2025, 17:41 (1 year ago) parent9667bce
7 files changed+684−1
CHANGELOG.md+2 docs/docs/configuration/providers/cidaas.md+37 docs/docs/configuration/providers/index.md+1 pkg/apis/options/providers.go+3 providers/cidaas.go+144 providers/cidaas_test.go+493 providers/providers.go+4−1
CHANGELOG.md+2
@@ -8,6 +8,8 @@
88
99## Changes since v7.11.0
1010
11+- [#2273](https://github.com/oauth2-proxy/oauth2-proxy/pull/2273) feat: add Cidaas provider (@Bibob7, @Teko012)
12+
1113# V7.11.0
1214
1315## Release Highlights
docs/docs/configuration/providers/cidaas.mdnew file+37
@@ -0,0 +1,37 @@
1+---
2+id: cidaas
3+title: Cidaas
4+---
5+
6+[Cidaas](https://www.cidaas.com/) is an Identity as a Service (IDaaS) solution that provides authentication and authorization services.
7+It supports various protocols including OpenID Connect, OAuth 2.0, and SAML.
8+
9+However, Cidaas provides groups and their roles as hierarchical claims, which are not supported by oauth2-proxy yet.
10+The Cidaas provider transforms the hierarchical claims into a flat list of groups, which can be used by oauth2-proxy.
11+
12+Example of groups and roles in Cidaas:
13+
14+```json
15+{
16+ "groups": [
17+ {
18+ "groupId": "group1",
19+ "roles": ["role1", "role2"]
20+ },
21+ {
22+ "groupId": "group2",
23+ "roles": ["role3"]
24+ }
25+ ]
26+}
27+```
28+
29+This will be transformed into a flat list of groups:
30+
31+```json
32+{
33+ "groups": ["group1:role1", "group2:role2", "group2:role3"]
34+}
35+```
36+
37+Apart from that the Cidaas provider inherits all the features of the [OpenID Connect provider](openid_connect.md).
docs/docs/configuration/providers/index.md+1
@@ -10,6 +10,7 @@ Valid providers are :
1010
1111- [ADFS](adfs.md)
1212- [Bitbucket](bitbucket.md)
13+- [Cidaas](cidaas.md)
1314- [DigitalOcean](digitalocean.md)
1415- [Facebook](facebook.md)
1516- [Gitea](gitea.md)
pkg/apis/options/providers.go+3
@@ -115,6 +115,9 @@ const (
115115 // BitbucketProvider is the provider type for Bitbucket
116116 BitbucketProvider ProviderType = "bitbucket"
117117
118+ // CidaasProvider is the provider type for Cidaas IDP
119+ CidaasProvider ProviderType = "cidaas"
120+
118121 // DigitalOceanProvider is the provider type for DigitalOcean
119122 DigitalOceanProvider ProviderType = "digitalocean"
120123
providers/cidaas.gonew file+144
@@ -0,0 +1,144 @@
1+package providers
2+
3+import (
4+ "context"
5+ "encoding/json"
6+ "errors"
7+ "fmt"
8+
9+ "github.com/bitly/go-simplejson"
10+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
13+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
14+)
15+
16+type GroupsClaimList []GroupClaimEntry
17+
18+type GroupClaimEntry struct {
19+ GroupID string `json:"groupId"`
20+ Roles []string `json:"roles"`
21+}
22+
23+// CIDAASProvider represents an CIDAAS based Identity Provider
24+type CIDAASProvider struct {
25+ *OIDCProvider
26+}
27+
28+var _ Provider = (*CIDAASProvider)(nil)
29+
30+const (
31+ CidaasProviderName = "CIDAAS"
32+ CidaasGroupName = "cidaas"
33+ CidaasDefaultScope = "openid email profile roles groups"
34+)
35+
36+// NewCIDAASProvider initiates a new CIDAASProvider
37+func NewCIDAASProvider(p *ProviderData, opts options.Provider) *CIDAASProvider {
38+ p.setProviderDefaults(providerDefaults{
39+ name: CidaasProviderName,
40+ scope: CidaasDefaultScope,
41+ })
42+
43+ return &CIDAASProvider{
44+ OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
45+ }
46+}
47+
48+// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
49+func (p *CIDAASProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
50+ if s == nil || s.RefreshToken == "" {
51+ return false, nil
52+ }
53+
54+ if err := p.redeemRefreshToken(ctx, s); err != nil {
55+ return false, fmt.Errorf("unable to redeem refresh token: %w", err)
56+ }
57+
58+ if err := p.EnrichSession(ctx, s); err != nil {
59+ return false, fmt.Errorf("unable to enrich session data after refresh: %w %v", err, s)
60+ }
61+
62+ return true, nil
63+}
64+
65+// EnrichSession data to add email an groups
66+func (p *CIDAASProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
67+ if p.ProfileURL.String() == "" && s.Email == "" {
68+ return errors.New("id_token did not contain an email and profileURL is not defined")
69+ } else if p.ProfileURL.String() == "" {
70+ return nil
71+ }
72+
73+ // Try to get missing emails or groups from a profileURL
74+ if err := p.enrichFromUserinfoEndpoint(ctx, s); err != nil {
75+ logger.Errorf("Warning: Profile URL request failed: %s", err)
76+ }
77+
78+ // If a mandatory email wasn't set, error at this point.
79+ if s.Email == "" {
80+ return errors.New("neither the id_token nor the profileURL set an email")
81+ }
82+ return nil
83+}
84+
85+// enrichFromUserinfoEndpoint enriches a session's Email & Groups via the JSON response of
86+// an OIDC profile URL
87+func (p *CIDAASProvider) enrichFromUserinfoEndpoint(ctx context.Context, s *sessions.SessionState) error {
88+ // profile url is userinfo url in case of Cidaas
89+ respJSON, err := requests.New(p.ProfileURL.String()).
90+ WithContext(ctx).
91+ WithHeaders(makeOIDCHeader(s.AccessToken)).
92+ Do().
93+ UnmarshalSimpleJSON()
94+ if err != nil {
95+ return err
96+ }
97+
98+ email, err := respJSON.Get(p.EmailClaim).String()
99+ if err == nil && s.Email == "" {
100+ s.Email = email
101+ }
102+
103+ groups, err := p.extractGroups(respJSON)
104+ if err != nil {
105+ return fmt.Errorf("extracting groups failed: %w", err)
106+ }
107+
108+ s.Groups = groups
109+ return nil
110+}
111+
112+func (p *CIDAASProvider) extractGroups(respJSON *simplejson.Json) ([]string, error) {
113+ rawGroupsClaim, err := respJSON.Get(p.GroupsClaim).MarshalJSON()
114+ if err != nil {
115+ return nil, err
116+ }
117+
118+ var groupsClaimList GroupsClaimList
119+ err = json.Unmarshal(rawGroupsClaim, &groupsClaimList)
120+ if err != nil {
121+ return nil, err
122+ }
123+
124+ var groups []string
125+ for _, group := range groupsClaimList {
126+ for _, role := range group.Roles {
127+ groups = append(groups, fmt.Sprintf("%s:%s", group.GroupID, role))
128+ }
129+ }
130+
131+ // Cidaas specific roles
132+ if rolesVal, rolesClaimExists := respJSON.CheckGet("roles"); rolesClaimExists {
133+ cidaasRoles, err := rolesVal.StringArray()
134+ if err != nil {
135+ return nil, fmt.Errorf("unmarshal roles failed: %w", err)
136+ }
137+
138+ for _, role := range cidaasRoles {
139+ groups = append(groups, fmt.Sprintf("%s:%s", CidaasGroupName, role))
140+ }
141+ }
142+
143+ return groups, nil
144+}
providers/cidaas_test.gonew file+493
@@ -0,0 +1,493 @@
1+package providers
2+
3+import (
4+ "context"
5+ "encoding/json"
6+ "errors"
7+ "fmt"
8+ "net/http"
9+ "net/http/httptest"
10+ "net/url"
11+ "testing"
12+
13+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
14+
15+ "github.com/coreos/go-oidc/v3/oidc"
16+ "github.com/stretchr/testify/assert"
17+
18+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
19+)
20+
21+func newCidaasProvider(serverURL *url.URL) *CIDAASProvider {
22+ providerData := &ProviderData{
23+ ProviderName: "cidaas",
24+ ClientID: oidcClientID,
25+ ClientSecret: oidcSecret,
26+ LoginURL: &url.URL{
27+ Scheme: serverURL.Scheme,
28+ Host: serverURL.Host,
29+ Path: "/login/oauth/authorize"},
30+ RedeemURL: &url.URL{
31+ Scheme: serverURL.Scheme,
32+ Host: serverURL.Host,
33+ Path: "/login/oauth/access_token"},
34+ ProfileURL: &url.URL{
35+ Scheme: serverURL.Scheme,
36+ Host: serverURL.Host,
37+ Path: "/profile"},
38+ ValidateURL: &url.URL{
39+ Scheme: serverURL.Scheme,
40+ Host: serverURL.Host,
41+ Path: "/api"},
42+ Scope: "openid profile offline_access roles groups",
43+ EmailClaim: "email",
44+ GroupsClaim: "groups",
45+ Verifier: oidc.NewVerifier(
46+ oidcIssuer,
47+ mockJWKS{},
48+ &oidc.Config{ClientID: oidcClientID},
49+ ),
50+ }
51+ cfg := options.Provider{
52+ Type: options.CidaasProvider,
53+ }
54+
55+ p := NewCIDAASProvider(providerData, cfg)
56+
57+ return p
58+}
59+
60+func newCidaasServer(pathBodyMap map[string][]byte) (*url.URL, *httptest.Server) {
61+ s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
62+ body, ok := pathBodyMap[r.URL.Path]
63+ if !ok {
64+ rw.WriteHeader(404)
65+ return
66+ }
67+ rw.Header().Add("content-type", "application/json")
68+ _, _ = rw.Write(body)
69+ }))
70+ u, _ := url.Parse(s.URL)
71+ return u, s
72+}
73+
74+func newTestCidaasSetup(pathToBodyMap map[string][]byte) (*httptest.Server, *CIDAASProvider) {
75+ redeemURL, server := newCidaasServer(pathToBodyMap)
76+ provider := newCidaasProvider(redeemURL)
77+ return server, provider
78+}
79+
80+func TestCidaasProvider_EnrichSession(t *testing.T) {
81+ testCases := map[string]struct {
82+ ExistingSession *sessions.SessionState
83+ EmailClaim string
84+ GroupsClaim string
85+ ProfileJSON map[string]interface{}
86+ ExpectedError error
87+ ExpectedSession *sessions.SessionState
88+ }{
89+ "Missing Email Only in Profile URL": {
90+ ExistingSession: &sessions.SessionState{
91+ User: "missing.email",
92+ IDToken: idToken,
93+ AccessToken: accessToken,
94+ RefreshToken: refreshToken,
95+ },
96+ EmailClaim: "email",
97+ GroupsClaim: "groups",
98+ ProfileJSON: map[string]interface{}{
99+ "email": "found@email.com",
100+ },
101+ ExpectedError: nil,
102+ ExpectedSession: &sessions.SessionState{
103+ User: "missing.email",
104+ Email: "found@email.com",
105+ IDToken: idToken,
106+ AccessToken: accessToken,
107+ RefreshToken: refreshToken,
108+ },
109+ },
110+ "Missing Email with Custom Claim": {
111+ ExistingSession: &sessions.SessionState{
112+ User: "missing.email",
113+ Groups: []string{"already", "populated"},
114+ IDToken: idToken,
115+ AccessToken: accessToken,
116+ RefreshToken: refreshToken,
117+ },
118+ EmailClaim: "weird",
119+ GroupsClaim: "groups",
120+ ProfileJSON: map[string]interface{}{
121+ "weird": "weird@claim.com",
122+ "groups": []map[string]interface{}{
123+ {
124+ "groupId": "CIDAAS_USERS",
125+ "roles": []string{"USER"},
126+ },
127+ },
128+ "roles": []string{"USER"},
129+ },
130+ ExpectedError: nil,
131+ ExpectedSession: &sessions.SessionState{
132+ User: "missing.email",
133+ Email: "weird@claim.com",
134+ Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
135+ IDToken: idToken,
136+ AccessToken: accessToken,
137+ RefreshToken: refreshToken,
138+ },
139+ },
140+ "Missing Email not in Profile URL": {
141+ ExistingSession: &sessions.SessionState{
142+ User: "missing.email",
143+ Groups: []string{"already", "populated"},
144+ IDToken: idToken,
145+ AccessToken: accessToken,
146+ RefreshToken: refreshToken,
147+ },
148+ EmailClaim: "email",
149+ GroupsClaim: "groups",
150+ ProfileJSON: map[string]interface{}{
151+ "groups": []map[string]interface{}{
152+ {
153+ "groupId": "CIDAAS_USERS",
154+ "roles": []string{"USER"},
155+ },
156+ },
157+ "roles": []string{"USER"},
158+ },
159+ ExpectedError: errors.New("neither the id_token nor the profileURL set an email"),
160+ ExpectedSession: &sessions.SessionState{
161+ User: "missing.email",
162+ Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
163+ IDToken: idToken,
164+ AccessToken: accessToken,
165+ RefreshToken: refreshToken,
166+ },
167+ },
168+ "Missing Groups": {
169+ ExistingSession: &sessions.SessionState{
170+ User: "already",
171+ Email: "already@populated.com",
172+ Groups: nil,
173+ IDToken: idToken,
174+ AccessToken: accessToken,
175+ RefreshToken: refreshToken,
176+ },
177+ EmailClaim: "email",
178+ GroupsClaim: "groups",
179+ ProfileJSON: map[string]interface{}{
180+ "email": "new@thing.com",
181+ "groups": []map[string]interface{}{
182+ {
183+ "groupId": "CIDAAS_USERS",
184+ "roles": []string{"USER"},
185+ },
186+ },
187+ "roles": []string{"USER"},
188+ },
189+ ExpectedError: nil,
190+ ExpectedSession: &sessions.SessionState{
191+ User: "already",
192+ Email: "already@populated.com",
193+ Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
194+ IDToken: idToken,
195+ AccessToken: accessToken,
196+ RefreshToken: refreshToken,
197+ },
198+ },
199+ "Empty Groups Claims": {
200+ ExistingSession: &sessions.SessionState{
201+ User: "already",
202+ Email: "already@populated.com",
203+ Groups: []string{},
204+ IDToken: idToken,
205+ AccessToken: accessToken,
206+ RefreshToken: refreshToken,
207+ },
208+ EmailClaim: "email",
209+ GroupsClaim: "groups",
210+ ProfileJSON: map[string]interface{}{
211+ "email": "new@thing.com",
212+ "groups": []map[string]interface{}{
213+ {
214+ "groupId": "CIDAAS_USERS",
215+ "roles": []string{"USER"},
216+ },
217+ },
218+ "roles": []string{"USER"},
219+ },
220+ ExpectedError: nil,
221+ ExpectedSession: &sessions.SessionState{
222+ User: "already",
223+ Email: "already@populated.com",
224+ Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
225+ IDToken: idToken,
226+ AccessToken: accessToken,
227+ RefreshToken: refreshToken,
228+ },
229+ },
230+ "Missing Groups with Custom Claim": {
231+ ExistingSession: &sessions.SessionState{
232+ User: "already",
233+ Email: "already@populated.com",
234+ Groups: nil,
235+ IDToken: idToken,
236+ AccessToken: accessToken,
237+ RefreshToken: refreshToken,
238+ },
239+ EmailClaim: "email",
240+ GroupsClaim: "groups2",
241+ ProfileJSON: map[string]interface{}{
242+ "email": "already@populated.com",
243+ "groups2": []map[string]interface{}{
244+ {
245+ "sub": "aa5181ea-0841-4ea7-b67f-81882f153d40",
246+ "groupId": "CIDAAS_ADMINS",
247+ "path": "/CIDAAS_ADMINS/",
248+ "roles": []string{"ADMIN"},
249+ },
250+ {
251+ "sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
252+ "groupId": "customers",
253+ "groupType": "Customers",
254+ "path": "/customers/",
255+ "roles": []string{
256+ "CUSTOMER_ACCOUNT_LOGIN",
257+ "GROUP_ADMIN",
258+ },
259+ },
260+ {
261+ "groupId": "CIDAAS_USERS",
262+ "roles": []string{"USER"},
263+ },
264+ },
265+ "roles": []string{"USER"},
266+ },
267+ ExpectedError: nil,
268+ ExpectedSession: &sessions.SessionState{
269+ User: "already",
270+ Email: "already@populated.com",
271+ Groups: []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
272+ IDToken: idToken,
273+ AccessToken: accessToken,
274+ RefreshToken: refreshToken,
275+ },
276+ },
277+ "Just format Groups": {
278+ ExistingSession: &sessions.SessionState{
279+ User: "already",
280+ Email: "already@populated.com",
281+ Groups: nil,
282+ IDToken: idToken,
283+ AccessToken: accessToken,
284+ RefreshToken: refreshToken,
285+ },
286+ EmailClaim: "email",
287+ GroupsClaim: "groups2",
288+ ProfileJSON: map[string]interface{}{
289+ "email": "already@populated.com",
290+ "groups2": []map[string]interface{}{
291+ {
292+ "sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
293+ "groupId": "customers",
294+ "groupType": "Customers",
295+ "path": "/customers/",
296+ "roles": []string{
297+ "CUSTOMER_ACCOUNT_LOGIN",
298+ "GROUP_ADMIN",
299+ },
300+ },
301+ {
302+ "groupId": "CIDAAS_USERS",
303+ "roles": []string{"USER"},
304+ },
305+ },
306+ "roles": []string{"USER"},
307+ },
308+ ExpectedError: nil,
309+ ExpectedSession: &sessions.SessionState{
310+ User: "already",
311+ Email: "already@populated.com",
312+ Groups: []string{"customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
313+ IDToken: idToken,
314+ AccessToken: accessToken,
315+ RefreshToken: refreshToken,
316+ },
317+ },
318+ "Missing Groups String Profile URL Response": {
319+ ExistingSession: &sessions.SessionState{
320+ User: "already",
321+ Email: "already@populated.com",
322+ Groups: nil,
323+ IDToken: idToken,
324+ AccessToken: accessToken,
325+ RefreshToken: refreshToken,
326+ },
327+ EmailClaim: "email",
328+ GroupsClaim: "groups",
329+ ProfileJSON: map[string]interface{}{
330+ "groups": []map[string]interface{}{
331+ {
332+ "sub": "aa5181ea-0841-4ea7-b67f-81882f153d40",
333+ "groupId": "CIDAAS_ADMINS",
334+ "path": "/CIDAAS_ADMINS/",
335+ "roles": []string{"ADMIN"},
336+ },
337+ {
338+ "sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
339+ "groupId": "customers",
340+ "groupType": "Customers",
341+ "path": "/customers/",
342+ "roles": []string{
343+ "CUSTOMER_ACCOUNT_LOGIN",
344+ "GROUP_ADMIN",
345+ },
346+ },
347+ {
348+ "groupId": "CIDAAS_USERS",
349+ "roles": []string{"USER"},
350+ },
351+ },
352+ "roles": []string{"USER"},
353+ },
354+ ExpectedError: nil,
355+ ExpectedSession: &sessions.SessionState{
356+ User: "already",
357+ Email: "already@populated.com",
358+ Groups: []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
359+ IDToken: idToken,
360+ AccessToken: accessToken,
361+ RefreshToken: refreshToken,
362+ },
363+ },
364+ "Missing Groups in both Claims and Profile URL": {
365+ ExistingSession: &sessions.SessionState{
366+ User: "already",
367+ Email: "already@populated.com",
368+ IDToken: idToken,
369+ AccessToken: accessToken,
370+ RefreshToken: refreshToken,
371+ },
372+ EmailClaim: "email",
373+ GroupsClaim: "groups",
374+ ProfileJSON: map[string]interface{}{
375+ "email": "new@thing.com",
376+ },
377+ ExpectedError: nil,
378+ ExpectedSession: &sessions.SessionState{
379+ User: "already",
380+ Email: "already@populated.com",
381+ IDToken: idToken,
382+ AccessToken: accessToken,
383+ RefreshToken: refreshToken,
384+ },
385+ },
386+ }
387+ for testName, tc := range testCases {
388+ t.Run(testName, func(t *testing.T) {
389+ path := "/userinfo/"
390+ jsonResp, err := json.Marshal(tc.ProfileJSON)
391+ assert.NoError(t, err)
392+
393+ server, provider := newTestCidaasSetup(map[string][]byte{path: jsonResp})
394+ provider.ProfileURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, path))
395+ assert.NoError(t, err)
396+
397+ provider.EmailClaim = tc.EmailClaim
398+ provider.GroupsClaim = tc.GroupsClaim
399+ defer server.Close()
400+
401+ err = provider.EnrichSession(context.Background(), tc.ExistingSession)
402+ if tc.ExpectedError != nil {
403+ assert.EqualError(t, err, tc.ExpectedError.Error())
404+ } else {
405+ assert.NoError(t, err)
406+ }
407+ assert.Equal(t, *tc.ExpectedSession, *tc.ExistingSession)
408+ })
409+ }
410+}
411+
412+func TestCidaasProvider_RefreshSession(t *testing.T) {
413+ testCases := map[string]struct {
414+ ExistingSession *sessions.SessionState
415+ EmailClaim string
416+ GroupsClaim string
417+ ProfileJSON map[string]interface{}
418+ RedeemJSON redeemTokenResponse
419+ ExpectedRefreshed bool
420+ ExpectedError error
421+ ExpectedEmail string
422+ ExpectedUser string
423+ }{
424+ "Refresh session successfully": {
425+ ExistingSession: &sessions.SessionState{
426+ User: "session.is.not.locked",
427+ Email: "found@email.com",
428+ IDToken: idToken,
429+ AccessToken: accessToken,
430+ RefreshToken: refreshToken,
431+ },
432+ RedeemJSON: redeemTokenResponse{
433+ AccessToken: accessToken,
434+ ExpiresIn: 10,
435+ TokenType: "Bearer",
436+ RefreshToken: refreshToken,
437+ },
438+ ExpectedRefreshed: true,
439+ ExpectedError: nil,
440+ ExpectedEmail: defaultIDToken.Email,
441+ ExpectedUser: defaultIDToken.Subject,
442+ },
443+ "Unable to refresh session": {
444+ ExistingSession: &sessions.SessionState{
445+ User: "session.is.unable.to.refresh",
446+ Email: "found@email.com",
447+ IDToken: idToken,
448+ AccessToken: accessToken,
449+ RefreshToken: refreshToken,
450+ },
451+ ExpectedRefreshed: false,
452+ ExpectedError: fmt.Errorf("unable to redeem refresh token: failed to get token: oauth2: server response missing access_token"),
453+ ExpectedUser: "session.is.unable.to.refresh",
454+ ExpectedEmail: "found@email.com",
455+ },
456+ }
457+ for testName, tc := range testCases {
458+ t.Run(testName, func(t *testing.T) {
459+ idToken, _ := newSignedTestIDToken(defaultIDToken)
460+ tc.RedeemJSON.IDToken = idToken
461+ redeemPath := "/token/"
462+ redeemJSONResp, err := json.Marshal(tc.RedeemJSON)
463+ assert.NoError(t, err)
464+
465+ serverURL, server := newCidaasServer(
466+ map[string][]byte{
467+ redeemPath: redeemJSONResp,
468+ })
469+ provider := newCidaasProvider(serverURL)
470+
471+ // Disable session enrichment, because we want to focus on refreshing logic
472+ provider.ProfileURL, err = url.Parse("")
473+ assert.NoError(t, err)
474+ provider.RedeemURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, redeemPath))
475+ assert.NoError(t, err)
476+
477+ provider.GroupsClaim = tc.GroupsClaim
478+ defer server.Close()
479+
480+ var refreshed bool
481+ refreshed, err = provider.RefreshSession(context.Background(), tc.ExistingSession)
482+
483+ if tc.ExpectedError != nil {
484+ assert.EqualError(t, err, tc.ExpectedError.Error())
485+ } else {
486+ assert.NoError(t, err)
487+ }
488+ assert.Equal(t, tc.ExpectedRefreshed, refreshed)
489+ assert.Equal(t, tc.ExpectedEmail, tc.ExistingSession.Email)
490+ assert.Equal(t, tc.ExpectedUser, tc.ExistingSession.User)
491+ })
492+ }
493+}
providers/providers.go+4−1
@@ -45,6 +45,8 @@ func NewProvider(providerConfig options.Provider) (Provider, error) {
4545 return NewMicrosoftEntraIDProvider(providerData, providerConfig), nil
4646 case options.BitbucketProvider:
4747 return NewBitbucketProvider(providerData, providerConfig.BitbucketConfig), nil
48+ case options.CidaasProvider:
49+ return NewCIDAASProvider(providerData, providerConfig), nil
4850 case options.DigitalOceanProvider:
4951 return NewDigitalOceanProvider(providerData), nil
5052 case options.FacebookProvider:
@@ -188,7 +190,8 @@ func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bo
188190 options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider,
189191 options.NextCloudProvider, options.SourceHutProvider:
190192 return false, nil
191− case options.ADFSProvider, options.AzureProvider, options.GitLabProvider, options.KeycloakOIDCProvider, options.OIDCProvider, options.MicrosoftEntraIDProvider:
193+ case options.OIDCProvider, options.ADFSProvider, options.AzureProvider, options.CidaasProvider,
194+ options.GitLabProvider, options.KeycloakOIDCProvider, options.MicrosoftEntraIDProvider:
192195 return true, nil
193196 default:
194197 return false, fmt.Errorf("unknown provider type: %s", providerType)