git.iko.soy/typeable/oauth2-proxy
on master help sign in
Use OIDC as base of Gitlab provider
3092941Nick Meves
providers/gitlab.go+127−236
@@ -6,194 +6,196 @@ import (
6 "net/url"
6 "net/url"
7 "strconv"
7 "strconv"
8 "strings"
8 "strings"
9− "time"
10
9
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
14− "golang.org/x/oauth2"
13+)
14+
15+const (
16+ gitlabProviderName = "GitLab"
17+ gitlabDefaultScope = "openid email"
15)
18)
16
19
17// GitLabProvider represents a GitLab based Identity Provider
20// GitLabProvider represents a GitLab based Identity Provider
18type GitLabProvider struct {
21type GitLabProvider struct {
19− *ProviderData
22+ *OIDCProvider
20
23
21− Groups []string
24+ allowedProjects []*gitlabProject
22− Projects []*GitlabProject
23}
25}
24
26
27+var _ Provider = (*GitLabProvider)(nil)
28+
29+// NewGitLabProvider initiates a new GitLabProvider
30+func NewGitLabProvider(p *ProviderData) *GitLabProvider {
31+ p.ProviderName = gitlabProviderName
32+ if p.Scope == "" {
33+ p.Scope = gitlabDefaultScope
34+ }
35+
36+ return &GitLabProvider{
37+ OIDCProvider: &OIDCProvider{
38+ ProviderData: p,
39+ SkipNonce: false,
40+ },
41+ }
42+}
43+
44+// SetAllowedProjects adds Gitlab projects to the AllowedGroups list
45+// and tracks them to do a project API lookup during `EnrichSession`.
46+func (p *GitLabProvider) SetAllowedProjects(projects []string) error {
47+ for _, project := range projects {
48+ gp, err := newGitlabProject(project)
49+ if err != nil {
50+ return err
51+ }
52+ p.allowedProjects = append(p.allowedProjects, gp)
53+ p.AllowedGroups[formatProject(gp)] = struct{}{}
54+ }
55+ if len(p.allowedProjects) > 0 {
56+ p.setProjectScope()
57+ }
58+ return nil
59+}
60+
25−// GitlabProject represents a Gitlab project constraint entity
61+// gitlabProject represents a Gitlab project constraint entity
26−type GitlabProject struct {
62+type gitlabProject struct {
27 Name string
63 Name string
28 AccessLevel int
64 AccessLevel int
29}
65}
30
66
31−// newGitlabProject Creates a new GitlabProject struct from project string formatted as namespace/project=accesslevel
67+// newGitlabProject Creates a new GitlabProject struct from project string
68+// formatted as `namespace/project=accesslevel`
32// if no accesslevel provided, use the default one
69// if no accesslevel provided, use the default one
33−func newGitlabproject(project string) (*GitlabProject, error) {
70+func newGitlabProject(project string) (*gitlabProject, error) {
34− // default access level is 20
35− defaultAccessLevel := 20
71+ const defaultAccessLevel = 20
36 // see https://docs.gitlab.com/ee/api/members.html#valid-access-levels
72 // see https://docs.gitlab.com/ee/api/members.html#valid-access-levels
37 validAccessLevel := [4]int{10, 20, 30, 40}
73 validAccessLevel := [4]int{10, 20, 30, 40}
38
74
39 parts := strings.SplitN(project, "=", 2)
75 parts := strings.SplitN(project, "=", 2)
40−
41 if len(parts) == 2 {
76 if len(parts) == 2 {
42 lvl, err := strconv.Atoi(parts[1])
77 lvl, err := strconv.Atoi(parts[1])
43 if err != nil {
78 if err != nil {
44 return nil, err
79 return nil, err
45 }
80 }
46−
47 for _, valid := range validAccessLevel {
81 for _, valid := range validAccessLevel {
48 if lvl == valid {
82 if lvl == valid {
49− return &GitlabProject{
83+ return &gitlabProject{
50− Name: parts[0],
84+ Name: parts[0],
51− AccessLevel: lvl},
85+ AccessLevel: lvl,
52− err
86+ }, nil
53 }
87 }
54 }
88 }
55−
56 return nil, fmt.Errorf("invalid gitlab project access level specified (%s)", parts[0])
89 return nil, fmt.Errorf("invalid gitlab project access level specified (%s)", parts[0])
57−
58− }
59−
60− return &GitlabProject{
61− Name: project,
62− AccessLevel: defaultAccessLevel},
63− nil
64−
65−}
66−
67−var _ Provider = (*GitLabProvider)(nil)
68−
69−const (
70− gitlabProviderName = "GitLab"
71− gitlabDefaultScope = "openid email"
72−)
73−
74−// NewGitLabProvider initiates a new GitLabProvider
75−func NewGitLabProvider(p *ProviderData) *GitLabProvider {
76− p.ProviderName = gitlabProviderName
77−
78− if p.Scope == "" {
79− p.Scope = gitlabDefaultScope
80 }
90 }
81
91
82− return &GitLabProvider{ProviderData: p}
92+ return &gitlabProject{
93+ Name: project,
94+ AccessLevel: defaultAccessLevel,
95+ }, nil
83}
96}
84
97
85−// Redeem exchanges the OAuth2 authentication token for an ID token
86−func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (s *sessions.SessionState, err error) {
87− clientSecret, err := p.GetClientSecret()
88− if err != nil {
89− return
90− }
91−
92− c := oauth2.Config{
93− ClientID: p.ClientID,
94− ClientSecret: clientSecret,
95− Endpoint: oauth2.Endpoint{
96− TokenURL: p.RedeemURL.String(),
97− },
98− RedirectURL: redirectURL,
99− }
100− token, err := c.Exchange(ctx, code)
101− if err != nil {
102− return nil, fmt.Errorf("token exchange: %v", err)
103− }
104− s, err = p.createSession(ctx, token)
105− if err != nil {
106− return nil, fmt.Errorf("unable to update session: %v", err)
107− }
108− return
109−}
110−
111−// SetProjectScope ensure read_api is added to scope when filtering on projects
98+// setProjectScope ensures read_api is added to scope when filtering on projects
112−func (p *GitLabProvider) SetProjectScope() {
99+func (p *GitLabProvider) setProjectScope() {
113− if len(p.Projects) > 0 {
114− for _, val := range strings.Split(p.Scope, " ") {
100+ for _, val := range strings.Split(p.Scope, " ") {
115− if val == "read_api" {
101+ if val == "read_api" {
116− return
102+ return
117− }
118−
119 }
103 }
120− p.Scope += " read_api"
121 }
104 }
105+ p.Scope += " read_api"
122}
106}
123
107
124−// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
108+// EnrichSession enriches the session with the response from the userinfo API
109+// endpoint & projects API endpoint for allowed projects.
125−func (p *GitLabProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
110+func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
126− if s == nil || s.RefreshToken == "" {
111+ // Retrieve user info
127− return false, nil
112+ userinfo, err := p.getUserinfo(ctx, s)
128− }
129−
130− origExpiration := s.ExpiresOn
131−
132− err := p.redeemRefreshToken(ctx, s)
133 if err != nil {
113 if err != nil {
134− return false, fmt.Errorf("unable to redeem refresh token: %v", err)
114+ return fmt.Errorf("failed to retrieve user info: %v", err)
135 }
115 }
136
116
137− logger.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
117+ // Check if email is verified
138− return true, nil
118+ if !p.AllowUnverifiedEmail && !userinfo.EmailVerified {
139−}
119+ return fmt.Errorf("user email is not verified")
140−
141−func (p *GitLabProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) error {
142− clientSecret, err := p.GetClientSecret()
143− if err != nil {
144− return err
145 }
120 }
146
121
147− c := oauth2.Config{
122+ s.User = userinfo.Username
148− ClientID: p.ClientID,
123+ s.Email = userinfo.Email
149− ClientSecret: clientSecret,
124+ s.Groups = append(s.Groups, userinfo.Groups...)
150− Endpoint: oauth2.Endpoint{
125+
151− TokenURL: p.RedeemURL.String(),
126+ p.addProjectsToSession(ctx, s)
152− },
153− }
154− t := &oauth2.Token{
155− RefreshToken: s.RefreshToken,
156− Expiry: time.Now().Add(-time.Hour),
157− }
158− token, err := c.TokenSource(ctx, t).Token()
159− if err != nil {
160− return fmt.Errorf("failed to get token: %v", err)
161− }
162− newSession, err := p.createSession(ctx, token)
163− if err != nil {
164− return fmt.Errorf("unable to update session: %v", err)
165− }
166− *s = *newSession
167
127
168 return nil
128 return nil
169}
129}
170
130
171−type gitlabUserInfo struct {
131+type gitlabUserinfo struct {
172 Username string `json:"nickname"`
132 Username string `json:"nickname"`
173 Email string `json:"email"`
133 Email string `json:"email"`
174 EmailVerified bool `json:"email_verified"`
134 EmailVerified bool `json:"email_verified"`
175 Groups []string `json:"groups"`
135 Groups []string `json:"groups"`
176}
136}
177
137
178−func (p *GitLabProvider) getUserInfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserInfo, error) {
138+func (p *GitLabProvider) getUserinfo(ctx context.Context, s *sessions.SessionState) (*gitlabUserinfo, error) {
179 // Retrieve user info JSON
139 // Retrieve user info JSON
180 // https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
140 // https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
181
141
182 // Build user info url from login url of GitLab instance
142 // Build user info url from login url of GitLab instance
183− userInfoURL := *p.LoginURL
143+ userinfoURL := *p.LoginURL
184− userInfoURL.Path = "/oauth/userinfo"
144+ userinfoURL.Path = "/oauth/userinfo"
185
145
186− var userInfo gitlabUserInfo
146+ var userinfo gitlabUserinfo
187− err := requests.New(userInfoURL.String()).
147+ err := requests.New(userinfoURL.String()).
188 WithContext(ctx).
148 WithContext(ctx).
189 SetHeader("Authorization", "Bearer "+s.AccessToken).
149 SetHeader("Authorization", "Bearer "+s.AccessToken).
190 Do().
150 Do().
191− UnmarshalInto(&userInfo)
151+ UnmarshalInto(&userinfo)
192 if err != nil {
152 if err != nil {
193 return nil, fmt.Errorf("error getting user info: %v", err)
153 return nil, fmt.Errorf("error getting user info: %v", err)
194 }
154 }
195
155
196− return &userInfo, nil
156+ return &userinfo, nil
157+}
158+
159+// addProjectsToSession adds projects matching user access requirements into
160+// the session state groups list.
161+// This method prefixes projects names with `project:` to specify group kind.
162+func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.SessionState) {
163+ // Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
164+ for _, project := range p.allowedProjects {
165+ projectInfo, err := p.getProjectInfo(ctx, s, project.Name)
166+ if err != nil {
167+ logger.Errorf("Warning: project info request failed: %v", err)
168+ continue
169+ }
170+
171+ if projectInfo.Archived {
172+ logger.Errorf("Warning: project %s is archived", project.Name)
173+ continue
174+ }
175+
176+ perms := projectInfo.Permissions.ProjectAccess
177+ if perms == nil {
178+ // use group project access as fallback
179+ perms = projectInfo.Permissions.GroupAccess
180+ // group project access is not set for this user then we give up
181+ if perms == nil {
182+ logger.Errorf("Warning: user %q has no project level access to %s",
183+ s.Email, project.Name)
184+ continue
185+ }
186+ }
187+
188+ if perms.AccessLevel < project.AccessLevel {
189+ logger.Errorf(
190+ "Warning: user %q does not have the minimum required access level for project %q",
191+ s.Email,
192+ project.Name,
193+ )
194+ continue
195+ }
196+
197+ s.Groups = append(s.Groups, formatProject(project))
198+ }
197}
199}
198
200
199type gitlabPermissionAccess struct {
201type gitlabPermissionAccess struct {
@@ -226,7 +228,6 @@ func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.Session
226 SetHeader("Authorization", "Bearer "+s.AccessToken).
228 SetHeader("Authorization", "Bearer "+s.AccessToken).
227 Do().
229 Do().
228 UnmarshalInto(&projectInfo)
230 UnmarshalInto(&projectInfo)
229−
230 if err != nil {
231 if err != nil {
231 return nil, fmt.Errorf("failed to get project info: %v", err)
232 return nil, fmt.Errorf("failed to get project info: %v", err)
232 }
233 }
@@ -234,116 +235,6 @@ func (p *GitLabProvider) getProjectInfo(ctx context.Context, s *sessions.Session
234 return &projectInfo, nil
235 return &projectInfo, nil
235}
236}
236
237
237−// AddProjects adds Gitlab projects from options to GitlabProvider struct
238+func formatProject(project *gitlabProject) string {
238−func (p *GitLabProvider) AddProjects(projects []string) error {
239− for _, project := range projects {
240− gp, err := newGitlabproject(project)
241− if err != nil {
242− return err
243− }
244−
245− p.Projects = append(p.Projects, gp)
246− }
247−
248− return nil
249−}
250−
251−func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
252− idToken, err := p.verifyIDToken(ctx, token)
253− if err != nil {
254− switch err {
255− case ErrMissingIDToken:
256− return nil, fmt.Errorf("token response did not contain an id_token")
257− default:
258− return nil, fmt.Errorf("could not verify id_token: %v", err)
259− }
260− }
261−
262− ss := &sessions.SessionState{
263− AccessToken: token.AccessToken,
264− IDToken: getIDToken(token),
265− RefreshToken: token.RefreshToken,
266− }
267−
268− ss.CreatedAtNow()
269− ss.SetExpiresOn(idToken.Expiry)
270−
271− return ss, nil
272−}
273−
274−// ValidateSession checks that the session's IDToken is still valid
275−func (p *GitLabProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
276− _, err := p.Verifier.Verify(ctx, s.IDToken)
277− return err == nil
278−}
279−
280−// EnrichSession adds values and data from the Gitlab endpoint to current session
281−func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
282− // Retrieve user info
283− userInfo, err := p.getUserInfo(ctx, s)
284− if err != nil {
285− return fmt.Errorf("failed to retrieve user info: %v", err)
286− }
287−
288− // Check if email is verified
289− if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
290− return fmt.Errorf("user email is not verified")
291− }
292−
293− s.User = userInfo.Username
294− s.Email = userInfo.Email
295− for _, group := range userInfo.Groups {
296− s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
297− }
298−
299− p.addProjectsToSession(ctx, s)
300−
301− return nil
302−}
303−
304−// addProjectsToSession adds projects matching user access requirements into the session state groups list
305−// This method prefix projects names with `project` to specify group kind
306−func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.SessionState) {
307− // Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
308− for _, project := range p.Projects {
309− projectInfo, err := p.getProjectInfo(ctx, s, project.Name)
310−
311− if err != nil {
312− logger.Errorf("Warning: project info request failed: %v", err)
313− continue
314− }
315−
316− if !projectInfo.Archived {
317− perms := projectInfo.Permissions.ProjectAccess
318− if perms == nil {
319− // use group project access as fallback
320− perms = projectInfo.Permissions.GroupAccess
321− // group project access is not set for this user then we give up
322− if perms == nil {
323− logger.Errorf("Warning: user %q has no project level access to %s", s.Email, project.Name)
324− continue
325− }
326− }
327−
328− if perms != nil && perms.AccessLevel >= project.AccessLevel {
329− s.Groups = append(s.Groups, fmt.Sprintf("project:%s", project.Name))
239+ return fmt.Sprintf("project:%s", project.Name)
330− } else {
331− logger.Errorf("Warning: user %q does not have the minimum required access level for project %q", s.Email, project.Name)
332− }
333− continue
334− }
335−
336− logger.Errorf("Warning: project %s is archived", project.Name)
337− }
338−}
339−
340−// PrefixAllowedGroups returns a list of allowed groups, prefixed by their `kind` value
341−func (p *GitLabProvider) PrefixAllowedGroups() (groups []string) {
342− for _, val := range p.Groups {
343− groups = append(groups, fmt.Sprintf("group:%s", val))
344− }
345− for _, val := range p.Projects {
346− groups = append(groups, fmt.Sprintf("project:%s", val.Name))
347− }
348− return groups
349}
240}
providers/gitlab_test.go+65−60
@@ -188,7 +188,7 @@ var _ = Describe("Gitlab Provider Tests", func() {
188 err := p.EnrichSession(context.Background(), session)
188 err := p.EnrichSession(context.Background(), session)
189
189
190 if in.expectedError != nil {
190 if in.expectedError != nil {
191− Expect(err).To(MatchError(err))
191+ Expect(err).To(MatchError(in.expectedError))
192 } else {
192 } else {
193 Expect(err).To(BeNil())
193 Expect(err).To(BeNil())
194 Expect(session.Email).To(Equal(in.expectedValue))
194 Expect(session.Email).To(Equal(in.expectedValue))
@@ -208,97 +208,102 @@ var _ = Describe("Gitlab Provider Tests", func() {
208
208
209 Context("when filtering on gitlab entities (groups and projects)", func() {
209 Context("when filtering on gitlab entities (groups and projects)", func() {
210 type entitiesTableInput struct {
210 type entitiesTableInput struct {
211− expectedValue []string
211+ allowedProjects []string
212− projects []string
212+ allowedGroups []string
213− groups []string
213+ scope string
214+ expectedAuthz bool
215+ expectedError error
216+ expectedGroups []string
217+ expectedScope string
214 }
218 }
215
219
216 DescribeTable("should return expected results",
220 DescribeTable("should return expected results",
217 func(in entitiesTableInput) {
221 func(in entitiesTableInput) {
218 p.AllowUnverifiedEmail = true
222 p.AllowUnverifiedEmail = true
223+ if in.scope != "" {
224+ p.Scope = in.scope
225+ }
219 session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
226 session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
220
227
221− err := p.AddProjects(in.projects)
228+ p.SetAllowedGroups(in.allowedGroups)
222− Expect(err).To(BeNil())
223− p.SetProjectScope()
224
229
225− if len(in.groups) > 0 {
230+ err := p.SetAllowedProjects(in.allowedProjects)
226− p.Groups = in.groups
231+ if err == nil {
232+ Expect(err).To(BeNil())
233+ } else {
234+ Expect(err).To(MatchError(in.expectedError))
235+ return
227 }
236 }
237+ Expect(p.Scope).To(Equal(in.expectedScope))
228
238
229 err = p.EnrichSession(context.Background(), session)
239 err = p.EnrichSession(context.Background(), session)
240+ Expect(err).To(BeNil())
241+ Expect(session.Groups).To(Equal(in.expectedGroups))
230
242
243+ authorized, err := p.Authorize(context.Background(), session)
231 Expect(err).To(BeNil())
244 Expect(err).To(BeNil())
232− Expect(session.Groups).To(Equal(in.expectedValue))
245+ Expect(authorized).To(Equal(in.expectedAuthz))
233 },
246 },
234 Entry("project membership valid on group project", entitiesTableInput{
247 Entry("project membership valid on group project", entitiesTableInput{
248+ allowedProjects: []string{"my_group/my_project"},
249+ expectedAuthz: true,
235− expectedValue: []string{"group:foo", "group:bar", "project:my_group/my_project"},
250+ expectedGroups: []string{"foo", "bar", "project:my_group/my_project"},
236− projects: []string{"my_group/my_project"},
251+ expectedScope: "openid email read_api",
237 }),
252 }),
238 Entry("project membership invalid on group project, insufficient access level level", entitiesTableInput{
253 Entry("project membership invalid on group project, insufficient access level level", entitiesTableInput{
239− expectedValue: []string{"group:foo", "group:bar"},
240− projects: []string{"my_group/my_project=40"},
254+ allowedProjects: []string{"my_group/my_project=40"},
255+ expectedAuthz: false,
256+ expectedGroups: []string{"foo", "bar"},
257+ expectedScope: "openid email read_api",
241 }),
258 }),
242 Entry("project membership invalid on group project, no access at all", entitiesTableInput{
259 Entry("project membership invalid on group project, no access at all", entitiesTableInput{
243− expectedValue: []string{"group:foo", "group:bar"},
244− projects: []string{"no_access_group/no_access_project=30"},
260+ allowedProjects: []string{"no_access_group/no_access_project=30"},
261+ expectedAuthz: false,
262+ expectedGroups: []string{"foo", "bar"},
263+ expectedScope: "openid email read_api",
245 }),
264 }),
246 Entry("project membership valid on personnal project", entitiesTableInput{
265 Entry("project membership valid on personnal project", entitiesTableInput{
266+ allowedProjects: []string{"my_profile/my_personal_project"},
267+ scope: "openid email read_api profile",
268+ expectedAuthz: true,
247− expectedValue: []string{"group:foo", "group:bar", "project:my_profile/my_personal_project"},
269+ expectedGroups: []string{"foo", "bar", "project:my_profile/my_personal_project"},
248− projects: []string{"my_profile/my_personal_project"},
270+ expectedScope: "openid email read_api profile",
249 }),
271 }),
250 Entry("project membership invalid on personnal project, insufficient access level", entitiesTableInput{
272 Entry("project membership invalid on personnal project, insufficient access level", entitiesTableInput{
251− expectedValue: []string{"group:foo", "group:bar"},
252− projects: []string{"my_profile/my_personal_project=40"},
273+ allowedProjects: []string{"my_profile/my_personal_project=40"},
274+ expectedAuthz: false,
275+ expectedGroups: []string{"foo", "bar"},
276+ expectedScope: "openid email read_api",
253 }),
277 }),
254 Entry("project membership invalid", entitiesTableInput{
278 Entry("project membership invalid", entitiesTableInput{
255− expectedValue: []string{"group:foo", "group:bar"},
256− projects: []string{"my_group/my_bad_project"},
279+ allowedProjects: []string{"my_group/my_bad_project"},
280+ expectedAuthz: false,
281+ expectedGroups: []string{"foo", "bar"},
282+ expectedScope: "openid email read_api",
257 }),
283 }),
258 Entry("group membership valid", entitiesTableInput{
284 Entry("group membership valid", entitiesTableInput{
259− expectedValue: []string{"group:foo", "group:bar"},
285+ allowedGroups: []string{"foo"},
260− groups: []string{"foo"},
286+ expectedGroups: []string{"foo", "bar"},
287+ expectedAuthz: true,
288+ expectedScope: "openid email",
261 }),
289 }),
262 Entry("groups and projects", entitiesTableInput{
290 Entry("groups and projects", entitiesTableInput{
263− expectedValue: []string{"group:foo", "group:bar", "project:my_group/my_project", "project:my_profile/my_personal_project"},
264− groups: []string{"foo", "baz"},
291+ allowedGroups: []string{"foo", "baz"},
265− projects: []string{"my_group/my_project", "my_profile/my_personal_project"},
292+ allowedProjects: []string{"my_group/my_project", "my_profile/my_personal_project"},
293+ expectedAuthz: true,
294+ expectedGroups: []string{"foo", "bar", "project:my_group/my_project", "project:my_profile/my_personal_project"},
295+ expectedScope: "openid email read_api",
266 }),
296 }),
267 Entry("archived projects", entitiesTableInput{
297 Entry("archived projects", entitiesTableInput{
268− expectedValue: []string{"group:foo", "group:bar"},
298+ allowedProjects: []string{"my_group/my_archived_project"},
269− groups: []string{},
299+ expectedAuthz: false,
270− projects: []string{"my_group/my_archived_project"},
300+ expectedGroups: []string{"foo", "bar"},
271− }),
301+ expectedScope: "openid email read_api",
272− )
273−
274− })
275−
276− Context("when generating group list from multiple kind", func() {
277− type entitiesTableInput struct {
278− projects []string
279− groups []string
280− }
281−
282− DescribeTable("should prefix entities with group kind", func(in entitiesTableInput) {
283− p.Groups = in.groups
284− err := p.AddProjects(in.projects)
285− Expect(err).To(BeNil())
286−
287− all := p.PrefixAllowedGroups()
288−
289− Expect(len(all)).To(Equal(len(in.projects) + len(in.groups)))
290− },
291− Entry("simple test case", entitiesTableInput{
292− projects: []string{"my_group/my_project", "my_group/my_other_project"},
293− groups: []string{"mygroup", "myothergroup"},
294− }),
295− Entry("projects only", entitiesTableInput{
296− projects: []string{"my_group/my_project", "my_group/my_other_project"},
297− groups: []string{},
298 }),
302 }),
299− Entry("groups only", entitiesTableInput{
303+ Entry("invalid project format", entitiesTableInput{
300− projects: []string{},
304+ allowedProjects: []string{"my_group/my_invalid_project=123"},
301− groups: []string{"mygroup", "myothergroup"},
305+ expectedError: errors.New("invalid gitlab project access level specified (my_group/my_invalid_project)"),
306+ expectedScope: "openid email read_api",
302 }),
307 }),
303 )
308 )
304 })
309 })