From f47ab27702b9eff068425ca406a505817a7cadfd Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Fri, 23 Jan 2015 21:51:37 +0300 Subject: [PATCH] Gitlab oauth login --- README.md | 4 ++ packaging/root/etc/drone/drone.toml | 2 + plugin/remote/gitlab/gitlab.go | 57 ++++++++++++++++++----- plugin/remote/gitlab/gitlab_test.go | 32 ++++++++----- plugin/remote/gitlab/helper.go | 12 ++++- plugin/remote/gitlab/register.go | 5 ++ plugin/remote/gitlab/testdata/testdata.go | 31 ++++++++++++ server/app/views/login.html | 5 +- server/app/views/login_gitlab.html | 24 ---------- 9 files changed, 120 insertions(+), 52 deletions(-) delete mode 100644 server/app/views/login_gitlab.html diff --git a/README.md b/README.md index 3cd213cb..d3b0437a 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,8 @@ secret="" [gitlab] url="" +client="" +secret="" skip_verify=false [gogs] @@ -178,6 +180,8 @@ export DRONE_BITBUCKET_SECRET="" # gitlab configuration export DRONE_GITLAB_URL="" +export DRONE_GITLAB_CLIENT="" +export DRONE_GITLAB_SECRET="" export DRONE_GITLAB_SKIP_VERIFY=false # email configuration diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml index 084db1d1..b5ec9c3c 100644 --- a/packaging/root/etc/drone/drone.toml +++ b/packaging/root/etc/drone/drone.toml @@ -44,6 +44,8 @@ datasource="/var/lib/drone/drone.sqlite" # [gitlab] # url="" +# client="" +# secret="" # skip_verify=false # open=false diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 73f5bf9e..ea1dce51 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -1,12 +1,15 @@ package gitlab import ( + "fmt" "io/ioutil" "net/http" "net/url" "strconv" "github.com/Bugagazavr/go-gitlab-client" + "github.com/drone/drone/plugin/remote/github/oauth" + "github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/model" ) @@ -14,34 +17,66 @@ type Gitlab struct { url string SkipVerify bool Open bool + Client string + Secret string } -func New(url string, skipVerify, open bool) *Gitlab { +func New(url string, skipVerify, open bool, client, secret string) *Gitlab { return &Gitlab{ url: url, SkipVerify: skipVerify, Open: open, + Client: client, + Secret: secret, } } // Authorize handles authentication with thrid party remote systems, // such as github or bitbucket, and returns user data. func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { - var username = req.FormValue("username") - var password = req.FormValue("password") + var config = &oauth.Config{ + ClientId: r.Client, + ClientSecret: r.Secret, + Scope: "api", + AuthURL: fmt.Sprintf("%s/oauth/authorize", r.url), + TokenURL: fmt.Sprintf("%s/oauth/token", r.url), + RedirectURL: fmt.Sprintf("%s/api/auth/%s", httputil.GetURL(req), r.GetKind()), + } - var client = NewClient(r.url, "", r.SkipVerify) - var session, err = client.GetSession(username, password) + var code = req.FormValue("code") + var state = req.FormValue("state") + + if len(code) == 0 { + var random = GetRandom() + httputil.SetCookie(res, req, "gitlab_state", random) + http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) + return nil, nil + } + + cookieState := httputil.GetCookie(req, "gitlab_state") + httputil.DelCookie(res, req, "gitlab_state") + if cookieState != state { + return nil, fmt.Errorf("Error matching state in OAuth2 redirect") + } + + var trans = &oauth.Transport{Config: config} + var token, err = trans.Exchange(code) if err != nil { - return nil, err + return nil, fmt.Errorf("Error exchanging token. %s", err) + } + + var client = NewClient(r.url, token.AccessToken, r.SkipVerify) + + var user, errr = client.CurrentUser() + if errr != nil { + return nil, fmt.Errorf("Error retrieving current user. %s", errr) } var login = new(model.Login) - login.ID = int64(session.Id) - login.Access = session.PrivateToken - login.Login = session.UserName - login.Name = session.Name - login.Email = session.Email + login.ID = int64(user.Id) + login.Access = token.AccessToken + login.Login = user.Username + login.Email = user.Email return login, nil } diff --git a/plugin/remote/gitlab/gitlab_test.go b/plugin/remote/gitlab/gitlab_test.go index 81d0d4e5..37333a86 100644 --- a/plugin/remote/gitlab/gitlab_test.go +++ b/plugin/remote/gitlab/gitlab_test.go @@ -1,7 +1,9 @@ package gitlab import ( + "fmt" "net/http" + "net/http/httptest" "testing" "github.com/drone/drone/plugin/remote/gitlab/testdata" @@ -14,7 +16,7 @@ func Test_Github(t *testing.T) { var server = testdata.NewServer() defer server.Close() - var gitlab = New(server.URL, false, false) + var gitlab = New(server.URL, false, false, "", "") var user = model.User{ Access: "e3b0c44298fc1c149afbf4c8996fb", } @@ -31,17 +33,6 @@ func Test_Github(t *testing.T) { g := goblin.Goblin(t) g.Describe("Gitlab Plugin", func() { - g.It("Should authorize user", func() { - var req, _ = http.NewRequest("GET", "/login/gitlab", nil) - var login, err = gitlab.Authorize(nil, req) - g.Assert(err == nil).IsTrue() - g.Assert(login.Email).Equal("john@example.com") - g.Assert(login.Name).Equal("John Smith") - g.Assert(login.Login).Equal("john_smith") - g.Assert(login.Access).Equal("dd34asd13as") - g.Assert(login.ID).Equal(int64(1)) - }) - g.It("Should get the repo list", func() { var repos, err = gitlab.GetRepos(&user) g.Assert(err == nil).IsTrue() @@ -55,6 +46,23 @@ func Test_Github(t *testing.T) { g.Assert(repos[0].Role.Read).Equal(true) g.Assert(repos[0].Role.Write).Equal(true) }) + + g.Describe("Authorize", func() { + var resp = httptest.NewRecorder() + var state = "validstate" + var req, _ = http.NewRequest( + "GET", + fmt.Sprintf("%s/?code=sekret&state=%s", server.URL, state), + nil, + ) + req.AddCookie(&http.Cookie{Name: "gitlab_state", Value: state}) + + g.It("Should authorize a valid user", func() { + var login, err = gitlab.Authorize(resp, req) + g.Assert(err == nil).IsTrue() + g.Assert(login == nil).IsFalse() + }) + }) /* g.It("Should get the build script", func() { var script, err = github.GetScript(&user, &repo, &commit) diff --git a/plugin/remote/gitlab/helper.go b/plugin/remote/gitlab/helper.go index c497c369..02ba00cf 100644 --- a/plugin/remote/gitlab/helper.go +++ b/plugin/remote/gitlab/helper.go @@ -1,16 +1,20 @@ package gitlab import ( + "encoding/base32" "fmt" "net/url" "github.com/Bugagazavr/go-gitlab-client" + "github.com/gorilla/securecookie" ) // NewClient is a helper function that returns a new GitHub // client using the provided OAuth token. func NewClient(uri, token string, skipVerify bool) *gogitlab.Gitlab { - return gogitlab.NewGitlabCert(uri, "/api/v3", token, skipVerify) + client := gogitlab.NewGitlabCert(uri, "/api/v3", token, skipVerify) + client.Bearer = true + return client } // IsRead is a helper function that returns true if the @@ -76,3 +80,9 @@ func GetKeyTitle(rawurl string) (string, error) { func ns(owner, name string) string { return fmt.Sprintf("%s%%2F%s", owner, name) } + +// GetRandom is a helper function that generates a 32-bit random +// key, base32 encoded as a string value. +func GetRandom() string { + return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) +} diff --git a/plugin/remote/gitlab/register.go b/plugin/remote/gitlab/register.go index ebd45a8b..4b6aecee 100644 --- a/plugin/remote/gitlab/register.go +++ b/plugin/remote/gitlab/register.go @@ -9,6 +9,9 @@ var ( gitlabURL = config.String("gitlab-url", "") gitlabSkipVerify = config.Bool("gitlab-skip-verify", false) gitlabOpen = config.Bool("gitlab-open", false) + + gitlabClient = config.String("gitlab-client", "") + gitlabSecret = config.String("gitlab-secret", "") ) // Registers the Gitlab plugin using the default @@ -23,6 +26,8 @@ func Register() { *gitlabURL, *gitlabSkipVerify, *gitlabOpen, + *gitlabClient, + *gitlabSecret, ), ) } diff --git a/plugin/remote/gitlab/testdata/testdata.go b/plugin/remote/gitlab/testdata/testdata.go index 2e1f3853..cb727a87 100644 --- a/plugin/remote/gitlab/testdata/testdata.go +++ b/plugin/remote/gitlab/testdata/testdata.go @@ -27,6 +27,12 @@ func NewServer() *httptest.Server { case "/api/v3/session": w.Write(sessionPayload) return + case "/oauth/token": + w.Write(accessTokenPayload) + return + case "/api/v3/user": + w.Write(currentUserPayload) + return } // else return a 404 @@ -231,3 +237,28 @@ var droneYamlPayload = []byte(` "content": "aW1hZ2U6IGdv" } `) + +var accessTokenPayload = []byte(`access_token=sekret&scope=api&token_type=bearer`) + +var currentUserPayload = []byte(` +{ + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "private_token": "dd34asd13as", + "state": "active", + "created_at": "2012-05-23T08:00:58Z", + "bio": null, + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "theme_id": 1, + "color_scheme_id": 2, + "is_admin": false, + "can_create_group": true, + "can_create_project": true, + "projects_limit": 100 +} +`) diff --git a/server/app/views/login.html b/server/app/views/login.html index 4de2fd9d..6d899aba 100644 --- a/server/app/views/login.html +++ b/server/app/views/login.html @@ -11,10 +11,7 @@ minor modifications to the style that only apply to this view