Add authorization support for Gitlab projects (#630)
* Add support for gitlab projets * Add group membership in state * Use prefixed allowed groups everywhere * Fix: remove unused function * Fix: rename func that add data to session * Simplify projects and groups session funcs * Add project access level for gitlab projects * Fix: default access level * Add per project access level * Add user email when missing access level * Fix: harmonize errors * Update docs and flags description for gitlab project * Add test with both projects and groups * Fix: log error message Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Fix: make doc a markdown link * Add notes about read_api scope for projects * Fix: Verifier override in Gitlab Provider This commit fixes a bug caused by an override of the Verifier value from *ProviderData inside GitlabProvider struct * Fix: ensure data in session before using it * Update providers/gitlab.go Co-authored-by: Nick Meves <nick.meves@greenhouse.io> * Rename gitlab project initializer * Improve return value readbility * Use splitN * Handle space delimiters in set project scope * Reword comment for AddProjects * Fix: typo * Rework error handling in addProjectsToSession * Reduce branching complexity in addProjectsToSession * Fix: line returns * Better comment for addProjectsToSession * Fix: enrich session comment * Fix: email domains is handled before provider mechanism * Add archived project unit test * Fix: emails handling in gitlab provider Co-authored-by: Wilfried OLLIVIER <wollivier@bearstech.com> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> Co-authored-by: Nick Meves <nick.meves@greenhouse.io>
This commit is contained in:
		
							parent
							
								
									5117f2314f
								
							
						
					
					
						commit
						d67d6e3152
					
				
							
								
								
									
										20
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										20
									
								
								CHANGELOG.md
								
								
								
								
							|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| ## Important Notes | ## Important Notes | ||||||
| 
 | 
 | ||||||
|  | - [#630](https://github.com/oauth2-proxy/oauth2-proxy/pull/630) Gitlab projects needs a Gitlab application with the extra `read_api` enabled | ||||||
| - [#905](https://github.com/oauth2-proxy/oauth2-proxy/pull/905) Existing sessions from v6.0.0 or earlier are no longer valid. They will trigger a reauthentication. | - [#905](https://github.com/oauth2-proxy/oauth2-proxy/pull/905) Existing sessions from v6.0.0 or earlier are no longer valid. They will trigger a reauthentication. | ||||||
| - [#826](https://github.com/oauth2-proxy/oauth2-proxy/pull/826) `skip-auth-strip-headers` now applies to all requests, not just those where authentication would be skipped. | - [#826](https://github.com/oauth2-proxy/oauth2-proxy/pull/826) `skip-auth-strip-headers` now applies to all requests, not just those where authentication would be skipped. | ||||||
| - [#797](https://github.com/oauth2-proxy/oauth2-proxy/pull/797) The behavior of the Google provider Groups restriction changes with this | - [#797](https://github.com/oauth2-proxy/oauth2-proxy/pull/797) The behavior of the Google provider Groups restriction changes with this | ||||||
|  | @ -43,6 +44,7 @@ | ||||||
| 
 | 
 | ||||||
| ## Changes since v6.1.1 | ## Changes since v6.1.1 | ||||||
| 
 | 
 | ||||||
|  | - [#630](https://github.com/oauth2-proxy/oauth2-proxy/pull/630) Add support for Gitlab project based authentication (@factorysh) | ||||||
| - [#907](https://github.com/oauth2-proxy/oauth2-proxy/pull/907) Introduce alpha configuration option to enable testing of structured configuration (@JoelSpeed) | - [#907](https://github.com/oauth2-proxy/oauth2-proxy/pull/907) Introduce alpha configuration option to enable testing of structured configuration (@JoelSpeed) | ||||||
| - [#938](https://github.com/oauth2-proxy/oauth2-proxy/pull/938) Cleanup missed provider renaming refactor methods (@NickMeves) | - [#938](https://github.com/oauth2-proxy/oauth2-proxy/pull/938) Cleanup missed provider renaming refactor methods (@NickMeves) | ||||||
| - [#925](https://github.com/oauth2-proxy/oauth2-proxy/pull/925) Fix basic auth legacy header conversion (@JoelSpeed) | - [#925](https://github.com/oauth2-proxy/oauth2-proxy/pull/925) Fix basic auth legacy header conversion (@JoelSpeed) | ||||||
|  | @ -78,7 +80,6 @@ | ||||||
| - [#829](https://github.com/oauth2-proxy/oauth2-proxy/pull/820) Rename test directory to testdata (@johejo) | - [#829](https://github.com/oauth2-proxy/oauth2-proxy/pull/820) Rename test directory to testdata (@johejo) | ||||||
| - [#819](https://github.com/oauth2-proxy/oauth2-proxy/pull/819) Improve CI (@johejo) | - [#819](https://github.com/oauth2-proxy/oauth2-proxy/pull/819) Improve CI (@johejo) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # v6.1.1 | # v6.1.1 | ||||||
| 
 | 
 | ||||||
| ## Release Highlights | ## Release Highlights | ||||||
|  | @ -306,15 +307,18 @@ N/A | ||||||
| # v5.1.0 | # v5.1.0 | ||||||
| 
 | 
 | ||||||
| ## Release Highlights | ## Release Highlights | ||||||
|  | 
 | ||||||
| - Bump to Go 1.14 | - Bump to Go 1.14 | ||||||
| - Reduced number of Google API requests for group validation | - Reduced number of Google API requests for group validation | ||||||
| - Support for Redis Cluster | - Support for Redis Cluster | ||||||
| - Support for overriding hosts in hosts file | - Support for overriding hosts in hosts file | ||||||
| 
 | 
 | ||||||
| ## Important Notes | ## Important Notes | ||||||
|  | 
 | ||||||
| - [#335] The session expiry for the OIDC provider is now taken from the Token Response (expires_in) rather than from the id_token (exp) | - [#335] The session expiry for the OIDC provider is now taken from the Token Response (expires_in) rather than from the id_token (exp) | ||||||
| 
 | 
 | ||||||
| ## Breaking Changes | ## Breaking Changes | ||||||
|  | 
 | ||||||
| N/A | N/A | ||||||
| 
 | 
 | ||||||
| ## Changes since v5.0.0 | ## Changes since v5.0.0 | ||||||
|  | @ -338,12 +342,14 @@ N/A | ||||||
| # v5.0.0 | # v5.0.0 | ||||||
| 
 | 
 | ||||||
| ## Release Highlights | ## Release Highlights | ||||||
|  | 
 | ||||||
| - Disabled CGO (binaries will work regardless og glibc/musl) | - Disabled CGO (binaries will work regardless og glibc/musl) | ||||||
| - Allow whitelisted redirect ports | - Allow whitelisted redirect ports | ||||||
| - Nextcloud provider support added | - Nextcloud provider support added | ||||||
| - DigitalOcean provider support added | - DigitalOcean provider support added | ||||||
| 
 | 
 | ||||||
| ## Important Notes | ## Important Notes | ||||||
|  | 
 | ||||||
| - (Security) Fix for [open redirect vulnerability](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-qqxw-m5fj-f7gv).. a bad actor using `/\` in redirect URIs can redirect a session to another domain | - (Security) Fix for [open redirect vulnerability](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-qqxw-m5fj-f7gv).. a bad actor using `/\` in redirect URIs can redirect a session to another domain | ||||||
| 
 | 
 | ||||||
| ## Breaking Changes | ## Breaking Changes | ||||||
|  | @ -365,6 +371,7 @@ N/A | ||||||
| # v4.1.0 | # v4.1.0 | ||||||
| 
 | 
 | ||||||
| ## Release Highlights | ## Release Highlights | ||||||
|  | 
 | ||||||
| - Added Keycloak provider | - Added Keycloak provider | ||||||
| - Build on Go 1.13 | - Build on Go 1.13 | ||||||
| - Upgrade Docker image to use Debian Buster | - Upgrade Docker image to use Debian Buster | ||||||
|  | @ -373,12 +380,15 @@ N/A | ||||||
| - Added support for GitHub teams | - Added support for GitHub teams | ||||||
| 
 | 
 | ||||||
| ## Important Notes | ## Important Notes | ||||||
|  | 
 | ||||||
| N/A | N/A | ||||||
| 
 | 
 | ||||||
| ## Breaking Changes | ## Breaking Changes | ||||||
|  | 
 | ||||||
| N/A | N/A | ||||||
| 
 | 
 | ||||||
| ## Changes since v4.0.0 | ## Changes since v4.0.0 | ||||||
|  | 
 | ||||||
| - [#292](https://github.com/oauth2-proxy/oauth2-proxy/pull/292) Added bash >= 4.0 dependency to configure script (@jmfrank63) | - [#292](https://github.com/oauth2-proxy/oauth2-proxy/pull/292) Added bash >= 4.0 dependency to configure script (@jmfrank63) | ||||||
| - [#227](https://github.com/oauth2-proxy/oauth2-proxy/pull/227) Add Keycloak provider (@Ofinka) | - [#227](https://github.com/oauth2-proxy/oauth2-proxy/pull/227) Add Keycloak provider (@Ofinka) | ||||||
| - [#259](https://github.com/oauth2-proxy/oauth2-proxy/pull/259) Redirect to HTTPS (@jmickey) | - [#259](https://github.com/oauth2-proxy/oauth2-proxy/pull/259) Redirect to HTTPS (@jmickey) | ||||||
|  | @ -401,6 +411,7 @@ N/A | ||||||
| # v4.0.0 | # v4.0.0 | ||||||
| 
 | 
 | ||||||
| ## Release Highlights | ## Release Highlights | ||||||
|  | 
 | ||||||
| - Documentation is now on a [microsite](https://oauth2-proxy.github.io/oauth2-proxy/) | - Documentation is now on a [microsite](https://oauth2-proxy.github.io/oauth2-proxy/) | ||||||
| - Health check logging can now be disabled for quieter logs | - Health check logging can now be disabled for quieter logs | ||||||
| - Authorization Header JWTs can now be verified by the proxy to skip authentication for machine users | - Authorization Header JWTs can now be verified by the proxy to skip authentication for machine users | ||||||
|  | @ -408,8 +419,9 @@ N/A | ||||||
| - Logging overhaul allows customisable logging formats | - Logging overhaul allows customisable logging formats | ||||||
| 
 | 
 | ||||||
| ## Important Notes | ## Important Notes | ||||||
|  | 
 | ||||||
| - This release includes a number of breaking changes that will require users to | - This release includes a number of breaking changes that will require users to | ||||||
| reconfigure their proxies. Please read the Breaking Changes below thoroughly. |   reconfigure their proxies. Please read the Breaking Changes below thoroughly. | ||||||
| 
 | 
 | ||||||
| ## Breaking Changes | ## Breaking Changes | ||||||
| 
 | 
 | ||||||
|  | @ -514,6 +526,7 @@ reconfigure their proxies. Please read the Breaking Changes below thoroughly. | ||||||
| # v3.2.0 | # v3.2.0 | ||||||
| 
 | 
 | ||||||
| ## Release highlights | ## Release highlights | ||||||
|  | 
 | ||||||
| - Internal restructure of session state storage to use JSON rather than proprietary scheme | - Internal restructure of session state storage to use JSON rather than proprietary scheme | ||||||
| - Added health check options for running on GCP behind a load balancer | - Added health check options for running on GCP behind a load balancer | ||||||
| - Improved support for protecting websockets | - Improved support for protecting websockets | ||||||
|  | @ -521,9 +534,10 @@ reconfigure their proxies. Please read the Breaking Changes below thoroughly. | ||||||
| - Allow manual configuration of OIDC providers | - Allow manual configuration of OIDC providers | ||||||
| 
 | 
 | ||||||
| ## Important notes | ## Important notes | ||||||
|  | 
 | ||||||
| - Dockerfile user is now non-root, this may break your existing deployment | - Dockerfile user is now non-root, this may break your existing deployment | ||||||
| - In the OIDC provider, when no email is returned, the ID Token subject will be used | - In the OIDC provider, when no email is returned, the ID Token subject will be used | ||||||
| instead of returning an error |   instead of returning an error | ||||||
| - GitHub user emails must now be primary and verified before authenticating | - GitHub user emails must now be primary and verified before authenticating | ||||||
| 
 | 
 | ||||||
| ## Changes since v3.1.0 | ## Changes since v3.1.0 | ||||||
|  |  | ||||||
|  | @ -149,6 +149,8 @@ The group management in keycloak is using a tree. If you create a group named ad | ||||||
| 
 | 
 | ||||||
| Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes, and set the redirect url to your application url e.g. https://myapp.com/oauth2/callback. | Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes, and set the redirect url to your application url e.g. https://myapp.com/oauth2/callback. | ||||||
| 
 | 
 | ||||||
|  | If you need projects filtering, add the extra `read_api` scope to your application. | ||||||
|  | 
 | ||||||
| The following config should be set to ensure that the oauth will work properly. To get a cookie secret follow [these steps](https://github.com/oauth2-proxy/oauth2-proxy/blob/master/docs/configuration/configuration.md#configuration) | The following config should be set to ensure that the oauth will work properly. To get a cookie secret follow [these steps](https://github.com/oauth2-proxy/oauth2-proxy/blob/master/docs/configuration/configuration.md#configuration) | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -54,6 +54,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/ | ||||||
| | `--github-token` | string | the token to use when verifying repository collaborators (must have push access to the repository) | | | | `--github-token` | string | the token to use when verifying repository collaborators (must have push access to the repository) | | | ||||||
| | `--github-user` | string \| list | To allow users to login by username even if they do not belong to the specified org and team or collaborators | | | | `--github-user` | string \| list | To allow users to login by username even if they do not belong to the specified org and team or collaborators | | | ||||||
| | `--gitlab-group` | string \| list | restrict logins to members of any of these groups (slug), separated by a comma | | | | `--gitlab-group` | string \| list | restrict logins to members of any of these groups (slug), separated by a comma | | | ||||||
|  | | `--gitlab-projects` | string \| list | restrict logins to members of any of these projects (may be given multiple times) formatted as `orgname/repo=accesslevel`. Access level should be a value matching [Gitlab access levels](https://docs.gitlab.com/ee/api/members.html#valid-access-levels), defaulted to 20 if absent | | | ||||||
| | `--google-admin-email` | string | the google admin to impersonate for api calls | | | | `--google-admin-email` | string | the google admin to impersonate for api calls | | | ||||||
| | `--google-group` | string | restrict logins to members of this google group (may be given multiple times). | | | | `--google-group` | string | restrict logins to members of this google group (may be given multiple times). | | | ||||||
| | `--google-service-account-json` | string | the path to the service account json credentials | | | | `--google-service-account-json` | string | the path to the service account json credentials | | | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ type Options struct { | ||||||
| 	GitHubToken              string   `flag:"github-token" cfg:"github_token"` | 	GitHubToken              string   `flag:"github-token" cfg:"github_token"` | ||||||
| 	GitHubUsers              []string `flag:"github-user" cfg:"github_users"` | 	GitHubUsers              []string `flag:"github-user" cfg:"github_users"` | ||||||
| 	GitLabGroup              []string `flag:"gitlab-group" cfg:"gitlab_groups"` | 	GitLabGroup              []string `flag:"gitlab-group" cfg:"gitlab_groups"` | ||||||
|  | 	GitlabProjects           []string `flag:"gitlab-project" cfg:"gitlab_projects"` | ||||||
| 	GoogleGroups             []string `flag:"google-group" cfg:"google_group"` | 	GoogleGroups             []string `flag:"google-group" cfg:"google_group"` | ||||||
| 	GoogleAdminEmail         string   `flag:"google-admin-email" cfg:"google_admin_email"` | 	GoogleAdminEmail         string   `flag:"google-admin-email" cfg:"google_admin_email"` | ||||||
| 	GoogleServiceAccountJSON string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | 	GoogleServiceAccountJSON string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | ||||||
|  | @ -188,6 +189,7 @@ func NewFlagSet() *pflag.FlagSet { | ||||||
| 	flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)") | 	flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)") | ||||||
| 	flagSet.StringSlice("github-user", []string{}, "allow users with these usernames to login even if they do not belong to the specified org and team or collaborators (may be given multiple times)") | 	flagSet.StringSlice("github-user", []string{}, "allow users with these usernames to login even if they do not belong to the specified org and team or collaborators (may be given multiple times)") | ||||||
| 	flagSet.StringSlice("gitlab-group", []string{}, "restrict logins to members of this group (may be given multiple times)") | 	flagSet.StringSlice("gitlab-group", []string{}, "restrict logins to members of this group (may be given multiple times)") | ||||||
|  | 	flagSet.StringSlice("gitlab-project", []string{}, "restrict logins to members of this project (may be given multiple times) (eg `group/project=accesslevel`). Access level should be a value matching Gitlab access levels (see https://docs.gitlab.com/ee/api/members.html#valid-access-levels), defaulted to 20 if absent") | ||||||
| 	flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).") | 	flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).") | ||||||
| 	flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") | 	flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") | ||||||
| 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | ||||||
|  |  | ||||||
|  | @ -282,6 +282,12 @@ func parseProviderInfo(o *options.Options, msgs []string) []string { | ||||||
| 	case *providers.GitLabProvider: | 	case *providers.GitLabProvider: | ||||||
| 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | ||||||
| 		p.Groups = o.GitLabGroup | 		p.Groups = o.GitLabGroup | ||||||
|  | 		err := p.AddProjects(o.GitlabProjects) | ||||||
|  | 		if err != nil { | ||||||
|  | 			msgs = append(msgs, "failed to setup gitlab project access level") | ||||||
|  | 		} | ||||||
|  | 		p.SetAllowedGroups(p.PrefixAllowedGroups()) | ||||||
|  | 		p.SetProjectScope() | ||||||
| 
 | 
 | ||||||
| 		if p.Verifier == nil { | 		if p.Verifier == nil { | ||||||
| 			// Initialize with default verifier for gitlab.com
 | 			// Initialize with default verifier for gitlab.com
 | ||||||
|  |  | ||||||
|  | @ -3,10 +3,13 @@ package providers | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	oidc "github.com/coreos/go-oidc" |  | ||||||
| 	"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/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
| 	"golang.org/x/oauth2" | 	"golang.org/x/oauth2" | ||||||
| ) | ) | ||||||
|  | @ -16,10 +19,53 @@ type GitLabProvider struct { | ||||||
| 	*ProviderData | 	*ProviderData | ||||||
| 
 | 
 | ||||||
| 	Groups   []string | 	Groups   []string | ||||||
| 	Verifier             *oidc.IDTokenVerifier | 	Projects []*GitlabProject | ||||||
|  | 
 | ||||||
| 	AllowUnverifiedEmail bool | 	AllowUnverifiedEmail bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 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) | var _ Provider = (*GitLabProvider)(nil) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -64,6 +110,19 @@ func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) ( | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SetProjectScope ensure read_api is added to scope when filtering on projects
 | ||||||
|  | func (p *GitLabProvider) SetProjectScope() { | ||||||
|  | 	if len(p.Projects) > 0 { | ||||||
|  | 		for _, val := range strings.Split(p.Scope, " ") { | ||||||
|  | 			if val == "read_api" { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 		p.Scope += " read_api" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // RefreshSessionIfNeeded checks if the session has expired and uses the
 | // RefreshSessionIfNeeded checks if the session has expired and uses the
 | ||||||
| // RefreshToken to fetch a new ID token if required
 | // RefreshToken to fetch a new ID token if required
 | ||||||
| func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) { | func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) { | ||||||
|  | @ -144,25 +203,56 @@ func (p *GitLabProvider) getUserInfo(ctx context.Context, s *sessions.SessionSta | ||||||
| 	return &userInfo, nil | 	return &userInfo, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *GitLabProvider) verifyGroupMembership(userInfo *gitlabUserInfo) error { | type gitlabPermissionAccess struct { | ||||||
| 	if len(p.Groups) == 0 { | 	AccessLevel int `json:"access_level"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type gitlabProjectPermission struct { | ||||||
|  | 	ProjectAccess *gitlabPermissionAccess `json:"project_access"` | ||||||
|  | 	GroupAccess   *gitlabPermissionAccess `json:"group_access"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type gitlabProjectInfo struct { | ||||||
|  | 	Name              string                  `json:"name"` | ||||||
|  | 	Archived          bool                    `json:"archived"` | ||||||
|  | 	PathWithNamespace string                  `json:"path_with_namespace"` | ||||||
|  | 	Permissions       gitlabProjectPermission `json:"permissions"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.SessionState, project string) (*gitlabProjectInfo, error) { | ||||||
|  | 	var projectInfo gitlabProjectInfo | ||||||
|  | 
 | ||||||
|  | 	endpointURL := &url.URL{ | ||||||
|  | 		Scheme: p.LoginURL.Scheme, | ||||||
|  | 		Host:   p.LoginURL.Host, | ||||||
|  | 		Path:   "/api/v4/projects/", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := requests.New(fmt.Sprintf("%s%s", endpointURL.String(), url.QueryEscape(project))). | ||||||
|  | 		WithContext(ctx). | ||||||
|  | 		SetHeader("Authorization", "Bearer "+s.AccessToken). | ||||||
|  | 		Do(). | ||||||
|  | 		UnmarshalInto(&projectInfo) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to get project info: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &projectInfo, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddProjects adds Gitlab projects from options to GitlabProvider struct
 | ||||||
|  | func (p *GitLabProvider) AddProjects(projects []string) error { | ||||||
|  | 	for _, project := range projects { | ||||||
|  | 		gp, err := newGitlabproject(project) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.Projects = append(p.Projects, gp) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Collect user group memberships
 |  | ||||||
| 	membershipSet := make(map[string]bool) |  | ||||||
| 	for _, group := range userInfo.Groups { |  | ||||||
| 		membershipSet[group] = true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Find a valid group that they are a member of
 |  | ||||||
| 	for _, validGroup := range p.Groups { |  | ||||||
| 		if _, ok := membershipSet[validGroup]; ok { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fmt.Errorf("user is not a member of '%s'", p.Groups) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) { | func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) { | ||||||
|  | @ -193,7 +283,7 @@ func (p *GitLabProvider) ValidateSession(ctx context.Context, s *sessions.Sessio | ||||||
| 	return err == nil | 	return err == nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetEmailAddress returns the Account email address
 | // EnrichSession adds values and data from the Gitlab endpoint to current session
 | ||||||
| func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { | func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { | ||||||
| 	// Retrieve user info
 | 	// Retrieve user info
 | ||||||
| 	userInfo, err := p.getUserInfo(ctx, s) | 	userInfo, err := p.getUserInfo(ctx, s) | ||||||
|  | @ -206,15 +296,67 @@ func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionS | ||||||
| 		return fmt.Errorf("user email is not verified") | 		return fmt.Errorf("user email is not verified") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check group membership
 |  | ||||||
| 	// TODO (@NickMeves) - Refactor to Authorize
 |  | ||||||
| 	err = p.verifyGroupMembership(userInfo) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("group membership check failed: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	s.User = userInfo.Username | 	s.User = userInfo.Username | ||||||
| 	s.Email = userInfo.Email | 	s.Email = userInfo.Email | ||||||
| 
 | 
 | ||||||
|  | 	p.addGroupsToSession(ctx, s) | ||||||
|  | 
 | ||||||
|  | 	p.addProjectsToSession(ctx, s) | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // addGroupsToSession projects into session.Groups
 | ||||||
|  | func (p *GitLabProvider) addGroupsToSession(ctx context.Context, s *sessions.SessionState) { | ||||||
|  | 	// Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
 | ||||||
|  | 	for _, group := range p.Groups { | ||||||
|  | 		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if 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) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,13 +2,15 @@ package providers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/stretchr/testify/assert" | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/ginkgo/extensions/table" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func testGitLabProvider(hostname string) *GitLabProvider { | func testGitLabProvider(hostname string) *GitLabProvider { | ||||||
|  | @ -39,100 +41,241 @@ func testGitLabBackend() *httptest.Server { | ||||||
| 			"groups": ["foo", "bar"] | 			"groups": ["foo", "bar"] | ||||||
| 		} | 		} | ||||||
| 	` | 	` | ||||||
|  | 	projectInfo := ` | ||||||
|  | 		{ | ||||||
|  | 			"name": "MyProject", | ||||||
|  | 			"archived": false, | ||||||
|  | 			"path_with_namespace": "my_group/my_project", | ||||||
|  | 			"permissions": { | ||||||
|  | 				"project_access": null, | ||||||
|  | 				"group_access": { | ||||||
|  | 					"access_level": 30, | ||||||
|  | 					"notification_level": 3 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	` | ||||||
|  | 
 | ||||||
|  | 	personalProjectInfo := ` | ||||||
|  | 		{ | ||||||
|  | 			"name": "MyPersonalProject", | ||||||
|  | 			"archived": false, | ||||||
|  | 			"path_with_namespace": "my_profile/my_personal_project", | ||||||
|  | 			"permissions": { | ||||||
|  | 				"project_access": { | ||||||
|  | 					"access_level": 30, | ||||||
|  | 					"notification_level": 3 | ||||||
|  | 				}, | ||||||
|  | 				"group_access": null | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	` | ||||||
|  | 
 | ||||||
|  | 	archivedProjectInfo := ` | ||||||
|  | 		{ | ||||||
|  | 			"name": "MyArchivedProject", | ||||||
|  | 			"archived": true, | ||||||
|  | 			"path_with_namespace": "my_group/my_archived_project", | ||||||
|  | 			"permissions": { | ||||||
|  | 				"project_access": { | ||||||
|  | 					"access_level": 30, | ||||||
|  | 					"notification_level": 3 | ||||||
|  | 				}, | ||||||
|  | 				"group_access": null | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	` | ||||||
|  | 
 | ||||||
| 	authHeader := "Bearer gitlab_access_token" | 	authHeader := "Bearer gitlab_access_token" | ||||||
| 
 | 
 | ||||||
| 	return httptest.NewServer(http.HandlerFunc( | 	return httptest.NewServer(http.HandlerFunc( | ||||||
| 		func(w http.ResponseWriter, r *http.Request) { | 		func(w http.ResponseWriter, r *http.Request) { | ||||||
| 			if r.URL.Path == "/oauth/userinfo" { | 			switch r.URL.Path { | ||||||
|  | 			case "/oauth/userinfo": | ||||||
| 				if r.Header["Authorization"][0] == authHeader { | 				if r.Header["Authorization"][0] == authHeader { | ||||||
| 					w.WriteHeader(200) | 					w.WriteHeader(200) | ||||||
| 					w.Write([]byte(userInfo)) | 					w.Write([]byte(userInfo)) | ||||||
| 				} else { | 				} else { | ||||||
| 					w.WriteHeader(401) | 					w.WriteHeader(401) | ||||||
| 				} | 				} | ||||||
|  | 			case "/api/v4/projects/my_group/my_project": | ||||||
|  | 				if r.Header["Authorization"][0] == authHeader { | ||||||
|  | 					w.WriteHeader(200) | ||||||
|  | 					w.Write([]byte(projectInfo)) | ||||||
| 				} else { | 				} else { | ||||||
|  | 					w.WriteHeader(401) | ||||||
|  | 				} | ||||||
|  | 			case "/api/v4/projects/my_group/my_archived_project": | ||||||
|  | 				if r.Header["Authorization"][0] == authHeader { | ||||||
|  | 					w.WriteHeader(200) | ||||||
|  | 					w.Write([]byte(archivedProjectInfo)) | ||||||
|  | 				} else { | ||||||
|  | 					w.WriteHeader(401) | ||||||
|  | 				} | ||||||
|  | 			case "/api/v4/projects/my_profile/my_personal_project": | ||||||
|  | 				if r.Header["Authorization"][0] == authHeader { | ||||||
|  | 					w.WriteHeader(200) | ||||||
|  | 					w.Write([]byte(personalProjectInfo)) | ||||||
|  | 				} else { | ||||||
|  | 					w.WriteHeader(401) | ||||||
|  | 				} | ||||||
|  | 			case "/api/v4/projects/my_group/my_bad_project": | ||||||
|  | 				w.WriteHeader(403) | ||||||
|  | 			default: | ||||||
| 				w.WriteHeader(404) | 				w.WriteHeader(404) | ||||||
| 			} | 			} | ||||||
| 		})) | 		})) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderBadToken(t *testing.T) { | var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 	b := testGitLabBackend() | 	var p *GitLabProvider | ||||||
| 	defer b.Close() | 	var b *httptest.Server | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	BeforeEach(func() { | ||||||
| 	p := testGitLabProvider(bURL.Host) | 		b = testGitLabBackend() | ||||||
| 
 | 
 | ||||||
|  | 		bURL, err := url.Parse(b.URL) | ||||||
|  | 		Expect(err).To(BeNil()) | ||||||
|  | 
 | ||||||
|  | 		p = testGitLabProvider(bURL.Host) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	AfterEach(func() { | ||||||
|  | 		b.Close() | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	Context("with bad token", func() { | ||||||
|  | 		It("should trigger an error", func() { | ||||||
|  | 			p.AllowUnverifiedEmail = false | ||||||
| 			session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"} | 			session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"} | ||||||
| 			err := p.EnrichSession(context.Background(), session) | 			err := p.EnrichSession(context.Background(), session) | ||||||
| 	assert.Error(t, err) | 			Expect(err).To(MatchError(errors.New("failed to retrieve user info: error getting user info: unexpected status \"401\": "))) | ||||||
| } | 		}) | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) { | 	Context("when filtering on email", func() { | ||||||
| 	b := testGitLabBackend() | 		type emailsTableInput struct { | ||||||
| 	defer b.Close() | 			expectedError        error | ||||||
| 
 | 			expectedValue        string | ||||||
| 	bURL, _ := url.Parse(b.URL) | 			allowUnverifiedEmail bool | ||||||
| 	p := testGitLabProvider(bURL.Host) | 		} | ||||||
| 
 | 
 | ||||||
|  | 		DescribeTable("should return expected results", | ||||||
|  | 			func(in emailsTableInput) { | ||||||
|  | 				p.AllowUnverifiedEmail = in.allowUnverifiedEmail | ||||||
| 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | ||||||
|  | 
 | ||||||
| 				err := p.EnrichSession(context.Background(), session) | 				err := p.EnrichSession(context.Background(), session) | ||||||
| 	assert.Error(t, err) |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) { | 				if in.expectedError != nil { | ||||||
| 	b := testGitLabBackend() | 					Expect(err).To(MatchError(err)) | ||||||
| 	defer b.Close() | 				} else { | ||||||
|  | 					Expect(err).To(BeNil()) | ||||||
|  | 					Expect(session.Email).To(Equal(in.expectedValue)) | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			Entry("unverified email denied", emailsTableInput{ | ||||||
|  | 				expectedError:        errors.New("user email is not verified"), | ||||||
|  | 				allowUnverifiedEmail: false, | ||||||
|  | 			}), | ||||||
|  | 			Entry("unverified email allowed", emailsTableInput{ | ||||||
|  | 				expectedError:        nil, | ||||||
|  | 				expectedValue:        "foo@bar.com", | ||||||
|  | 				allowUnverifiedEmail: true, | ||||||
|  | 			}), | ||||||
|  | 		) | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	Context("when filtering on gitlab entities (groups and projects)", func() { | ||||||
| 	p := testGitLabProvider(bURL.Host) | 		type entitiesTableInput struct { | ||||||
|  | 			expectedValue []string | ||||||
|  | 			projects      []string | ||||||
|  | 			groups        []string | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		DescribeTable("should return expected results", | ||||||
|  | 			func(in entitiesTableInput) { | ||||||
| 				p.AllowUnverifiedEmail = true | 				p.AllowUnverifiedEmail = true | ||||||
| 
 |  | ||||||
| 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | ||||||
| 	err := p.EnrichSession(context.Background(), session) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.Equal(t, "foo@bar.com", session.Email) |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderUsername(t *testing.T) { | 				err := p.AddProjects(in.projects) | ||||||
| 	b := testGitLabBackend() | 				Expect(err).To(BeNil()) | ||||||
| 	defer b.Close() | 				p.SetProjectScope() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 				if len(in.groups) > 0 { | ||||||
| 	p := testGitLabProvider(bURL.Host) | 					p.Groups = in.groups | ||||||
| 	p.AllowUnverifiedEmail = true | 				} | ||||||
| 
 | 
 | ||||||
| 	session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 				err = p.EnrichSession(context.Background(), session) | ||||||
| 	err := p.EnrichSession(context.Background(), session) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.Equal(t, "FooBar", session.User) |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderGroupMembershipValid(t *testing.T) { | 				Expect(err).To(BeNil()) | ||||||
| 	b := testGitLabBackend() | 				Expect(session.Groups).To(Equal(in.expectedValue)) | ||||||
| 	defer b.Close() | 			}, | ||||||
|  | 			Entry("project membership valid on group project", entitiesTableInput{ | ||||||
|  | 				expectedValue: []string{"project:my_group/my_project"}, | ||||||
|  | 				projects:      []string{"my_group/my_project"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("project membership invalid on group project, insufficient access level level", entitiesTableInput{ | ||||||
|  | 				expectedValue: nil, | ||||||
|  | 				projects:      []string{"my_group/my_project=40"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("project membership valid on personnal project", entitiesTableInput{ | ||||||
|  | 				expectedValue: []string{"project:my_profile/my_personal_project"}, | ||||||
|  | 				projects:      []string{"my_profile/my_personal_project"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("project membership invalid on personnal project, insufficient access level", entitiesTableInput{ | ||||||
|  | 				expectedValue: nil, | ||||||
|  | 				projects:      []string{"my_profile/my_personal_project=40"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("project membership invalid", entitiesTableInput{ | ||||||
|  | 				expectedValue: nil, | ||||||
|  | 				projects:      []string{"my_group/my_bad_project"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("group membership valid", entitiesTableInput{ | ||||||
|  | 				expectedValue: []string{"group:foo"}, | ||||||
|  | 				groups:        []string{"foo"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("groups and projects", entitiesTableInput{ | ||||||
|  | 				expectedValue: []string{"group:foo", "group:baz", "project:my_group/my_project", "project:my_profile/my_personal_project"}, | ||||||
|  | 				groups:        []string{"foo", "baz"}, | ||||||
|  | 				projects:      []string{"my_group/my_project", "my_profile/my_personal_project"}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("archived projects", entitiesTableInput{ | ||||||
|  | 				expectedValue: nil, | ||||||
|  | 				groups:        []string{}, | ||||||
|  | 				projects:      []string{"my_group/my_archived_project"}, | ||||||
|  | 			}), | ||||||
|  | 		) | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	}) | ||||||
| 	p := testGitLabProvider(bURL.Host) |  | ||||||
| 	p.AllowUnverifiedEmail = true |  | ||||||
| 	p.Groups = []string{"foo"} |  | ||||||
| 
 | 
 | ||||||
| 	session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 	Context("when generating group list from multiple kind", func() { | ||||||
| 	err := p.EnrichSession(context.Background(), session) | 		type entitiesTableInput struct { | ||||||
| 	assert.NoError(t, err) | 			projects []string | ||||||
| 	assert.Equal(t, "FooBar", session.User) | 			groups   []string | ||||||
| } | 		} | ||||||
| 
 | 
 | ||||||
| func TestGitLabProviderGroupMembershipMissing(t *testing.T) { | 		DescribeTable("should prefix entities with group kind", func(in entitiesTableInput) { | ||||||
| 	b := testGitLabBackend() | 			p.Groups = in.groups | ||||||
| 	defer b.Close() | 			err := p.AddProjects(in.projects) | ||||||
|  | 			Expect(err).To(BeNil()) | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 			all := p.PrefixAllowedGroups() | ||||||
| 	p := testGitLabProvider(bURL.Host) |  | ||||||
| 	p.AllowUnverifiedEmail = true |  | ||||||
| 	p.Groups = []string{"baz"} |  | ||||||
| 
 | 
 | ||||||
| 	session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | 			Expect(len(all)).To(Equal(len(in.projects) + len(in.groups))) | ||||||
| 	err := p.EnrichSession(context.Background(), session) | 		}, | ||||||
| 	assert.Error(t, err) | 			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"}, | ||||||
|  | 			}), | ||||||
|  | 		) | ||||||
|  | 	}) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package providers_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
|  | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestProviderSuite(t *testing.T) { | ||||||
|  | 	logger.SetOutput(GinkgoWriter) | ||||||
|  | 
 | ||||||
|  | 	RegisterFailHandler(Fail) | ||||||
|  | 	RunSpecs(t, "Providers") | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue