mirror of
https://github.com/harness/drone.git
synced 2025-05-09 21:13:04 +08:00

...so that it's possible to enable or disable open registration on a per-remote basis. For example, the `DRONE_REGISTRATION_OPEN` environment variable now becomes `DRONE_GITHUB_OPEN` when using GitHub as a remote. The default for open registration in this commit is `false` (disabled), which matches the existing behaviour. This is useful if you need to support both public and private remotes, e.g. GitHub.com and GitHub Enterprise, where you trust all of the private users and want to allow open registration for those but would not want all GitHub.com users to run builds on your server. Tested with GitHub and GitLab.
278 lines
6.9 KiB
Go
278 lines
6.9 KiB
Go
package bitbucket
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/drone/drone/shared/httputil"
|
|
"github.com/drone/drone/shared/model"
|
|
"github.com/drone/go-bitbucket/bitbucket"
|
|
"github.com/drone/go-bitbucket/oauth1"
|
|
)
|
|
|
|
const (
|
|
DefaultAPI = "https://api.bitbucket.org/1.0"
|
|
DefaultURL = "https://bitbucket.org"
|
|
)
|
|
|
|
// parses an email address from string format
|
|
// `John Doe <john.doe@example.com>`
|
|
var emailRegexp = regexp.MustCompile("<(.*)>")
|
|
|
|
type Bitbucket struct {
|
|
URL string
|
|
API string
|
|
Client string
|
|
Secret string
|
|
Open bool
|
|
}
|
|
|
|
func New(url, api, client, secret string, open bool) *Bitbucket {
|
|
return &Bitbucket{
|
|
URL: url,
|
|
API: api,
|
|
Client: client,
|
|
Secret: secret,
|
|
Open: open,
|
|
}
|
|
}
|
|
|
|
func NewDefault(client, secret string, open bool) *Bitbucket {
|
|
return New(DefaultURL, DefaultAPI, client, secret, open)
|
|
}
|
|
|
|
// Authorize handles Bitbucket API Authorization
|
|
func (r *Bitbucket) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
|
|
consumer := oauth1.Consumer{
|
|
RequestTokenURL: "https://bitbucket.org/api/1.0/oauth/request_token/",
|
|
AuthorizationURL: "https://bitbucket.org/!api/1.0/oauth/authenticate",
|
|
AccessTokenURL: "https://bitbucket.org/api/1.0/oauth/access_token/",
|
|
CallbackURL: httputil.GetScheme(req) + "://" + httputil.GetHost(req) + "/api/auth/bitbucket.org",
|
|
ConsumerKey: r.Client,
|
|
ConsumerSecret: r.Secret,
|
|
}
|
|
|
|
// get the oauth verifier
|
|
verifier := req.FormValue("oauth_verifier")
|
|
if len(verifier) == 0 {
|
|
// Generate a Request Token
|
|
requestToken, err := consumer.RequestToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// add the request token as a signed cookie
|
|
httputil.SetCookie(res, req, "bitbucket_token", requestToken.Encode())
|
|
|
|
url, _ := consumer.AuthorizeRedirect(requestToken)
|
|
http.Redirect(res, req, url, http.StatusSeeOther)
|
|
return nil, nil
|
|
}
|
|
|
|
// remove bitbucket token data once before redirecting
|
|
// back to the application.
|
|
defer httputil.DelCookie(res, req, "bitbucket_token")
|
|
|
|
// get the tokens from the request
|
|
requestTokenStr := httputil.GetCookie(req, "bitbucket_token")
|
|
requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// exchange for an access token
|
|
accessToken, err := consumer.AuthorizeToken(requestToken, verifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create the Bitbucket client
|
|
client := bitbucket.New(
|
|
r.Client,
|
|
r.Secret,
|
|
accessToken.Token(),
|
|
accessToken.Secret(),
|
|
)
|
|
|
|
// get the currently authenticated Bitbucket User
|
|
user, err := client.Users.Current()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// put the user data in the common format
|
|
login := model.Login{
|
|
Login: user.User.Username,
|
|
Access: accessToken.Token(),
|
|
Secret: accessToken.Secret(),
|
|
Name: user.User.DisplayName,
|
|
}
|
|
|
|
email, _ := client.Emails.FindPrimary(user.User.Username)
|
|
if email != nil {
|
|
login.Email = email.Email
|
|
}
|
|
|
|
return &login, nil
|
|
}
|
|
|
|
// GetKind returns the internal identifier of this remote Bitbucket instane.
|
|
func (r *Bitbucket) GetKind() string {
|
|
return model.RemoteBitbucket
|
|
}
|
|
|
|
// GetHost returns the hostname of this remote Bitbucket instance.
|
|
func (r *Bitbucket) GetHost() string {
|
|
uri, _ := url.Parse(r.URL)
|
|
return uri.Host
|
|
}
|
|
|
|
// GetRepos fetches all repositories that the specified
|
|
// user has access to in the remote system.
|
|
func (r *Bitbucket) GetRepos(user *model.User) ([]*model.Repo, error) {
|
|
var repos []*model.Repo
|
|
var client = bitbucket.New(
|
|
r.Client,
|
|
r.Secret,
|
|
user.Access,
|
|
user.Secret,
|
|
)
|
|
var list, err = client.Repos.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var remote = r.GetKind()
|
|
var hostname = r.GetHost()
|
|
|
|
for _, item := range list {
|
|
// for now we only support git repos
|
|
if item.Scm != "git" {
|
|
continue
|
|
}
|
|
|
|
// these are the urls required to clone the repository
|
|
// TODO use the bitbucketurl.Host and bitbucketurl.Scheme instead of hardcoding
|
|
// so that we can support Stash.
|
|
var html = fmt.Sprintf("https://bitbucket.org/%s/%s", item.Owner, item.Name)
|
|
var clone = fmt.Sprintf("https://bitbucket.org/%s/%s.git", item.Owner, item.Name)
|
|
var ssh = fmt.Sprintf("git@bitbucket.org:%s/%s.git", item.Owner, item.Name)
|
|
|
|
var repo = model.Repo{
|
|
UserID: user.ID,
|
|
Remote: remote,
|
|
Host: hostname,
|
|
Owner: item.Owner,
|
|
Name: item.Name,
|
|
Private: item.Private,
|
|
URL: html,
|
|
CloneURL: clone,
|
|
GitURL: clone,
|
|
SSHURL: ssh,
|
|
Role: &model.Perm{
|
|
Admin: true,
|
|
Write: true,
|
|
Read: true,
|
|
},
|
|
}
|
|
|
|
if repo.Private {
|
|
repo.CloneURL = repo.SSHURL
|
|
}
|
|
|
|
repos = append(repos, &repo)
|
|
}
|
|
|
|
return repos, err
|
|
}
|
|
|
|
// GetScript fetches the build script (.drone.yml) from the remote
|
|
// repository and returns in string format.
|
|
func (r *Bitbucket) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
|
|
var client = bitbucket.New(
|
|
r.Client,
|
|
r.Secret,
|
|
user.Access,
|
|
user.Secret,
|
|
)
|
|
|
|
// get the yaml from the database
|
|
var raw, err = client.Sources.Find(repo.Owner, repo.Name, hook.Sha, ".drone.yml")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []byte(raw.Data), nil
|
|
}
|
|
|
|
// Activate activates a repository by adding a Post-commit hook and
|
|
// a Public Deploy key, if applicable.
|
|
func (r *Bitbucket) Activate(user *model.User, repo *model.Repo, link string) error {
|
|
var client = bitbucket.New(
|
|
r.Client,
|
|
r.Secret,
|
|
user.Access,
|
|
user.Secret,
|
|
)
|
|
|
|
// parse the hostname from the hook, and use this
|
|
// to name the ssh key
|
|
var hookurl, err = url.Parse(link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if the repository is private we'll need
|
|
// to upload a github key to the repository
|
|
if repo.Private {
|
|
// name the key
|
|
var keyname = "drone@" + hookurl.Host
|
|
var _, err = client.RepoKeys.CreateUpdate(repo.Owner, repo.Name, repo.PublicKey, keyname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// add the hook
|
|
_, err = client.Brokers.CreateUpdate(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost)
|
|
return err
|
|
}
|
|
|
|
// ParseHook parses the post-commit hook from the Request body
|
|
// and returns the required data in a standard format.
|
|
func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) {
|
|
var payload = req.FormValue("payload")
|
|
var hook, err = bitbucket.ParseHook([]byte(payload))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// verify the payload has the minimum amount of required data.
|
|
if hook.Repo == nil || hook.Commits == nil || len(hook.Commits) == 0 {
|
|
return nil, fmt.Errorf("Invalid Bitbucket post-commit Hook. Missing Repo or Commit data.")
|
|
}
|
|
|
|
var author = hook.Commits[len(hook.Commits)-1].RawAuthor
|
|
var matches = emailRegexp.FindStringSubmatch(author)
|
|
if len(matches) == 2 {
|
|
author = matches[1]
|
|
}
|
|
|
|
return &model.Hook{
|
|
Owner: hook.Repo.Owner,
|
|
Repo: hook.Repo.Name,
|
|
Sha: hook.Commits[len(hook.Commits)-1].Hash,
|
|
Branch: hook.Commits[len(hook.Commits)-1].Branch,
|
|
Author: author,
|
|
Timestamp: time.Now().UTC().String(),
|
|
Message: hook.Commits[len(hook.Commits)-1].Message,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Bitbucket) OpenRegistration() bool {
|
|
return r.Open
|
|
}
|