diff --git a/README.md b/README.md index 13576ca..673f3dc 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Valid providers are : * [Google](#google-auth-provider) *default* * [Azure](#azure-auth-provider) * [GitHub](#github-auth-provider) +* [GitLab](#gitlab-auth-provider) * [LinkedIn](#linkedin-auth-provider) * [MyUSA](#myusa-auth-provider) @@ -103,6 +104,17 @@ If you are using github enterprise, make sure you set the following to the appro -validate-url="/user/emails" +### GitLab Auth Provider + +Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html) + +If you are using self-hosted GitLab, make sure you set the following to the appropriate URL: + + -login-url="/oauth/authorize" + -redeem-url="/oauth/token" + -validate-url="/api/v3/user" + + ### LinkedIn Auth Provider For LinkedIn, the registration steps are: diff --git a/providers/gitlab.go b/providers/gitlab.go new file mode 100644 index 0000000..708283a --- /dev/null +++ b/providers/gitlab.go @@ -0,0 +1,58 @@ +package providers + +import ( + "log" + "net/http" + "net/url" + + "github.com/bitly/oauth2_proxy/api" +) + +type GitLabProvider struct { + *ProviderData +} + +func NewGitLabProvider(p *ProviderData) *GitLabProvider { + p.ProviderName = "GitLab" + if p.LoginURL == nil || p.LoginURL.String() == "" { + p.LoginURL = &url.URL{ + Scheme: "https", + Host: "gitlab.com", + Path: "/oauth/authorize", + } + } + if p.RedeemURL == nil || p.RedeemURL.String() == "" { + p.RedeemURL = &url.URL{ + Scheme: "https", + Host: "gitlab.com", + Path: "/oauth/token", + } + } + if p.ValidateURL == nil || p.ValidateURL.String() == "" { + p.ValidateURL = &url.URL{ + Scheme: "https", + Host: "gitlab.com", + Path: "/api/v3/user", + } + } + if p.Scope == "" { + p.Scope = "api" + } + return &GitLabProvider{ProviderData: p} +} + +func (p *GitLabProvider) GetEmailAddress(s *SessionState) (string, error) { + + req, err := http.NewRequest("GET", + p.ValidateURL.String()+"?access_token="+s.AccessToken, nil) + if err != nil { + log.Printf("failed building request %s", err) + return "", err + } + json, err := api.Request(req) + if err != nil { + log.Printf("failed making request %s", err) + return "", err + } + return json.Get("email").String() +} diff --git a/providers/gitlab_test.go b/providers/gitlab_test.go new file mode 100644 index 0000000..3df001c --- /dev/null +++ b/providers/gitlab_test.go @@ -0,0 +1,128 @@ +package providers + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/bmizerany/assert" +) + +func testGitLabProvider(hostname string) *GitLabProvider { + p := NewGitLabProvider( + &ProviderData{ + ProviderName: "", + LoginURL: &url.URL{}, + RedeemURL: &url.URL{}, + ProfileURL: &url.URL{}, + ValidateURL: &url.URL{}, + Scope: ""}) + if hostname != "" { + updateURL(p.Data().LoginURL, hostname) + updateURL(p.Data().RedeemURL, hostname) + updateURL(p.Data().ProfileURL, hostname) + updateURL(p.Data().ValidateURL, hostname) + } + return p +} + +func testGitLabBackend(payload string) *httptest.Server { + path := "/api/v3/user" + query := "access_token=imaginary_access_token" + + return httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + url := r.URL + if url.Path != path || url.RawQuery != query { + w.WriteHeader(404) + } else { + w.WriteHeader(200) + w.Write([]byte(payload)) + } + })) +} + +func TestGitLabProviderDefaults(t *testing.T) { + p := testGitLabProvider("") + assert.NotEqual(t, nil, p) + assert.Equal(t, "GitLab", p.Data().ProviderName) + assert.Equal(t, "https://gitlab.com/oauth/authorize", + p.Data().LoginURL.String()) + assert.Equal(t, "https://gitlab.com/oauth/token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://gitlab.com/api/v3/user", + p.Data().ValidateURL.String()) + assert.Equal(t, "api", p.Data().Scope) +} + +func TestGitLabProviderOverrides(t *testing.T) { + p := NewGitLabProvider( + &ProviderData{ + LoginURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/auth"}, + RedeemURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/token"}, + ValidateURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/api/v3/user"}, + Scope: "profile"}) + assert.NotEqual(t, nil, p) + assert.Equal(t, "GitLab", p.Data().ProviderName) + assert.Equal(t, "https://example.com/oauth/auth", + p.Data().LoginURL.String()) + assert.Equal(t, "https://example.com/oauth/token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://example.com/api/v3/user", + p.Data().ValidateURL.String()) + assert.Equal(t, "profile", p.Data().Scope) +} + +func TestGitLabProviderGetEmailAddress(t *testing.T) { + b := testGitLabBackend("{\"email\": \"michael.bland@gsa.gov\"}") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testGitLabProvider(b_url.Host) + + session := &SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +// Note that trying to trigger the "failed building request" case is not +// practical, since the only way it can fail is if the URL fails to parse. +func TestGitLabProviderGetEmailAddressFailedRequest(t *testing.T) { + b := testGitLabBackend("unused payload") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testGitLabProvider(b_url.Host) + + // We'll trigger a request failure by using an unexpected access + // token. Alternatively, we could allow the parsing of the payload as + // JSON to fail. + session := &SessionState{AccessToken: "unexpected_access_token"} + email, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) + assert.Equal(t, "", email) +} + +func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { + b := testGitLabBackend("{\"foo\": \"bar\"}") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testGitLabProvider(b_url.Host) + + session := &SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) + assert.Equal(t, "", email) +} diff --git a/providers/providers.go b/providers/providers.go index db0fe13..010e633 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -26,6 +26,8 @@ func New(provider string, p *ProviderData) Provider { return NewGitHubProvider(p) case "azure": return NewAzureProvider(p) + case "gitlab": + return NewGitLabProvider(p) default: return NewGoogleProvider(p) }