1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-12-05 23:08:20 +02:00

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:
Mathieu Lecarme
2020-12-05 19:57:33 +01:00
committed by GitHub
parent 5117f2314f
commit d67d6e3152
8 changed files with 437 additions and 111 deletions

View File

@@ -2,13 +2,15 @@ package providers
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"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 {
@@ -39,100 +41,241 @@ func testGitLabBackend() *httptest.Server {
"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"
return httptest.NewServer(http.HandlerFunc(
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 {
w.WriteHeader(200)
w.Write([]byte(userInfo))
} else {
w.WriteHeader(401)
}
} else {
case "/api/v4/projects/my_group/my_project":
if r.Header["Authorization"][0] == authHeader {
w.WriteHeader(200)
w.Write([]byte(projectInfo))
} 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)
}
}))
}
func TestGitLabProviderBadToken(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
var _ = Describe("Gitlab Provider Tests", func() {
var p *GitLabProvider
var b *httptest.Server
bURL, _ := url.Parse(b.URL)
p := testGitLabProvider(bURL.Host)
BeforeEach(func() {
b = testGitLabBackend()
session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
assert.Error(t, err)
}
bURL, err := url.Parse(b.URL)
Expect(err).To(BeNil())
func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
p = testGitLabProvider(bURL.Host)
})
bURL, _ := url.Parse(b.URL)
p := testGitLabProvider(bURL.Host)
AfterEach(func() {
b.Close()
})
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
assert.Error(t, err)
}
Context("with bad token", func() {
It("should trigger an error", func() {
p.AllowUnverifiedEmail = false
session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
Expect(err).To(MatchError(errors.New("failed to retrieve user info: error getting user info: unexpected status \"401\": ")))
})
})
func TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
Context("when filtering on email", func() {
type emailsTableInput struct {
expectedError error
expectedValue string
allowUnverifiedEmail bool
}
bURL, _ := url.Parse(b.URL)
p := testGitLabProvider(bURL.Host)
p.AllowUnverifiedEmail = true
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)
assert.NoError(t, err)
assert.Equal(t, "foo@bar.com", session.Email)
}
err := p.EnrichSession(context.Background(), session)
func TestGitLabProviderUsername(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
if in.expectedError != nil {
Expect(err).To(MatchError(err))
} 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)
p := testGitLabProvider(bURL.Host)
p.AllowUnverifiedEmail = true
Context("when filtering on gitlab entities (groups and projects)", func() {
type entitiesTableInput struct {
expectedValue []string
projects []string
groups []string
}
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
assert.NoError(t, err)
assert.Equal(t, "FooBar", session.User)
}
DescribeTable("should return expected results",
func(in entitiesTableInput) {
p.AllowUnverifiedEmail = true
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
func TestGitLabProviderGroupMembershipValid(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
err := p.AddProjects(in.projects)
Expect(err).To(BeNil())
p.SetProjectScope()
bURL, _ := url.Parse(b.URL)
p := testGitLabProvider(bURL.Host)
p.AllowUnverifiedEmail = true
p.Groups = []string{"foo"}
if len(in.groups) > 0 {
p.Groups = in.groups
}
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
assert.NoError(t, err)
assert.Equal(t, "FooBar", session.User)
}
err = p.EnrichSession(context.Background(), session)
func TestGitLabProviderGroupMembershipMissing(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
Expect(err).To(BeNil())
Expect(session.Groups).To(Equal(in.expectedValue))
},
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{"baz"}
})
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
err := p.EnrichSession(context.Background(), session)
assert.Error(t, err)
}
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"},
}),
)
})
})