1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-12-18 08:26:45 +02:00
woodpecker/remote/bitbucketserver/bitbucketserver.go
Joachim Hill-Grannec 2ce566483b Handling some of the error in bitbucksetserver
Fixed returning the http(s) version of the clone link which had the username in it however should not to work with netrc
Quick typo fix
2016-06-11 18:42:55 -07:00

364 lines
9.8 KiB
Go

package bitbucketserver
// WARNING! This is an work-in-progress patch and does not yet conform to the coding,
// quality or security standards expected of this project. Please use with caution.
import (
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"net/url"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/mrjones/oauth"
"strings"
)
// Opts defines configuration options.
type Opts struct {
URL string // Stash server url.
Username string // Git machine account username.
Password string // Git machine account password.
ConsumerKey string // Oauth1 consumer key.
ConsumerRSA string // Oauth1 consumer key file.
SkipVerify bool // Skip ssl verification.
}
// New returns a Remote implementation that integrates with Bitbucket Server,
// the on-premise edition of Bitbucket Cloud, formerly known as Stash.
func New(opts Opts) (remote.Remote, error) {
bb := &client{
URL: opts.URL,
ConsumerKey: opts.ConsumerKey,
ConsumerRSA: opts.ConsumerRSA,
GitUserName: opts.Username,
GitPassword: opts.Password,
}
switch {
case bb.GitUserName == "":
return nil, fmt.Errorf("Must have a git machine account username")
case bb.GitPassword == "":
return nil, fmt.Errorf("Must have a git machine account password")
case bb.ConsumerKey == "":
return nil, fmt.Errorf("Must have a oauth1 consumer key")
case bb.ConsumerRSA == "":
return nil, fmt.Errorf("Must have a oauth1 consumer key file")
}
keyfile, err := ioutil.ReadFile(bb.ConsumerRSA)
if err != nil {
return nil, err
}
block, _ := pem.Decode(keyfile)
bb.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// TODO de-referencing is a bit weird and may not behave as expected, and could
// have race conditions. Instead store the parsed key (I already did this above)
// and then pass the parsed private key when creating the Bitbucket client.
bb.Consumer = *NewClient(bb.ConsumerRSA, bb.ConsumerKey, bb.URL)
return bb, nil
}
type client struct {
URL string
ConsumerKey string
GitUserName string
GitPassword string
ConsumerRSA string
PrivateKey *rsa.PrivateKey
Consumer oauth.Consumer
}
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob")
if err != nil {
return nil, err
}
var code = req.FormValue("oauth_verifier")
if len(code) == 0 {
http.Redirect(res, req, url, http.StatusSeeOther)
return nil, nil
}
requestToken.Token = req.FormValue("oauth_token")
accessToken, err := c.Consumer.AuthorizeToken(requestToken, code)
if err != nil {
return nil, err
}
client, err := c.Consumer.MakeHttpClient(accessToken)
if err != nil {
return nil, err
}
response, err := client.Get(fmt.Sprintf("%s/plugins/servlet/applinks/whoami", c.URL))
if err != nil {
return nil, err
}
defer response.Body.Close()
bits, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
login := string(bits)
// TODO errors should never be ignored like this
response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s", c.URL, login))
if err != nil {
return nil, err
}
contents, err := ioutil.ReadAll(response1.Body)
if err !=nil {
return nil, err
}
defer response1.Body.Close()
var user User
err = json.Unmarshal(contents, &user)
if err != nil {
return nil, err
}
return &model.User{
Login: login,
Email: user.EmailAddress,
Token: accessToken.Token,
Avatar: avatarLink(user.EmailAddress),
}, nil
}
// Auth is not supported by the Stash driver.
func (*client) Auth(token, secret string) (string, error) {
return "", fmt.Errorf("Not Implemented")
}
// Teams is not supported by the Stash driver.
func (*client) Teams(u *model.User) ([]*model.Team, error) {
var teams []*model.Team
return teams, nil
}
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClientWithToken(&c.Consumer, u.Token)
url := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", c.URL, owner, name)
response, err := client.Get(url)
if err != nil {
log.Error(err)
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
bsRepo := BSRepo{}
err = json.Unmarshal(contents, &bsRepo)
if err !=nil {
return nil, err
}
repo := &model.Repo{
Name: bsRepo.Slug,
Owner: bsRepo.Project.Key,
Branch: "master",
Kind: model.RepoGit,
IsPrivate: true, // TODO(josmo) possibly set this as a setting - must always be private to use netrc
FullName: fmt.Sprintf("%s/%s", bsRepo.Project.Key, bsRepo.Slug),
}
for _, item := range bsRepo.Links.Clone {
if item.Name == "http" {
//TODO sdhould find a clean way to do this
//We are removing the username out fo the link to allow for Netrc to work
splitUrl := strings.SplitAfterN(item.Href,"@",2)
splitProtocal := strings.SplitAfterN(splitUrl[0],"//",2)
cleanUrl := fmt.Sprintf("%s%s",splitProtocal[0], splitUrl[1])
repo.Clone = cleanUrl
}
}
for _, item := range bsRepo.Links.Self {
if item.Href != "" {
repo.Link = item.Href
}
}
return repo, nil
}
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
var repos = []*model.RepoLite{}
client := NewClientWithToken(&c.Consumer, u.Token)
response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000", c.URL))
if err != nil {
log.Error(err)
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
var repoResponse Repos
err = json.Unmarshal(contents, &repoResponse)
if err != nil {
return nil, err
}
for _, repo := range repoResponse.Values {
repos = append(repos, &model.RepoLite{
Name: repo.Slug,
FullName: repo.Project.Key + "/" + repo.Slug,
Owner: repo.Project.Key,
})
}
return repos, nil
}
func (c *client) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
// TODO need to fetch real permissions here
perms := new(model.Perm)
perms.Pull = true
perms.Admin = true
perms.Push = true
return perms, nil
}
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
log.Info(fmt.Sprintf("Staring file for bitbucketServer login: %s repo: %s buildevent: %s string: %s", u.Login, r.Name, b.Event, f))
client := NewClientWithToken(&c.Consumer, u.Token)
fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", c.URL, r.Owner, r.Name, f)
log.Info(fileURL)
response, err := client.Get(fileURL)
if err != nil {
log.Error(err)
}
if response.StatusCode == 404 {
return nil, nil
}
defer response.Body.Close()
responseBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Error(err)
}
return responseBytes, nil
}
// Status is not supported by the Gogs driver.
func (*client) Status(*model.User, *model.Repo, *model.Build, string) error {
return nil
}
func (c *client) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) {
u, err := url.Parse(c.URL)
//remove the port
tmp := strings.Split(u.Host, ":")
var host = tmp[0]
if err != nil {
return nil, err
}
log.Info(fmt.Sprintf("machine % login %s password %s", host, c.GitUserName, c.GitPassword))
return &model.Netrc{
Machine: host,
Login: c.GitUserName,
Password: c.GitPassword,
}, nil
}
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
client := NewClientWithToken(&c.Consumer, u.Token)
hook, err := c.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
if err != nil {
return err
}
log.Info(hook)
return nil
}
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
client := NewClientWithToken(&c.Consumer, u.Token)
err := c.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
if err != nil {
return err
}
return nil
}
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
hook := new(postHook)
if err := json.NewDecoder(r.Body).Decode(hook); err != nil {
return nil, nil, err
}
build := &model.Build{
Event: model.EventPush,
Ref: hook.RefChanges[0].RefID, // TODO check for index Values
Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values
Commit: hook.RefChanges[0].ToHash, // TODO check for index value
Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress),
}
repo := &model.Repo{
Name: hook.Repository.Slug,
Owner: hook.Repository.Project.Key,
FullName: fmt.Sprintf("%s/%s", hook.Repository.Project.Key, hook.Repository.Slug),
Branch: "master",
Kind: model.RepoGit,
}
return repo, build, nil
}
type HookDetail struct {
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Version string `json:"version"`
ConfigFormKey string `json:"configFormKey"`
}
type Hook struct {
Enabled bool `json:"enabled"`
Details *HookDetail `json:"details"`
}
// Enable hook for named repository
func (bs *client) CreateHook(client *http.Client, project, slug, hook_key, link string) (*Hook, error) {
// Set hook
hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, link))
// Enable hook
enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled",
project, slug, hook_key)
doPut(client, bs.URL+enablePath, hookBytes)
return nil, nil
}
// Disable hook for named repository
func (bs *client) DeleteHook(client *http.Client, project, slug, hook_key, link string) error {
enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled",
project, slug, hook_key)
doDelete(client, bs.URL+enablePath)
return nil
}