12 oidc "github.com/coreos/go-oidc"
13 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
17// GitLabProvider represents a GitLab based Identity Provider
18type GitLabProvider struct {
24 Verifier *oidc.IDTokenVerifier
25 AllowUnverifiedEmail bool
28var _ Provider = (*GitLabProvider)(nil)
30// NewGitLabProvider initiates a new GitLabProvider
31func NewGitLabProvider(p *ProviderData) *GitLabProvider {
32 p.ProviderName = "GitLab"
35 p.Scope = "openid email"
38 return &GitLabProvider{ProviderData: p}
41// Redeem exchanges the OAuth2 authentication token for an ID token
42func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (s *sessions.SessionState, err error) {
43 clientSecret, err := p.GetClientSecret()
50 ClientSecret: clientSecret,
51 Endpoint: oauth2.Endpoint{
52 TokenURL: p.RedeemURL.String(),
54 RedirectURL: redirectURL,
56 token, err := c.Exchange(ctx, code)
58 return nil, fmt.Errorf("token exchange: %v", err)
60 s, err = p.createSessionState(ctx, token)
62 return nil, fmt.Errorf("unable to update session: %v", err)
67// RefreshSessionIfNeeded checks if the session has expired and uses the
68// RefreshToken to fetch a new ID token if required
69func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
70 if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
74 origExpiration := s.ExpiresOn
76 err := p.redeemRefreshToken(ctx, s)
78 return false, fmt.Errorf("unable to redeem refresh token: %v", err)
81 fmt.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
85func (p *GitLabProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) (err error) {
86 clientSecret, err := p.GetClientSecret()
93 ClientSecret: clientSecret,
94 Endpoint: oauth2.Endpoint{
95 TokenURL: p.RedeemURL.String(),
99 RefreshToken: s.RefreshToken,
100 Expiry: time.Now().Add(-time.Hour),
102 token, err := c.TokenSource(ctx, t).Token()
104 return fmt.Errorf("failed to get token: %v", err)
106 newSession, err := p.createSessionState(ctx, token)
108 return fmt.Errorf("unable to update session: %v", err)
110 s.AccessToken = newSession.AccessToken
111 s.IDToken = newSession.IDToken
112 s.RefreshToken = newSession.RefreshToken
113 s.CreatedAt = newSession.CreatedAt
114 s.ExpiresOn = newSession.ExpiresOn
115 s.Email = newSession.Email
119type gitlabUserInfo struct {
120 Username string `json:"nickname"`
121 Email string `json:"email"`
122 EmailVerified bool `json:"email_verified"`
123 Groups []string `json:"groups"`
126func (p *GitLabProvider) getUserInfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserInfo, error) {
127 // Retrieve user info JSON
128 // https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
130 // Build user info url from login url of GitLab instance
131 userInfoURL := *p.LoginURL
132 userInfoURL.Path = "/oauth/userinfo"
134 req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL.String(), nil)
136 return nil, fmt.Errorf("failed to create user info request: %v", err)
138 req.Header.Set("Authorization", "Bearer "+s.AccessToken)
140 resp, err := http.DefaultClient.Do(req)
142 return nil, fmt.Errorf("failed to perform user info request: %v", err)
145 body, err = ioutil.ReadAll(resp.Body)
148 return nil, fmt.Errorf("failed to read user info response: %v", err)
151 if resp.StatusCode != 200 {
152 return nil, fmt.Errorf("got %d during user info request: %s", resp.StatusCode, body)
155 var userInfo gitlabUserInfo
156 err = json.Unmarshal(body, &userInfo)
158 return nil, fmt.Errorf("failed to parse user info: %v", err)
161 return &userInfo, nil
164func (p *GitLabProvider) verifyGroupMembership(userInfo *gitlabUserInfo) error {
169 // Collect user group memberships
170 membershipSet := make(map[string]bool)
171 for _, group := range userInfo.Groups {
172 membershipSet[group] = true
175 // Find a valid group that they are a member of
176 validGroups := strings.Split(p.Group, " ")
177 for _, validGroup := range validGroups {
178 if _, ok := membershipSet[validGroup]; ok {
183 return fmt.Errorf("user is not a member of '%s'", p.Group)
186func (p *GitLabProvider) verifyEmailDomain(userInfo *gitlabUserInfo) error {
187 if len(p.EmailDomains) == 0 || p.EmailDomains[0] == "*" {
191 for _, domain := range p.EmailDomains {
192 if strings.HasSuffix(userInfo.Email, domain) {
197 return fmt.Errorf("user email is not one of the valid domains '%v'", p.EmailDomains)
200func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
201 rawIDToken, ok := token.Extra("id_token").(string)
203 return nil, fmt.Errorf("token response did not contain an id_token")
206 // Parse and verify ID Token payload.
207 idToken, err := p.Verifier.Verify(ctx, rawIDToken)
209 return nil, fmt.Errorf("could not verify id_token: %v", err)
212 return &sessions.SessionState{
213 AccessToken: token.AccessToken,
215 RefreshToken: token.RefreshToken,
216 CreatedAt: time.Now(),
217 ExpiresOn: idToken.Expiry,
221// ValidateSessionState checks that the session's IDToken is still valid
222func (p *GitLabProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool {
223 _, err := p.Verifier.Verify(ctx, s.IDToken)
227// GetEmailAddress returns the Account email address
228func (p *GitLabProvider) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) {
229 // Retrieve user info
230 userInfo, err := p.getUserInfo(ctx, s)
232 return "", fmt.Errorf("failed to retrieve user info: %v", err)
235 // Check if email is verified
236 if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
237 return "", fmt.Errorf("user email is not verified")
240 // Check if email has valid domain
241 err = p.verifyEmailDomain(userInfo)
243 return "", fmt.Errorf("email domain check failed: %v", err)
246 // Check group membership
247 err = p.verifyGroupMembership(userInfo)
249 return "", fmt.Errorf("group membership check failed: %v", err)
252 return userInfo.Email, nil
255// GetUserName returns the Account user name
256func (p *GitLabProvider) GetUserName(ctx context.Context, s *sessions.SessionState) (string, error) {
257 userInfo, err := p.getUserInfo(ctx, s)
259 return "", fmt.Errorf("failed to retrieve user info: %v", err)
262 return userInfo.Username, nil
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list