git.iko.soy/typeable/oauth2-proxy
on master help sign in
Preserve projects after `RefreshSession`

RefreshSession will override session.Groups with the new `groups` claims. We need to preserve all `project:` prefixed groups and reattach them post refresh.

commit95f9de5979e897fe7a61dc439245fade44f20ed9 authorNick Meves <nick.meves@greenhouse.io> dateSat 19 Jun 2021, 15:18 (5 years ago) parent11c2177
2 files changed+101−8
providers/gitlab.go+50−8 providers/gitlab_test.go+51
providers/gitlab.go+50−8
@@ -13,9 +13,10 @@ import (
1313)
1414
1515const (
16− gitlabProviderName = "GitLab"
17− gitlabDefaultScope = "openid email"
18− gitlabUserClaim = "nickname"
16+ gitlabProviderName = "GitLab"
17+ gitlabDefaultScope = "openid email"
18+ gitlabUserClaim = "nickname"
19+ gitlabProjectPrefix = "project:"
1920)
2021
2122// GitLabProvider represents a GitLab based Identity Provider
@@ -23,6 +24,8 @@ type GitLabProvider struct {
2324 *OIDCProvider
2425
2526 allowedProjects []*gitlabProject
27+ // Expose this for unit testing
28+ oidcRefreshFunc func(context.Context, *sessions.SessionState) (bool, error)
2629}
2730
2831var _ Provider = (*GitLabProvider)(nil)
@@ -35,11 +38,14 @@ func NewGitLabProvider(p *ProviderData) *GitLabProvider {
3538 p.Scope = gitlabDefaultScope
3639 }
3740
41+ oidcProvider := &OIDCProvider{
42+ ProviderData: p,
43+ SkipNonce: false,
44+ }
45+
3846 return &GitLabProvider{
39− OIDCProvider: &OIDCProvider{
40− ProviderData: p,
41− SkipNonce: false,
42− },
47+ OIDCProvider: oidcProvider,
48+ oidcRefreshFunc: oidcProvider.RefreshSession,
4349 }
4450}
4551
@@ -245,5 +251,41 @@ func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.Session
245251}
246252
247253func formatProject(project *gitlabProject) string {
248− return fmt.Sprintf("project:%s", project.Name)
254+ return gitlabProjectPrefix + project.Name
255+}
256+
257+// RefreshSession refreshes the session with the OIDCProvider implementation
258+// but preserves the custom GitLab projects added in the `EnrichSession` stage.
259+func (p *GitLabProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
260+ projects := getSessionProjects(s)
261+ // This will overwrite s.Groups with the new IDToken's `groups` claims
262+ refreshed, err := p.oidcRefreshFunc(ctx, s)
263+ if refreshed && err == nil {
264+ s.Groups = append(s.Groups, projects...)
265+ s.Groups = deduplicateGroups(s.Groups)
266+ }
267+ return refreshed, err
268+}
269+
270+func getSessionProjects(s *sessions.SessionState) []string {
271+ var projects []string
272+ for _, group := range s.Groups {
273+ if strings.HasPrefix(group, gitlabProjectPrefix) {
274+ projects = append(projects, group)
275+ }
276+ }
277+ return projects
278+}
279+
280+func deduplicateGroups(groups []string) []string {
281+ groupSet := make(map[string]struct{})
282+ for _, group := range groups {
283+ groupSet[group] = struct{}{}
284+ }
285+
286+ uniqueGroups := make([]string, 0, len(groupSet))
287+ for group := range groupSet {
288+ uniqueGroups = append(uniqueGroups, group)
289+ }
290+ return uniqueGroups
249291}
providers/gitlab_test.go+51
@@ -307,4 +307,55 @@ var _ = Describe("Gitlab Provider Tests", func() {
307307 }),
308308 )
309309 })
310+
311+ Context("when refreshing", func() {
312+ It("keeps existing projects after refreshing groups", func() {
313+ session := &sessions.SessionState{}
314+ session.Groups = []string{"foo", "bar", "project:thing", "project:sample"}
315+
316+ p.oidcRefreshFunc = func(_ context.Context, s *sessions.SessionState) (bool, error) {
317+ s.Groups = []string{"baz"}
318+ return true, nil
319+ }
320+
321+ refreshed, err := p.RefreshSession(context.Background(), session)
322+ Expect(refreshed).To(BeTrue())
323+ Expect(err).ToNot(HaveOccurred())
324+ Expect(len(session.Groups)).To(Equal(3))
325+ Expect(session.Groups).
326+ To(ContainElements([]string{"baz", "project:thing", "project:sample"}))
327+ })
328+
329+ It("leaves existing groups when not refreshed", func() {
330+ session := &sessions.SessionState{}
331+ session.Groups = []string{"foo", "bar", "project:thing", "project:sample"}
332+
333+ p.oidcRefreshFunc = func(_ context.Context, s *sessions.SessionState) (bool, error) {
334+ return false, nil
335+ }
336+
337+ refreshed, err := p.RefreshSession(context.Background(), session)
338+ Expect(refreshed).To(BeFalse())
339+ Expect(err).ToNot(HaveOccurred())
340+ Expect(len(session.Groups)).To(Equal(4))
341+ Expect(session.Groups).
342+ To(ContainElements([]string{"foo", "bar", "project:thing", "project:sample"}))
343+ })
344+
345+ It("leaves existing groups when OIDC refresh errors", func() {
346+ session := &sessions.SessionState{}
347+ session.Groups = []string{"foo", "bar", "project:thing", "project:sample"}
348+
349+ p.oidcRefreshFunc = func(_ context.Context, s *sessions.SessionState) (bool, error) {
350+ return false, errors.New("failure")
351+ }
352+
353+ refreshed, err := p.RefreshSession(context.Background(), session)
354+ Expect(refreshed).To(BeFalse())
355+ Expect(err).To(HaveOccurred())
356+ Expect(len(session.Groups)).To(Equal(4))
357+ Expect(session.Groups).
358+ To(ContainElements([]string{"foo", "bar", "project:thing", "project:sample"}))
359+ })
360+ })
310361})