Use OIDC as base of Gitlab provider
This commit is contained in:
		
							parent
							
								
									05a4e77c4c
								
							
						
					
					
						commit
						3092941c57
					
				|  | @ -6,194 +6,196 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
| 	"golang.org/x/oauth2" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // GitLabProvider represents a GitLab based Identity Provider
 |  | ||||||
| type GitLabProvider struct { |  | ||||||
| 	*ProviderData |  | ||||||
| 
 |  | ||||||
| 	Groups   []string |  | ||||||
| 	Projects []*GitlabProject |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GitlabProject represents a Gitlab project constraint entity
 |  | ||||||
| type GitlabProject struct { |  | ||||||
| 	Name        string |  | ||||||
| 	AccessLevel int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // newGitlabProject Creates a new GitlabProject struct from project string formatted as namespace/project=accesslevel
 |  | ||||||
| // if no accesslevel provided, use the default one
 |  | ||||||
| func newGitlabproject(project string) (*GitlabProject, error) { |  | ||||||
| 	// default access level is 20
 |  | ||||||
| 	defaultAccessLevel := 20 |  | ||||||
| 	// see https://docs.gitlab.com/ee/api/members.html#valid-access-levels
 |  | ||||||
| 	validAccessLevel := [4]int{10, 20, 30, 40} |  | ||||||
| 
 |  | ||||||
| 	parts := strings.SplitN(project, "=", 2) |  | ||||||
| 
 |  | ||||||
| 	if len(parts) == 2 { |  | ||||||
| 		lvl, err := strconv.Atoi(parts[1]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, valid := range validAccessLevel { |  | ||||||
| 			if lvl == valid { |  | ||||||
| 				return &GitlabProject{ |  | ||||||
| 						Name:        parts[0], |  | ||||||
| 						AccessLevel: lvl}, |  | ||||||
| 					err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nil, fmt.Errorf("invalid gitlab project access level specified (%s)", parts[0]) |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &GitlabProject{ |  | ||||||
| 			Name:        project, |  | ||||||
| 			AccessLevel: defaultAccessLevel}, |  | ||||||
| 		nil |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var _ Provider = (*GitLabProvider)(nil) |  | ||||||
| 
 |  | ||||||
| const ( | const ( | ||||||
| 	gitlabProviderName = "GitLab" | 	gitlabProviderName = "GitLab" | ||||||
| 	gitlabDefaultScope = "openid email" | 	gitlabDefaultScope = "openid email" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // GitLabProvider represents a GitLab based Identity Provider
 | ||||||
|  | type GitLabProvider struct { | ||||||
|  | 	*OIDCProvider | ||||||
|  | 
 | ||||||
|  | 	allowedProjects []*gitlabProject | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ Provider = (*GitLabProvider)(nil) | ||||||
|  | 
 | ||||||
| // NewGitLabProvider initiates a new GitLabProvider
 | // NewGitLabProvider initiates a new GitLabProvider
 | ||||||
| func NewGitLabProvider(p *ProviderData) *GitLabProvider { | func NewGitLabProvider(p *ProviderData) *GitLabProvider { | ||||||
| 	p.ProviderName = gitlabProviderName | 	p.ProviderName = gitlabProviderName | ||||||
| 
 |  | ||||||
| 	if p.Scope == "" { | 	if p.Scope == "" { | ||||||
| 		p.Scope = gitlabDefaultScope | 		p.Scope = gitlabDefaultScope | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &GitLabProvider{ProviderData: p} | 	return &GitLabProvider{ | ||||||
| } | 		OIDCProvider: &OIDCProvider{ | ||||||
| 
 | 			ProviderData: p, | ||||||
| // Redeem exchanges the OAuth2 authentication token for an ID token
 | 			SkipNonce:    false, | ||||||
| func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (s *sessions.SessionState, err error) { |  | ||||||
| 	clientSecret, err := p.GetClientSecret() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	c := oauth2.Config{ |  | ||||||
| 		ClientID:     p.ClientID, |  | ||||||
| 		ClientSecret: clientSecret, |  | ||||||
| 		Endpoint: oauth2.Endpoint{ |  | ||||||
| 			TokenURL: p.RedeemURL.String(), |  | ||||||
| 		}, | 		}, | ||||||
| 		RedirectURL: redirectURL, |  | ||||||
| 	} | 	} | ||||||
| 	token, err := c.Exchange(ctx, code) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("token exchange: %v", err) |  | ||||||
| 	} |  | ||||||
| 	s, err = p.createSession(ctx, token) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("unable to update session: %v", err) |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetProjectScope ensure read_api is added to scope when filtering on projects
 | // SetAllowedProjects adds Gitlab projects to the AllowedGroups list
 | ||||||
| func (p *GitLabProvider) SetProjectScope() { | // and tracks them to do a project API lookup during `EnrichSession`.
 | ||||||
| 	if len(p.Projects) > 0 { | func (p *GitLabProvider) SetAllowedProjects(projects []string) error { | ||||||
|  | 	for _, project := range projects { | ||||||
|  | 		gp, err := newGitlabProject(project) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		p.allowedProjects = append(p.allowedProjects, gp) | ||||||
|  | 		p.AllowedGroups[formatProject(gp)] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 	if len(p.allowedProjects) > 0 { | ||||||
|  | 		p.setProjectScope() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // gitlabProject represents a Gitlab project constraint entity
 | ||||||
|  | type gitlabProject struct { | ||||||
|  | 	Name        string | ||||||
|  | 	AccessLevel int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newGitlabProject Creates a new GitlabProject struct from project string
 | ||||||
|  | // formatted as `namespace/project=accesslevel`
 | ||||||
|  | // if no accesslevel provided, use the default one
 | ||||||
|  | func newGitlabProject(project string) (*gitlabProject, error) { | ||||||
|  | 	const defaultAccessLevel = 20 | ||||||
|  | 	// see https://docs.gitlab.com/ee/api/members.html#valid-access-levels
 | ||||||
|  | 	validAccessLevel := [4]int{10, 20, 30, 40} | ||||||
|  | 
 | ||||||
|  | 	parts := strings.SplitN(project, "=", 2) | ||||||
|  | 	if len(parts) == 2 { | ||||||
|  | 		lvl, err := strconv.Atoi(parts[1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		for _, valid := range validAccessLevel { | ||||||
|  | 			if lvl == valid { | ||||||
|  | 				return &gitlabProject{ | ||||||
|  | 					Name:        parts[0], | ||||||
|  | 					AccessLevel: lvl, | ||||||
|  | 				}, nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil, fmt.Errorf("invalid gitlab project access level specified (%s)", parts[0]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &gitlabProject{ | ||||||
|  | 		Name:        project, | ||||||
|  | 		AccessLevel: defaultAccessLevel, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // setProjectScope ensures read_api is added to scope when filtering on projects
 | ||||||
|  | func (p *GitLabProvider) setProjectScope() { | ||||||
| 	for _, val := range strings.Split(p.Scope, " ") { | 	for _, val := range strings.Split(p.Scope, " ") { | ||||||
| 		if val == "read_api" { | 		if val == "read_api" { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 	} | 	} | ||||||
| 	p.Scope += " read_api" | 	p.Scope += " read_api" | ||||||
| } | } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
 | // EnrichSession enriches the session with the response from the userinfo API
 | ||||||
| func (p *GitLabProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) { | // endpoint & projects API endpoint for allowed projects.
 | ||||||
| 	if s == nil || s.RefreshToken == "" { | func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { | ||||||
| 		return false, nil | 	// Retrieve user info
 | ||||||
| 	} | 	userinfo, err := p.getUserinfo(ctx, s) | ||||||
| 
 |  | ||||||
| 	origExpiration := s.ExpiresOn |  | ||||||
| 
 |  | ||||||
| 	err := p.redeemRefreshToken(ctx, s) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, fmt.Errorf("unable to redeem refresh token: %v", err) | 		return fmt.Errorf("failed to retrieve user info: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	logger.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration) | 	// Check if email is verified
 | ||||||
| 	return true, nil | 	if !p.AllowUnverifiedEmail && !userinfo.EmailVerified { | ||||||
|  | 		return fmt.Errorf("user email is not verified") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| func (p *GitLabProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) error { | 	s.User = userinfo.Username | ||||||
| 	clientSecret, err := p.GetClientSecret() | 	s.Email = userinfo.Email | ||||||
| 	if err != nil { | 	s.Groups = append(s.Groups, userinfo.Groups...) | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	c := oauth2.Config{ | 	p.addProjectsToSession(ctx, s) | ||||||
| 		ClientID:     p.ClientID, |  | ||||||
| 		ClientSecret: clientSecret, |  | ||||||
| 		Endpoint: oauth2.Endpoint{ |  | ||||||
| 			TokenURL: p.RedeemURL.String(), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	t := &oauth2.Token{ |  | ||||||
| 		RefreshToken: s.RefreshToken, |  | ||||||
| 		Expiry:       time.Now().Add(-time.Hour), |  | ||||||
| 	} |  | ||||||
| 	token, err := c.TokenSource(ctx, t).Token() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("failed to get token: %v", err) |  | ||||||
| 	} |  | ||||||
| 	newSession, err := p.createSession(ctx, token) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("unable to update session: %v", err) |  | ||||||
| 	} |  | ||||||
| 	*s = *newSession |  | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type gitlabUserInfo struct { | type gitlabUserinfo struct { | ||||||
| 	Username      string   `json:"nickname"` | 	Username      string   `json:"nickname"` | ||||||
| 	Email         string   `json:"email"` | 	Email         string   `json:"email"` | ||||||
| 	EmailVerified bool     `json:"email_verified"` | 	EmailVerified bool     `json:"email_verified"` | ||||||
| 	Groups        []string `json:"groups"` | 	Groups        []string `json:"groups"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *GitLabProvider) getUserInfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserInfo, error) { | func (p *GitLabProvider) getUserinfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserinfo, error) { | ||||||
| 	// Retrieve user info JSON
 | 	// Retrieve user info JSON
 | ||||||
| 	// https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
 | 	// https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
 | ||||||
| 
 | 
 | ||||||
| 	// Build user info url from login url of GitLab instance
 | 	// Build user info url from login url of GitLab instance
 | ||||||
| 	userInfoURL := *p.LoginURL | 	userinfoURL := *p.LoginURL | ||||||
| 	userInfoURL.Path = "/oauth/userinfo" | 	userinfoURL.Path = "/oauth/userinfo" | ||||||
| 
 | 
 | ||||||
| 	var userInfo gitlabUserInfo | 	var userinfo gitlabUserinfo | ||||||
| 	err := requests.New(userInfoURL.String()). | 	err := requests.New(userinfoURL.String()). | ||||||
| 		WithContext(ctx). | 		WithContext(ctx). | ||||||
| 		SetHeader("Authorization", "Bearer "+s.AccessToken). | 		SetHeader("Authorization", "Bearer "+s.AccessToken). | ||||||
| 		Do(). | 		Do(). | ||||||
| 		UnmarshalInto(&userInfo) | 		UnmarshalInto(&userinfo) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("error getting user info: %v", err) | 		return nil, fmt.Errorf("error getting user info: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &userInfo, nil | 	return &userinfo, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // addProjectsToSession adds projects matching user access requirements into
 | ||||||
|  | // the session state groups list.
 | ||||||
|  | // This method prefixes projects names with `project:` to specify group kind.
 | ||||||
|  | func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.SessionState) { | ||||||
|  | 	// Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
 | ||||||
|  | 	for _, project := range p.allowedProjects { | ||||||
|  | 		projectInfo, err := p.getProjectInfo(ctx, s, project.Name) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logger.Errorf("Warning: project info request failed: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if projectInfo.Archived { | ||||||
|  | 			logger.Errorf("Warning: project %s is archived", project.Name) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		perms := projectInfo.Permissions.ProjectAccess | ||||||
|  | 		if perms == nil { | ||||||
|  | 			// use group project access as fallback
 | ||||||
|  | 			perms = projectInfo.Permissions.GroupAccess | ||||||
|  | 			// group project access is not set for this user then we give up
 | ||||||
|  | 			if perms == nil { | ||||||
|  | 				logger.Errorf("Warning: user %q has no project level access to %s", | ||||||
|  | 					s.Email, project.Name) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if perms.AccessLevel < project.AccessLevel { | ||||||
|  | 			logger.Errorf( | ||||||
|  | 				"Warning: user %q does not have the minimum required access level for project %q", | ||||||
|  | 				s.Email, | ||||||
|  | 				project.Name, | ||||||
|  | 			) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s.Groups = append(s.Groups, formatProject(project)) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type gitlabPermissionAccess struct { | type gitlabPermissionAccess struct { | ||||||
|  | @ -226,7 +228,6 @@ func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.Session | ||||||
| 		SetHeader("Authorization", "Bearer "+s.AccessToken). | 		SetHeader("Authorization", "Bearer "+s.AccessToken). | ||||||
| 		Do(). | 		Do(). | ||||||
| 		UnmarshalInto(&projectInfo) | 		UnmarshalInto(&projectInfo) | ||||||
| 
 |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to get project info: %v", err) | 		return nil, fmt.Errorf("failed to get project info: %v", err) | ||||||
| 	} | 	} | ||||||
|  | @ -234,116 +235,6 @@ func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.Session | ||||||
| 	return &projectInfo, nil | 	return &projectInfo, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AddProjects adds Gitlab projects from options to GitlabProvider struct
 | func formatProject(project *gitlabProject) string { | ||||||
| func (p *GitLabProvider) AddProjects(projects []string) error { | 	return fmt.Sprintf("project:%s", project.Name) | ||||||
| 	for _, project := range projects { |  | ||||||
| 		gp, err := newGitlabproject(project) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		p.Projects = append(p.Projects, gp) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) { |  | ||||||
| 	idToken, err := p.verifyIDToken(ctx, token) |  | ||||||
| 	if err != nil { |  | ||||||
| 		switch err { |  | ||||||
| 		case ErrMissingIDToken: |  | ||||||
| 			return nil, fmt.Errorf("token response did not contain an id_token") |  | ||||||
| 		default: |  | ||||||
| 			return nil, fmt.Errorf("could not verify id_token: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ss := &sessions.SessionState{ |  | ||||||
| 		AccessToken:  token.AccessToken, |  | ||||||
| 		IDToken:      getIDToken(token), |  | ||||||
| 		RefreshToken: token.RefreshToken, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ss.CreatedAtNow() |  | ||||||
| 	ss.SetExpiresOn(idToken.Expiry) |  | ||||||
| 
 |  | ||||||
| 	return ss, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ValidateSession checks that the session's IDToken is still valid
 |  | ||||||
| func (p *GitLabProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { |  | ||||||
| 	_, err := p.Verifier.Verify(ctx, s.IDToken) |  | ||||||
| 	return err == nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // EnrichSession adds values and data from the Gitlab endpoint to current session
 |  | ||||||
| func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { |  | ||||||
| 	// Retrieve user info
 |  | ||||||
| 	userInfo, err := p.getUserInfo(ctx, s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("failed to retrieve user info: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if email is verified
 |  | ||||||
| 	if !p.AllowUnverifiedEmail && !userInfo.EmailVerified { |  | ||||||
| 		return fmt.Errorf("user email is not verified") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	s.User = userInfo.Username |  | ||||||
| 	s.Email = userInfo.Email |  | ||||||
| 	for _, group := range userInfo.Groups { |  | ||||||
| 		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	p.addProjectsToSession(ctx, s) |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // addProjectsToSession adds projects matching user access requirements into the session state groups list
 |  | ||||||
| // This method prefix projects names with `project` to specify group kind
 |  | ||||||
| func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.SessionState) { |  | ||||||
| 	// Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
 |  | ||||||
| 	for _, project := range p.Projects { |  | ||||||
| 		projectInfo, err := p.getProjectInfo(ctx, s, project.Name) |  | ||||||
| 
 |  | ||||||
| 		if err != nil { |  | ||||||
| 			logger.Errorf("Warning: project info request failed: %v", err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if !projectInfo.Archived { |  | ||||||
| 			perms := projectInfo.Permissions.ProjectAccess |  | ||||||
| 			if perms == nil { |  | ||||||
| 				// use group project access as fallback
 |  | ||||||
| 				perms = projectInfo.Permissions.GroupAccess |  | ||||||
| 				// group project access is not set for this user then we give up
 |  | ||||||
| 				if perms == nil { |  | ||||||
| 					logger.Errorf("Warning: user %q has no project level access to %s", s.Email, project.Name) |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if perms != nil && perms.AccessLevel >= project.AccessLevel { |  | ||||||
| 				s.Groups = append(s.Groups, fmt.Sprintf("project:%s", project.Name)) |  | ||||||
| 			} else { |  | ||||||
| 				logger.Errorf("Warning: user %q does not have the minimum required access level for project %q", s.Email, project.Name) |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		logger.Errorf("Warning: project %s is archived", project.Name) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PrefixAllowedGroups returns a list of allowed groups, prefixed by their `kind` value
 |  | ||||||
| func (p *GitLabProvider) PrefixAllowedGroups() (groups []string) { |  | ||||||
| 	for _, val := range p.Groups { |  | ||||||
| 		groups = append(groups, fmt.Sprintf("group:%s", val)) |  | ||||||
| 	} |  | ||||||
| 	for _, val := range p.Projects { |  | ||||||
| 		groups = append(groups, fmt.Sprintf("project:%s", val.Name)) |  | ||||||
| 	} |  | ||||||
| 	return groups |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -188,7 +188,7 @@ var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 				err := p.EnrichSession(context.Background(), session) | 				err := p.EnrichSession(context.Background(), session) | ||||||
| 
 | 
 | ||||||
| 				if in.expectedError != nil { | 				if in.expectedError != nil { | ||||||
| 					Expect(err).To(MatchError(err)) | 					Expect(err).To(MatchError(in.expectedError)) | ||||||
| 				} else { | 				} else { | ||||||
| 					Expect(err).To(BeNil()) | 					Expect(err).To(BeNil()) | ||||||
| 					Expect(session.Email).To(Equal(in.expectedValue)) | 					Expect(session.Email).To(Equal(in.expectedValue)) | ||||||
|  | @ -208,97 +208,102 @@ var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 
 | 
 | ||||||
| 	Context("when filtering on gitlab entities (groups and projects)", func() { | 	Context("when filtering on gitlab entities (groups and projects)", func() { | ||||||
| 		type entitiesTableInput struct { | 		type entitiesTableInput struct { | ||||||
| 			expectedValue []string | 			allowedProjects []string | ||||||
| 			projects      []string | 			allowedGroups   []string | ||||||
| 			groups        []string | 			scope           string | ||||||
|  | 			expectedAuthz   bool | ||||||
|  | 			expectedError   error | ||||||
|  | 			expectedGroups  []string | ||||||
|  | 			expectedScope   string | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		DescribeTable("should return expected results", | 		DescribeTable("should return expected results", | ||||||
| 			func(in entitiesTableInput) { | 			func(in entitiesTableInput) { | ||||||
| 				p.AllowUnverifiedEmail = true | 				p.AllowUnverifiedEmail = true | ||||||
|  | 				if in.scope != "" { | ||||||
|  | 					p.Scope = in.scope | ||||||
|  | 				} | ||||||
| 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | ||||||
| 
 | 
 | ||||||
| 				err := p.AddProjects(in.projects) | 				p.SetAllowedGroups(in.allowedGroups) | ||||||
| 				Expect(err).To(BeNil()) |  | ||||||
| 				p.SetProjectScope() |  | ||||||
| 
 | 
 | ||||||
| 				if len(in.groups) > 0 { | 				err := p.SetAllowedProjects(in.allowedProjects) | ||||||
| 					p.Groups = in.groups | 				if err == nil { | ||||||
|  | 					Expect(err).To(BeNil()) | ||||||
|  | 				} else { | ||||||
|  | 					Expect(err).To(MatchError(in.expectedError)) | ||||||
|  | 					return | ||||||
| 				} | 				} | ||||||
|  | 				Expect(p.Scope).To(Equal(in.expectedScope)) | ||||||
| 
 | 
 | ||||||
| 				err = p.EnrichSession(context.Background(), session) | 				err = p.EnrichSession(context.Background(), session) | ||||||
| 
 |  | ||||||
| 				Expect(err).To(BeNil()) | 				Expect(err).To(BeNil()) | ||||||
| 				Expect(session.Groups).To(Equal(in.expectedValue)) | 				Expect(session.Groups).To(Equal(in.expectedGroups)) | ||||||
|  | 
 | ||||||
|  | 				authorized, err := p.Authorize(context.Background(), session) | ||||||
|  | 				Expect(err).To(BeNil()) | ||||||
|  | 				Expect(authorized).To(Equal(in.expectedAuthz)) | ||||||
| 			}, | 			}, | ||||||
| 			Entry("project membership valid on group project", entitiesTableInput{ | 			Entry("project membership valid on group project", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar", "project:my_group/my_project"}, | 				allowedProjects: []string{"my_group/my_project"}, | ||||||
| 				projects:      []string{"my_group/my_project"}, | 				expectedAuthz:   true, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar", "project:my_group/my_project"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("project membership invalid on group project, insufficient access level level", entitiesTableInput{ | 			Entry("project membership invalid on group project, insufficient access level level", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedProjects: []string{"my_group/my_project=40"}, | ||||||
| 				projects:      []string{"my_group/my_project=40"}, | 				expectedAuthz:   false, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("project membership invalid on group project, no access at all", entitiesTableInput{ | 			Entry("project membership invalid on group project, no access at all", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedProjects: []string{"no_access_group/no_access_project=30"}, | ||||||
| 				projects:      []string{"no_access_group/no_access_project=30"}, | 				expectedAuthz:   false, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("project membership valid on personnal project", entitiesTableInput{ | 			Entry("project membership valid on personnal project", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar", "project:my_profile/my_personal_project"}, | 				allowedProjects: []string{"my_profile/my_personal_project"}, | ||||||
| 				projects:      []string{"my_profile/my_personal_project"}, | 				scope:           "openid email read_api profile", | ||||||
|  | 				expectedAuthz:   true, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar", "project:my_profile/my_personal_project"}, | ||||||
|  | 				expectedScope:   "openid email read_api profile", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("project membership invalid on personnal project, insufficient access level", entitiesTableInput{ | 			Entry("project membership invalid on personnal project, insufficient access level", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedProjects: []string{"my_profile/my_personal_project=40"}, | ||||||
| 				projects:      []string{"my_profile/my_personal_project=40"}, | 				expectedAuthz:   false, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("project membership invalid", entitiesTableInput{ | 			Entry("project membership invalid", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedProjects: []string{"my_group/my_bad_project"}, | ||||||
| 				projects:      []string{"my_group/my_bad_project"}, | 				expectedAuthz:   false, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("group membership valid", entitiesTableInput{ | 			Entry("group membership valid", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedGroups:  []string{"foo"}, | ||||||
| 				groups:        []string{"foo"}, | 				expectedGroups: []string{"foo", "bar"}, | ||||||
|  | 				expectedAuthz:  true, | ||||||
|  | 				expectedScope:  "openid email", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("groups and projects", entitiesTableInput{ | 			Entry("groups and projects", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar", "project:my_group/my_project", "project:my_profile/my_personal_project"}, | 				allowedGroups:   []string{"foo", "baz"}, | ||||||
| 				groups:        []string{"foo", "baz"}, | 				allowedProjects: []string{"my_group/my_project", "my_profile/my_personal_project"}, | ||||||
| 				projects:      []string{"my_group/my_project", "my_profile/my_personal_project"}, | 				expectedAuthz:   true, | ||||||
|  | 				expectedGroups:  []string{"foo", "bar", "project:my_group/my_project", "project:my_profile/my_personal_project"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 			Entry("archived projects", entitiesTableInput{ | 			Entry("archived projects", entitiesTableInput{ | ||||||
| 				expectedValue: []string{"group:foo", "group:bar"}, | 				allowedProjects: []string{"my_group/my_archived_project"}, | ||||||
| 				groups:        []string{}, | 				expectedAuthz:   false, | ||||||
| 				projects:      []string{"my_group/my_archived_project"}, | 				expectedGroups:  []string{"foo", "bar"}, | ||||||
|  | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 		) | 			Entry("invalid project format", entitiesTableInput{ | ||||||
| 
 | 				allowedProjects: []string{"my_group/my_invalid_project=123"}, | ||||||
| 	}) | 				expectedError:   errors.New("invalid gitlab project access level specified (my_group/my_invalid_project)"), | ||||||
| 
 | 				expectedScope:   "openid email read_api", | ||||||
| 	Context("when generating group list from multiple kind", func() { |  | ||||||
| 		type entitiesTableInput struct { |  | ||||||
| 			projects []string |  | ||||||
| 			groups   []string |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		DescribeTable("should prefix entities with group kind", func(in entitiesTableInput) { |  | ||||||
| 			p.Groups = in.groups |  | ||||||
| 			err := p.AddProjects(in.projects) |  | ||||||
| 			Expect(err).To(BeNil()) |  | ||||||
| 
 |  | ||||||
| 			all := p.PrefixAllowedGroups() |  | ||||||
| 
 |  | ||||||
| 			Expect(len(all)).To(Equal(len(in.projects) + len(in.groups))) |  | ||||||
| 		}, |  | ||||||
| 			Entry("simple test case", entitiesTableInput{ |  | ||||||
| 				projects: []string{"my_group/my_project", "my_group/my_other_project"}, |  | ||||||
| 				groups:   []string{"mygroup", "myothergroup"}, |  | ||||||
| 			}), |  | ||||||
| 			Entry("projects only", entitiesTableInput{ |  | ||||||
| 				projects: []string{"my_group/my_project", "my_group/my_other_project"}, |  | ||||||
| 				groups:   []string{}, |  | ||||||
| 			}), |  | ||||||
| 			Entry("groups only", entitiesTableInput{ |  | ||||||
| 				projects: []string{}, |  | ||||||
| 				groups:   []string{"mygroup", "myothergroup"}, |  | ||||||
| 			}), | 			}), | ||||||
| 		) | 		) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue