Merge remote-tracking branch 'origin/master'

This commit is contained in:
Uchio KONDO 2015-01-26 15:51:09 +09:00
commit e4efc039c9
12 changed files with 236 additions and 63 deletions

View file

@ -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

View file

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

View file

@ -0,0 +1,105 @@
package flowdock
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
const (
ENDPOINT = "https://api.flowdock.com/v1/messages/team_inbox/"
)
var (
// Required default client settings
Token = ""
Source = ""
FromAddress = ""
// Optional default client settings
FromName = ""
ReplyTo = ""
Project = ""
Link = ""
Tags = []string{}
)
type Client struct {
// Required
Token string
Source string
FromAddress string
Subject string
Content string
// Optional
FromName string
ReplyTo string
Project string
Link string
Tags []string
}
func (c *Client) Inbox(subject, content string) error {
return send(c.Token, c.Source, c.FromAddress, subject, content, c.FromName, c.ReplyTo, c.Project, c.Link, c.Tags)
}
func Inbox(subject, content string) error {
return send(Token, Source, FromAddress, subject, content, FromName, ReplyTo, Project, Link, Tags)
}
func send(token, source, fromAddress, subject, content, fromName, replyTo, project, link string, tags []string) error {
// Required validation
if len(token) == 0 {
return fmt.Errorf(`"Token" is required`)
}
if len(source) == 0 {
return fmt.Errorf(`"Source" is required`)
}
if len(fromAddress) == 0 {
return fmt.Errorf(`"FromAddress" is required`)
}
if len(subject) == 0 {
return fmt.Errorf(`"Subject" is required`)
}
// Build payload
payload := map[string]interface{}{
"source": source,
"from_address": fromAddress,
"subject": subject,
"content": content,
}
if len(fromName) > 0 {
payload["from_name"] = fromName
}
if len(replyTo) > 0 {
payload["reply_to"] = replyTo
}
if len(project) > 0 {
payload["project"] = project
}
if len(link) > 0 {
payload["link"] = link
}
if len(tags) > 0 {
payload["tags"] = tags
}
jsonPayload, err := json.Marshal(payload)
if err != nil {
return err
}
// Send to Flowdock
resp, err := http.Post(ENDPOINT+token, "application/json", bytes.NewReader(jsonPayload))
defer resp.Body.Close()
if resp.StatusCode == 200 {
return nil
} else {
bodyBytes, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Unexpected response from Flowdock: %s %s", resp.Status, string(bodyBytes))
}
}

View file

@ -1,11 +1,10 @@
package notify
package flowdock
import (
"fmt"
"strings"
"github.com/drone/drone/shared/model"
"github.com/stvp/flowdock"
)
const (
@ -78,7 +77,7 @@ func (f *Flowdock) sendSuccess(context *model.Request) error {
// helper function to send Flowdock requests
func (f *Flowdock) send(fromAddress, subject, message string, tags []string) error {
c := flowdock.Client{Token: f.Token, Source: f.Source, FromName: "drone.io", FromAddress: fromAddress, Tags: tags}
c := Client{Token: f.Token, Source: f.Source, FromName: "drone.io", FromAddress: fromAddress, Tags: tags}
go c.Inbox(subject, message)
return nil
}

View file

@ -7,6 +7,7 @@ import (
"net/http"
"github.com/drone/drone/plugin/notify/email"
"github.com/drone/drone/plugin/notify/flowdock"
"github.com/drone/drone/plugin/notify/github"
"github.com/drone/drone/plugin/notify/irc"
"github.com/drone/drone/plugin/notify/katoim"
@ -22,14 +23,14 @@ type Sender interface {
// for notifying a user, or group of users,
// when their Build has completed.
type Notification struct {
Email *email.Email `yaml:"email,omitempty"`
Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
Irc *irc.IRC `yaml:"irc,omitempty"`
Slack *Slack `yaml:"slack,omitempty"`
Gitter *Gitter `yaml:"gitter,omitempty"`
Flowdock *Flowdock `yaml:"flowdock,omitempty"`
KatoIM *katoim.KatoIM `yaml:"katoim,omitempty"`
Email *email.Email `yaml:"email,omitempty"`
Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
Irc *irc.IRC `yaml:"irc,omitempty"`
Slack *Slack `yaml:"slack,omitempty"`
Gitter *Gitter `yaml:"gitter,omitempty"`
Flowdock *flowdock.Flowdock `yaml:"flowdock,omitempty"`
KatoIM *katoim.KatoIM `yaml:"katoim,omitempty"`
GitHub github.GitHub `yaml:"--"`
}

View file

@ -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
}

View file

@ -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)

View file

@ -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))
}

View file

@ -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,
),
)
}

View file

@ -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
}
`)

View file

@ -11,10 +11,7 @@ minor modifications to the style that only apply to this view
<article id="loginpage">
<div class="pure-g">
<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' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a>
<a ng-href="/gitlab" ng-if="remote.type == 'gitlab.com' ">
<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="/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>