git.iko.soy/typeable/oauth2-proxy
on master help sign in
Use global OIDC fields for Gitlab
d2ffef2Nick Meves
pkg/validation/options.go−1
@@ -287,7 +287,6 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
287 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
287 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
288 }
288 }
289 case *providers.GitLabProvider:
289 case *providers.GitLabProvider:
290− p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
291 p.Groups = o.GitLabGroup
290 p.Groups = o.GitLabGroup
292 err := p.AddProjects(o.GitlabProjects)
291 err := p.AddProjects(o.GitlabProjects)
293 if err != nil {
292 if err != nil {
providers/gitlab.go+11−14
@@ -20,8 +20,6 @@ type GitLabProvider struct {
20
20
21 Groups []string
21 Groups []string
22 Projects []*GitlabProject
22 Projects []*GitlabProject
23−
24− AllowUnverifiedEmail bool
25}
23}
26
24
27// GitlabProject represents a Gitlab project constraint entity
25// GitlabProject represents a Gitlab project constraint entity
@@ -103,7 +101,7 @@ func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (
103 if err != nil {
101 if err != nil {
104 return nil, fmt.Errorf("token exchange: %v", err)
102 return nil, fmt.Errorf("token exchange: %v", err)
105 }
103 }
106− s, err = p.createSessionState(ctx, token)
104+ s, err = p.createSession(ctx, token)
107 if err != nil {
105 if err != nil {
108 return nil, fmt.Errorf("unable to update session: %v", err)
106 return nil, fmt.Errorf("unable to update session: %v", err)
109 }
107 }
@@ -162,7 +160,7 @@ func (p *GitLabProvider) redeemRefreshToken(ctx context.Context, s *sessions.Ses
162 if err != nil {
160 if err != nil {
163 return fmt.Errorf("failed to get token: %v", err)
161 return fmt.Errorf("failed to get token: %v", err)
164 }
162 }
165− newSession, err := p.createSessionState(ctx, token)
163+ newSession, err := p.createSession(ctx, token)
166 if err != nil {
164 if err != nil {
167 return fmt.Errorf("unable to update session: %v", err)
165 return fmt.Errorf("unable to update session: %v", err)
168 }
166 }
@@ -255,22 +253,21 @@ func (p *GitLabProvider) AddProjects(projects []string) error {
255 return nil
253 return nil
256}
254}
257
255
258−func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
256+func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
259− rawIDToken, ok := token.Extra("id_token").(string)
260− if !ok {
261− return nil, fmt.Errorf("token response did not contain an id_token")
262− }
263−
264− // Parse and verify ID Token payload.
265− idToken, err := p.Verifier.Verify(ctx, rawIDToken)
257+ idToken, err := p.verifyIDToken(ctx, token)
266 if err != nil {
258 if err != nil {
259+ switch err {
260+ case ErrMissingIDToken:
261+ return nil, fmt.Errorf("token response did not contain an id_token")
262+ default:
267− return nil, fmt.Errorf("could not verify id_token: %v", err)
263+ return nil, fmt.Errorf("could not verify id_token: %v", err)
264+ }
268 }
265 }
269
266
270 created := time.Now()
267 created := time.Now()
271 return &sessions.SessionState{
268 return &sessions.SessionState{
272 AccessToken: token.AccessToken,
269 AccessToken: token.AccessToken,
273− IDToken: rawIDToken,
270+ IDToken: getIDToken(token),
274 RefreshToken: token.RefreshToken,
271 RefreshToken: token.RefreshToken,
275 CreatedAt: &created,
272 CreatedAt: &created,
276 ExpiresOn: &idToken.Expiry,
273 ExpiresOn: &idToken.Expiry,
providers/oidc.go+15−14
@@ -49,7 +49,7 @@ func (p *OIDCProvider) Redeem(ctx context.Context, redirectURL, code string) (*s
49 return p.createSession(ctx, token, false)
49 return p.createSession(ctx, token, false)
50}
50}
51
51
52−// EnrichSessionState is called after Redeem to allow providers to enrich session fields
52+// EnrichSession is called after Redeem to allow providers to enrich session fields
53// such as User, Email, Groups with provider specific API calls.
53// such as User, Email, Groups with provider specific API calls.
54func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
54func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
55 if p.ProfileURL.String() == "" {
55 if p.ProfileURL.String() == "" {
@@ -61,7 +61,7 @@ func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionSta
61
61
62 // Try to get missing emails or groups from a profileURL
62 // Try to get missing emails or groups from a profileURL
63 if s.Email == "" || s.Groups == nil {
63 if s.Email == "" || s.Groups == nil {
64− err := p.callProfileURL(ctx, s)
64+ err := p.enrichFromProfileURL(ctx, s)
65 if err != nil {
65 if err != nil {
66 logger.Errorf("Warning: Profile URL request failed: %v", err)
66 logger.Errorf("Warning: Profile URL request failed: %v", err)
67 }
67 }
@@ -74,9 +74,9 @@ func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionSta
74 return nil
74 return nil
75}
75}
76
76
77−// callProfileURL enriches a session's Email & Groups via the JSON response of
77+// enrichFromProfileURL enriches a session's Email & Groups via the JSON response of
78// an OIDC profile URL
78// an OIDC profile URL
79−func (p *OIDCProvider) callProfileURL(ctx context.Context, s *sessions.SessionState) error {
79+func (p *OIDCProvider) enrichFromProfileURL(ctx context.Context, s *sessions.SessionState) error {
80 respJSON, err := requests.New(p.ProfileURL.String()).
80 respJSON, err := requests.New(p.ProfileURL.String()).
81 WithContext(ctx).
81 WithContext(ctx).
82 WithHeaders(makeOIDCHeader(s.AccessToken)).
82 WithHeaders(makeOIDCHeader(s.AccessToken)).
@@ -91,22 +91,23 @@ func (p *OIDCProvider) callProfileURL(ctx context.Context, s *sessions.SessionSt
91 s.Email = email
91 s.Email = email
92 }
92 }
93
93
94− if len(s.Groups) == 0 {
94+ if len(s.Groups) > 0 {
95+ return nil
96+ }
95− for _, group := range coerceArray(respJSON, p.GroupsClaim) {
97+ for _, group := range coerceArray(respJSON, p.GroupsClaim) {
96− formatted, err := formatGroup(group)
98+ formatted, err := formatGroup(group)
97− if err != nil {
99+ if err != nil {
98− logger.Errorf("Warning: unable to format group of type %s with error %s",
100+ logger.Errorf("Warning: unable to format group of type %s with error %s",
99− reflect.TypeOf(group), err)
101+ reflect.TypeOf(group), err)
100− continue
102+ continue
101− }
102− s.Groups = append(s.Groups, formatted)
103 }
103 }
104+ s.Groups = append(s.Groups, formatted)
104 }
105 }
105
106
106 return nil
107 return nil
107}
108}
108
109
109−// ValidateSessionState checks that the session's IDToken is still valid
110+// ValidateSession checks that the session's IDToken is still valid
110func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
111func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
111 _, err := p.Verifier.Verify(ctx, s.IDToken)
112 _, err := p.Verifier.Verify(ctx, s.IDToken)
112 return err == nil
113 return err == nil
providers/provider_data.go+6−6
@@ -128,13 +128,13 @@ type OIDCClaims struct {
128
128
129func (p *ProviderData) verifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
129func (p *ProviderData) verifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
130 rawIDToken := getIDToken(token)
130 rawIDToken := getIDToken(token)
131− if strings.TrimSpace(rawIDToken) != "" {
131+ if strings.TrimSpace(rawIDToken) == "" {
132+ return nil, ErrMissingIDToken
133+ }
132− if p.Verifier == nil {
134+ if p.Verifier == nil {
133− return nil, ErrMissingOIDCVerifier
135+ return nil, ErrMissingOIDCVerifier
134− }
135− return p.Verifier.Verify(ctx, rawIDToken)
136 }
136 }
137− return nil, ErrMissingIDToken
137+ return p.Verifier.Verify(ctx, rawIDToken)
138}
138}
139
139
140// buildSessionFromClaims uses IDToken claims to populate a fresh SessionState
140// buildSessionFromClaims uses IDToken claims to populate a fresh SessionState
providers/util.go+8−8
@@ -73,15 +73,15 @@ func getIDToken(token *oauth2.Token) string {
73// formatGroup coerces an OIDC groups claim into a string
73// formatGroup coerces an OIDC groups claim into a string
74// If it is non-string, marshal it into JSON.
74// If it is non-string, marshal it into JSON.
75func formatGroup(rawGroup interface{}) (string, error) {
75func formatGroup(rawGroup interface{}) (string, error) {
76− group, ok := rawGroup.(string)
76+ if group, ok := rawGroup.(string); ok {
77− if !ok {
77+ return group, nil
78+ }
79+
78− jsonGroup, err := json.Marshal(rawGroup)
80+ jsonGroup, err := json.Marshal(rawGroup)
79− if err != nil {
81+ if err != nil {
80− return "", err
82+ return "", err
81− }
82− group = string(jsonGroup)
83 }
83 }
84− return group, nil
84+ return string(jsonGroup), nil
85}
85}
86
86
87// coerceArray extracts a field from simplejson.Json that might be a
87// coerceArray extracts a field from simplejson.Json that might be a