Gitlab oauth login

This commit is contained in:
Kirill Zaitsev 2015-01-23 21:51:37 +03:00
parent 44c5ca2b55
commit f47ab27702
9 changed files with 120 additions and 52 deletions

View file

@ -116,6 +116,8 @@ secret=""
[gitlab] [gitlab]
url="" url=""
client=""
secret=""
skip_verify=false skip_verify=false
[gogs] [gogs]
@ -178,6 +180,8 @@ export DRONE_BITBUCKET_SECRET=""
# gitlab configuration # gitlab configuration
export DRONE_GITLAB_URL="" export DRONE_GITLAB_URL=""
export DRONE_GITLAB_CLIENT=""
export DRONE_GITLAB_SECRET=""
export DRONE_GITLAB_SKIP_VERIFY=false export DRONE_GITLAB_SKIP_VERIFY=false
# email configuration # email configuration

View file

@ -44,6 +44,8 @@ datasource="/var/lib/drone/drone.sqlite"
# [gitlab] # [gitlab]
# url="" # url=""
# client=""
# secret=""
# skip_verify=false # skip_verify=false
# open=false # open=false

View file

@ -1,12 +1,15 @@
package gitlab package gitlab
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"github.com/Bugagazavr/go-gitlab-client" "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" "github.com/drone/drone/shared/model"
) )
@ -14,34 +17,66 @@ type Gitlab struct {
url string url string
SkipVerify bool SkipVerify bool
Open 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{ return &Gitlab{
url: url, url: url,
SkipVerify: skipVerify, SkipVerify: skipVerify,
Open: open, Open: open,
Client: client,
Secret: secret,
} }
} }
// Authorize handles authentication with thrid party remote systems, // Authorize handles authentication with thrid party remote systems,
// such as github or bitbucket, and returns user data. // such as github or bitbucket, and returns user data.
func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
var username = req.FormValue("username") var config = &oauth.Config{
var password = req.FormValue("password") 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 code = req.FormValue("code")
var session, err = client.GetSession(username, password) 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 { 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) var login = new(model.Login)
login.ID = int64(session.Id) login.ID = int64(user.Id)
login.Access = session.PrivateToken login.Access = token.AccessToken
login.Login = session.UserName login.Login = user.Username
login.Name = session.Name login.Email = user.Email
login.Email = session.Email
return login, nil return login, nil
} }

View file

@ -1,7 +1,9 @@
package gitlab package gitlab
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
"github.com/drone/drone/plugin/remote/gitlab/testdata" "github.com/drone/drone/plugin/remote/gitlab/testdata"
@ -14,7 +16,7 @@ func Test_Github(t *testing.T) {
var server = testdata.NewServer() var server = testdata.NewServer()
defer server.Close() defer server.Close()
var gitlab = New(server.URL, false, false) var gitlab = New(server.URL, false, false, "", "")
var user = model.User{ var user = model.User{
Access: "e3b0c44298fc1c149afbf4c8996fb", Access: "e3b0c44298fc1c149afbf4c8996fb",
} }
@ -31,17 +33,6 @@ func Test_Github(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Gitlab Plugin", func() { 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() { g.It("Should get the repo list", func() {
var repos, err = gitlab.GetRepos(&user) var repos, err = gitlab.GetRepos(&user)
g.Assert(err == nil).IsTrue() 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.Read).Equal(true)
g.Assert(repos[0].Role.Write).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() { g.It("Should get the build script", func() {
var script, err = github.GetScript(&user, &repo, &commit) var script, err = github.GetScript(&user, &repo, &commit)

View file

@ -1,16 +1,20 @@
package gitlab package gitlab
import ( import (
"encoding/base32"
"fmt" "fmt"
"net/url" "net/url"
"github.com/Bugagazavr/go-gitlab-client" "github.com/Bugagazavr/go-gitlab-client"
"github.com/gorilla/securecookie"
) )
// NewClient is a helper function that returns a new GitHub // NewClient is a helper function that returns a new GitHub
// client using the provided OAuth token. // client using the provided OAuth token.
func NewClient(uri, token string, skipVerify bool) *gogitlab.Gitlab { 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 // 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 { func ns(owner, name string) string {
return fmt.Sprintf("%s%%2F%s", owner, name) 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))
}

View file

@ -9,6 +9,9 @@ var (
gitlabURL = config.String("gitlab-url", "") gitlabURL = config.String("gitlab-url", "")
gitlabSkipVerify = config.Bool("gitlab-skip-verify", false) gitlabSkipVerify = config.Bool("gitlab-skip-verify", false)
gitlabOpen = config.Bool("gitlab-open", false) gitlabOpen = config.Bool("gitlab-open", false)
gitlabClient = config.String("gitlab-client", "")
gitlabSecret = config.String("gitlab-secret", "")
) )
// Registers the Gitlab plugin using the default // Registers the Gitlab plugin using the default
@ -23,6 +26,8 @@ func Register() {
*gitlabURL, *gitlabURL,
*gitlabSkipVerify, *gitlabSkipVerify,
*gitlabOpen, *gitlabOpen,
*gitlabClient,
*gitlabSecret,
), ),
) )
} }

View file

@ -27,6 +27,12 @@ func NewServer() *httptest.Server {
case "/api/v3/session": case "/api/v3/session":
w.Write(sessionPayload) w.Write(sessionPayload)
return return
case "/oauth/token":
w.Write(accessTokenPayload)
return
case "/api/v3/user":
w.Write(currentUserPayload)
return
} }
// else return a 404 // else return a 404
@ -231,3 +237,28 @@ var droneYamlPayload = []byte(`
"content": "aW1hZ2U6IGdv" "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
}
`)

View file

@ -11,10 +11,7 @@ minor modifications to the style that only apply to this view
<article id="loginpage"> <article id="loginpage">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes"> <div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes">
<a ng-href="/api/auth/{{ remote.type }}" target="_self" ng-if="remote.type != 'gitlab.com' && remote.type != 'gogs' "> <a ng-href="/api/auth/{{ remote.type }}" target="_self" ng-if="remote.type != 'gogs' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a>
<a ng-href="/gitlab" ng-if="remote.type == 'gitlab.com' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }} <i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a> </a>
<a ng-href="/gogs" ng-if="remote.type == 'gogs' "> <a ng-href="/gogs" ng-if="remote.type == 'gogs' ">

View file

@ -1,24 +0,0 @@
<!--
minor modifications to the style that only apply to this view
-->
<style>
#container { padding-top: 155px; }
#header { height: 150px; }
#header .user { display:none; }
#header .brand { margin-top:55px ; }
</style>
<article id="loginpage">
<form class="pure-g" method="POST" action="/api/auth/gitlab.com">
<div class="pure-u-1">
<input type="text" name="username" placeholder="Email Address" />
</div>
<div class="pure-u-1">
<input type="password" name="password" placeholder="Password" />
</div>
<div class="pure-u-1">
<input type="submit" value="Gitlab Login" />
</div>
</form>
</article>