2015-10-04 04:50:11 +00:00
|
|
|
package bitbucket
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/drone/drone/model"
|
2016-04-29 19:39:56 +00:00
|
|
|
"github.com/drone/drone/remote"
|
|
|
|
"github.com/drone/drone/remote/bitbucket/internal"
|
2015-10-04 04:50:11 +00:00
|
|
|
"github.com/drone/drone/shared/httputil"
|
|
|
|
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
"golang.org/x/oauth2/bitbucket"
|
|
|
|
)
|
|
|
|
|
2016-04-30 08:00:39 +00:00
|
|
|
// Bitbucket Server endpoint.
|
|
|
|
const Endpoint = "https://api.bitbucket.org"
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
type config struct {
|
2016-04-30 08:00:39 +00:00
|
|
|
URL string
|
2015-10-04 04:50:11 +00:00
|
|
|
Client string
|
|
|
|
Secret string
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
// New returns a new remote Configuration for integrating with the Bitbucket
|
|
|
|
// repository hosting service at https://bitbucket.org
|
|
|
|
func New(client, secret string) remote.Remote {
|
|
|
|
return &config{
|
2016-04-30 08:00:39 +00:00
|
|
|
URL: Endpoint,
|
2016-04-29 19:39:56 +00:00
|
|
|
Client: client,
|
|
|
|
Secret: secret,
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
// helper function to return the bitbucket oauth2 client
|
|
|
|
func (c *config) newClient(u *model.User) *internal.Client {
|
|
|
|
return internal.NewClientToken(
|
2016-04-30 08:00:39 +00:00
|
|
|
c.URL,
|
2016-04-29 19:39:56 +00:00
|
|
|
c.Client,
|
|
|
|
c.Secret,
|
|
|
|
&oauth2.Token{
|
|
|
|
AccessToken: u.Token,
|
|
|
|
RefreshToken: u.Secret,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2015-10-04 04:50:11 +00:00
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
2015-10-04 04:50:11 +00:00
|
|
|
config := &oauth2.Config{
|
2016-04-29 19:39:56 +00:00
|
|
|
ClientID: c.Client,
|
|
|
|
ClientSecret: c.Secret,
|
2015-10-04 04:50:11 +00:00
|
|
|
Endpoint: bitbucket.Endpoint,
|
|
|
|
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
|
|
|
|
}
|
|
|
|
|
|
|
|
var code = req.FormValue("code")
|
|
|
|
if len(code) == 0 {
|
|
|
|
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
2016-04-29 19:39:56 +00:00
|
|
|
return nil, nil
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var token, err = config.Exchange(oauth2.NoContext, code)
|
|
|
|
if err != nil {
|
2016-04-29 19:39:56 +00:00
|
|
|
return nil, err
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-30 08:00:39 +00:00
|
|
|
client := internal.NewClient(c.URL, config.Client(oauth2.NoContext, token))
|
2015-10-04 04:50:11 +00:00
|
|
|
curr, err := client.FindCurrent()
|
|
|
|
if err != nil {
|
2016-04-29 19:39:56 +00:00
|
|
|
return nil, err
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
return convertUser(curr, token), nil
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Auth(token, secret string) (string, error) {
|
|
|
|
client := internal.NewClientToken(
|
2016-04-30 08:00:39 +00:00
|
|
|
c.URL,
|
2016-04-29 19:39:56 +00:00
|
|
|
c.Client,
|
|
|
|
c.Secret,
|
|
|
|
&oauth2.Token{
|
|
|
|
AccessToken: token,
|
|
|
|
RefreshToken: secret,
|
|
|
|
},
|
|
|
|
)
|
2015-10-04 04:50:11 +00:00
|
|
|
user, err := client.FindCurrent()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return user.Login, nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Refresh(user *model.User) (bool, error) {
|
2015-10-04 04:50:11 +00:00
|
|
|
config := &oauth2.Config{
|
2016-04-29 19:39:56 +00:00
|
|
|
ClientID: c.Client,
|
|
|
|
ClientSecret: c.Secret,
|
2015-10-04 04:50:11 +00:00
|
|
|
Endpoint: bitbucket.Endpoint,
|
|
|
|
}
|
|
|
|
|
|
|
|
// creates a token source with just the refresh token.
|
|
|
|
// this will ensure an access token is automatically
|
|
|
|
// requested.
|
|
|
|
source := config.TokenSource(
|
|
|
|
oauth2.NoContext, &oauth2.Token{RefreshToken: user.Secret})
|
|
|
|
|
|
|
|
// requesting the token automatically refreshes and
|
|
|
|
// returns a new access token.
|
|
|
|
token, err := source.Token()
|
|
|
|
if err != nil || len(token.AccessToken) == 0 {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the user to include tne new access token
|
|
|
|
user.Token = token.AccessToken
|
|
|
|
user.Secret = token.RefreshToken
|
2015-10-05 00:40:27 +00:00
|
|
|
user.Expiry = token.Expiry.UTC().Unix()
|
2015-10-04 04:50:11 +00:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Teams(u *model.User) ([]*model.Team, error) {
|
|
|
|
opts := &internal.ListTeamOpts{
|
|
|
|
PageLen: 100,
|
|
|
|
Role: "member",
|
|
|
|
}
|
|
|
|
resp, err := c.newClient(u).ListTeams(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return convertTeamList(resp.Values), nil
|
|
|
|
}
|
2015-10-04 04:50:11 +00:00
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
|
|
|
repo, err := c.newClient(u).FindRepo(owner, name)
|
2015-10-04 04:50:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return convertRepo(repo), nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
|
|
|
|
client := c.newClient(u)
|
|
|
|
|
2015-10-04 04:50:11 +00:00
|
|
|
var repos []*model.RepoLite
|
2015-10-04 05:23:37 +00:00
|
|
|
|
2015-10-05 00:40:27 +00:00
|
|
|
// gets a list of all accounts to query, including the
|
|
|
|
// user's account and all team accounts.
|
|
|
|
logins := []string{u.Login}
|
2016-04-29 19:39:56 +00:00
|
|
|
resp, err := client.ListTeams(&internal.ListTeamOpts{PageLen: 100, Role: "member"})
|
2015-10-05 00:40:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return repos, err
|
|
|
|
}
|
|
|
|
for _, team := range resp.Values {
|
|
|
|
logins = append(logins, team.Login)
|
|
|
|
}
|
2015-10-04 04:50:11 +00:00
|
|
|
|
2015-10-05 00:40:27 +00:00
|
|
|
// for each account, get the list of repos
|
|
|
|
for _, login := range logins {
|
|
|
|
repos_, err := client.ListReposAll(login)
|
2015-10-04 04:50:11 +00:00
|
|
|
if err != nil {
|
2015-10-05 00:40:27 +00:00
|
|
|
return repos, err
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
2015-10-05 00:40:27 +00:00
|
|
|
for _, repo := range repos_ {
|
|
|
|
repos = append(repos, convertRepoLite(repo))
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return repos, nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
|
|
|
client := c.newClient(u)
|
2015-10-04 04:50:11 +00:00
|
|
|
|
|
|
|
perms := new(model.Perm)
|
|
|
|
_, err := client.FindRepo(owner, name)
|
|
|
|
if err != nil {
|
|
|
|
return perms, err
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
// if we've gotten this far we know that the user at least has read access
|
|
|
|
// to the repository.
|
2015-10-04 04:50:11 +00:00
|
|
|
perms.Pull = true
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
// if the user has access to the repository hooks we can deduce that the user
|
|
|
|
// has push and admin access.
|
|
|
|
_, err = client.ListHooks(owner, name, &internal.ListOpts{})
|
2015-10-04 04:50:11 +00:00
|
|
|
if err == nil {
|
|
|
|
perms.Push = true
|
|
|
|
perms.Admin = true
|
|
|
|
}
|
|
|
|
return perms, nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
|
|
|
config, err := c.newClient(u).FindSource(r.Owner, r.Name, b.Commit, f)
|
2015-10-04 05:23:37 +00:00
|
|
|
if err != nil {
|
2016-03-22 10:34:33 +00:00
|
|
|
return nil, err
|
2015-10-04 05:23:37 +00:00
|
|
|
}
|
2016-03-22 10:34:33 +00:00
|
|
|
return []byte(config.Data), err
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
|
|
|
status := internal.BuildStatus{
|
2016-04-30 08:00:39 +00:00
|
|
|
State: convertStatus(b.Status),
|
|
|
|
Desc: convertDesc(b.Status),
|
2016-03-22 10:34:33 +00:00
|
|
|
Key: "Drone",
|
|
|
|
Url: link,
|
2015-11-21 05:22:28 +00:00
|
|
|
}
|
2016-04-29 19:39:56 +00:00
|
|
|
return c.newClient(u).CreateStatus(r.Owner, r.Name, b.Commit, &status)
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
2015-10-05 00:40:27 +00:00
|
|
|
return &model.Netrc{
|
|
|
|
Machine: "bitbucket.org",
|
|
|
|
Login: "x-token-auth",
|
|
|
|
Password: u.Token,
|
|
|
|
}, nil
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
|
|
|
rawurl, err := url.Parse(link)
|
2015-10-05 00:40:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
// deletes any previously created hooks
|
2016-04-30 08:00:39 +00:00
|
|
|
c.Deactivate(u, r, link)
|
2015-10-05 00:40:27 +00:00
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
return c.newClient(u).CreateHook(r.Owner, r.Name, &internal.Hook{
|
2015-10-05 00:40:27 +00:00
|
|
|
Active: true,
|
2016-04-29 19:39:56 +00:00
|
|
|
Desc: rawurl.Host,
|
2015-10-05 00:40:27 +00:00
|
|
|
Events: []string{"repo:push"},
|
|
|
|
Url: link,
|
|
|
|
})
|
2015-10-04 04:50:11 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Deactivate(u *model.User, r *model.Repo, link string) error {
|
|
|
|
client := c.newClient(u)
|
2015-10-05 00:40:27 +00:00
|
|
|
|
|
|
|
linkurl, err := url.Parse(link)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-29 19:39:56 +00:00
|
|
|
hooks, err := client.ListHooks(r.Owner, r.Name, &internal.ListOpts{})
|
|
|
|
if err != nil {
|
2016-04-30 08:00:39 +00:00
|
|
|
return err
|
2016-04-29 19:39:56 +00:00
|
|
|
}
|
|
|
|
|
2015-10-05 00:40:27 +00:00
|
|
|
for _, hook := range hooks.Values {
|
|
|
|
hookurl, err := url.Parse(hook.Url)
|
2016-04-30 08:00:39 +00:00
|
|
|
if err == nil && hookurl.Host == linkurl.Host {
|
|
|
|
return client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
2015-10-05 00:40:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-04 04:50:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-30 08:00:39 +00:00
|
|
|
// Hook parses the incoming Bitbucket hook and returns the Repository and
|
|
|
|
// Build details. If the hook is unsupported nil values are returned and the
|
|
|
|
// hook should be skipped.
|
2016-04-29 19:39:56 +00:00
|
|
|
func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
2016-04-30 08:00:39 +00:00
|
|
|
return parseHook(r)
|
2015-10-06 06:17:59 +00:00
|
|
|
}
|