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"
17// GitLabProvider represents a GitLab based Identity Provider
18type GitLabProvider struct {
22 Projects []*GitlabProject
25// GitlabProject represents a Gitlab project constraint entity
26type GitlabProject struct {
31// newGitlabProject Creates a new GitlabProject struct from project string formatted as namespace/project=accesslevel
32// if no accesslevel provided, use the default one
33func newGitlabproject(project string) (*GitlabProject, error) {
34 // default access level is 20
35 defaultAccessLevel := 20
36 // see https://docs.gitlab.com/ee/api/members.html#valid-access-levels
37 validAccessLevel := [4]int{10, 20, 30, 40}
39 parts := strings.SplitN(project, "=", 2)
42 lvl, err := strconv.Atoi(parts[1])
47 for _, valid := range validAccessLevel {
49 return &GitlabProject{
56 return nil, fmt.Errorf("invalid gitlab project access level specified (%s)", parts[0])
60 return &GitlabProject{
62 AccessLevel: defaultAccessLevel},
67var _ Provider = (*GitLabProvider)(nil)
70 gitlabProviderName = "GitLab"
71 gitlabDefaultScope = "openid email"
74// NewGitLabProvider initiates a new GitLabProvider
75func NewGitLabProvider(p *ProviderData) *GitLabProvider {
76 p.ProviderName = gitlabProviderName
79 p.Scope = gitlabDefaultScope
82 return &GitLabProvider{ProviderData: p}
85// Redeem exchanges the OAuth2 authentication token for an ID token
86func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (s *sessions.SessionState, err error) {
87 clientSecret, err := p.GetClientSecret()
94 ClientSecret: clientSecret,
95 Endpoint: oauth2.Endpoint{
96 TokenURL: p.RedeemURL.String(),
98 RedirectURL: redirectURL,
100 token, err := c.Exchange(ctx, code)
102 return nil, fmt.Errorf("token exchange: %v", err)
104 s, err = p.createSession(ctx, token)
106 return nil, fmt.Errorf("unable to update session: %v", err)
111// SetProjectScope ensure read_api is added to scope when filtering on projects
112func (p *GitLabProvider) SetProjectScope() {
113 if len(p.Projects) > 0 {
114 for _, val := range strings.Split(p.Scope, " ") {
115 if val == "read_api" {
120 p.Scope += " read_api"
124// RefreshSessionIfNeeded checks if the session has expired and uses the
125// RefreshToken to fetch a new ID token if required
126func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
127 if s == nil || (s.ExpiresOn != nil && s.ExpiresOn.After(time.Now())) || s.RefreshToken == "" {
131 origExpiration := s.ExpiresOn
133 err := p.redeemRefreshToken(ctx, s)
135 return false, fmt.Errorf("unable to redeem refresh token: %v", err)
138 logger.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
142func (p *GitLabProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) (err error) {
143 clientSecret, err := p.GetClientSecret()
149 ClientID: p.ClientID,
150 ClientSecret: clientSecret,
151 Endpoint: oauth2.Endpoint{
152 TokenURL: p.RedeemURL.String(),
156 RefreshToken: s.RefreshToken,
157 Expiry: time.Now().Add(-time.Hour),
159 token, err := c.TokenSource(ctx, t).Token()
161 return fmt.Errorf("failed to get token: %v", err)
163 newSession, err := p.createSession(ctx, token)
165 return fmt.Errorf("unable to update session: %v", err)
167 s.AccessToken = newSession.AccessToken
168 s.IDToken = newSession.IDToken
169 s.RefreshToken = newSession.RefreshToken
170 s.CreatedAt = newSession.CreatedAt
171 s.ExpiresOn = newSession.ExpiresOn
172 s.Email = newSession.Email
176type gitlabUserInfo struct {
177 Username string `json:"nickname"`
178 Email string `json:"email"`
179 EmailVerified bool `json:"email_verified"`
180 Groups []string `json:"groups"`
183func (p *GitLabProvider) getUserInfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserInfo, error) {
184 // Retrieve user info JSON
185 // https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
187 // Build user info url from login url of GitLab instance
188 userInfoURL := *p.LoginURL
189 userInfoURL.Path = "/oauth/userinfo"
191 var userInfo gitlabUserInfo
192 err := requests.New(userInfoURL.String()).
194 SetHeader("Authorization", "Bearer "+s.AccessToken).
196 UnmarshalInto(&userInfo)
198 return nil, fmt.Errorf("error getting user info: %v", err)
201 return &userInfo, nil
204type gitlabPermissionAccess struct {
205 AccessLevel int `json:"access_level"`
208type gitlabProjectPermission struct {
209 ProjectAccess *gitlabPermissionAccess `json:"project_access"`
210 GroupAccess *gitlabPermissionAccess `json:"group_access"`
213type gitlabProjectInfo struct {
214 Name string `json:"name"`
215 Archived bool `json:"archived"`
216 PathWithNamespace string `json:"path_with_namespace"`
217 Permissions gitlabProjectPermission `json:"permissions"`
220func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.SessionState, project string) (*gitlabProjectInfo, error) {
221 var projectInfo gitlabProjectInfo
223 endpointURL := &url.URL{
224 Scheme: p.LoginURL.Scheme,
225 Host: p.LoginURL.Host,
226 Path: "/api/v4/projects/",
229 err := requests.New(fmt.Sprintf("%s%s", endpointURL.String(), url.QueryEscape(project))).
231 SetHeader("Authorization", "Bearer "+s.AccessToken).
233 UnmarshalInto(&projectInfo)
236 return nil, fmt.Errorf("failed to get project info: %v", err)
239 return &projectInfo, nil
242// AddProjects adds Gitlab projects from options to GitlabProvider struct
243func (p *GitLabProvider) AddProjects(projects []string) error {
244 for _, project := range projects {
245 gp, err := newGitlabproject(project)
250 p.Projects = append(p.Projects, gp)
256func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
257 idToken, err := p.verifyIDToken(ctx, token)
260 case ErrMissingIDToken:
261 return nil, fmt.Errorf("token response did not contain an id_token")
263 return nil, fmt.Errorf("could not verify id_token: %v", err)
267 created := time.Now()
268 return &sessions.SessionState{
269 AccessToken: token.AccessToken,
270 IDToken: getIDToken(token),
271 RefreshToken: token.RefreshToken,
273 ExpiresOn: &idToken.Expiry,
277// ValidateSession checks that the session's IDToken is still valid
278func (p *GitLabProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
279 _, err := p.Verifier.Verify(ctx, s.IDToken)
283// EnrichSession adds values and data from the Gitlab endpoint to current session
284func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
285 // Retrieve user info
286 userInfo, err := p.getUserInfo(ctx, s)
288 return fmt.Errorf("failed to retrieve user info: %v", err)
291 // Check if email is verified
292 if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
293 return fmt.Errorf("user email is not verified")
296 s.User = userInfo.Username
297 s.Email = userInfo.Email
298 for _, group := range userInfo.Groups {
299 s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
302 p.addProjectsToSession(ctx, s)
307// addProjectsToSession adds projects matching user access requirements into the session state groups list
308// This method prefix projects names with `project` to specify group kind
309func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.SessionState) {
310 // Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
311 for _, project := range p.Projects {
312 projectInfo, err := p.getProjectInfo(ctx, s, project.Name)
315 logger.Errorf("Warning: project info request failed: %v", err)
319 if !projectInfo.Archived {
320 perms := projectInfo.Permissions.ProjectAccess
322 // use group project access as fallback
323 perms = projectInfo.Permissions.GroupAccess
324 // group project access is not set for this user then we give up
326 logger.Errorf("Warning: user %q has no project level access to %s", s.Email, project.Name)
331 if perms != nil && perms.AccessLevel >= project.AccessLevel {
332 s.Groups = append(s.Groups, fmt.Sprintf("project:%s", project.Name))
334 logger.Errorf("Warning: user %q does not have the minimum required access level for project %q", s.Email, project.Name)
339 logger.Errorf("Warning: project %s is archived", project.Name)
343// PrefixAllowedGroups returns a list of allowed groups, prefixed by their `kind` value
344func (p *GitLabProvider) PrefixAllowedGroups() (groups []string) {
345 for _, val := range p.Groups {
346 groups = append(groups, fmt.Sprintf("group:%s", val))
348 for _, val := range p.Projects {
349 groups = append(groups, fmt.Sprintf("project:%s", val.Name))
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