diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 93dd50dc1..5b6000ca9 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -4,32 +4,31 @@ package bitbucketserver // quality or security standards expected of this project. Please use with caution. import ( + "crypto/md5" "crypto/rsa" + "crypto/tls" "crypto/x509" + "encoding/hex" "encoding/json" "encoding/pem" "fmt" + log "github.com/Sirupsen/logrus" + "github.com/drone/drone/model" + "github.com/drone/drone/remote" + "github.com/drone/drone/remote/bitbucketserver/internal" + "github.com/mrjones/oauth" "io/ioutil" "net/http" "net/url" - "github.com/drone/drone/remote/bitbucketserver/internal" - "github.com/drone/drone/model" - "github.com/drone/drone/remote" - "github.com/mrjones/oauth" "strings" - "crypto/tls" - "encoding/hex" - "crypto/md5" ) const ( - requestTokenURL = "%s/plugins/servlet/oauth/request-token" + requestTokenURL = "%s/plugins/servlet/oauth/request-token" authorizeTokenURL = "%s/plugins/servlet/oauth/authorize" - accessTokenURL = "%s/plugins/servlet/oauth/access-token" + accessTokenURL = "%s/plugins/servlet/oauth/access-token" ) - - // Opts defines configuration options. type Opts struct { URL string // Stash server url. @@ -37,27 +36,24 @@ type Opts struct { Password string // Git machine account password. ConsumerKey string // Oauth1 consumer key. ConsumerRSA string // Oauth1 consumer key file. - SkipVerify bool // Skip ssl verification. + SkipVerify bool // Skip ssl verification. } type Config struct { - URL string - Username string - Password string - PrivateKey *rsa.PrivateKey - ConsumerKey string + URL string + Username string + Password string SkipVerify bool - + Consumer *oauth.Consumer } // 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) { config := &Config{ - URL: opts.URL, - Username: opts.Username, - Password: opts.Password, - ConsumerKey: opts.ConsumerKey, + URL: opts.URL, + Username: opts.Username, + Password: opts.Password, SkipVerify: opts.SkipVerify, } @@ -77,16 +73,16 @@ func New(opts Opts) (remote.Remote, error) { return nil, err } block, _ := pem.Decode(keyFile) - config.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + PrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } + config.Consumer = CreateConsumer(opts.URL, opts.ConsumerKey, PrivateKey) return config, nil } - func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { - requestToken, url, err := c.Consumer().GetRequestTokenAndUrl("oob") + requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob") if err != nil { return nil, err } @@ -96,16 +92,15 @@ func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, return nil, nil } requestToken.Token = req.FormValue("oauth_token") - accessToken, err := c.Consumer().AuthorizeToken(requestToken, code) + accessToken, err := c.Consumer.AuthorizeToken(requestToken, code) if err != nil { return nil, err } - client := internal.NewClientWithToken(c.URL, c.Consumer(), accessToken.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, accessToken.Token) return client.FindCurrentUser() - } // Auth is not supported by the Stash driver. @@ -120,33 +115,34 @@ func (*Config) Teams(u *model.User) ([]*model.Team, error) { } func (c *Config) Repo(u *model.User, owner, name string) (*model.Repo, error) { - - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start repo lookup with: %+v %s %s\n", u, owner, name)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepo(owner, name) } func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { - - client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start repos lookup for: %+v\n", u)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepos() } func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) { - client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start perm lookup for: %+v %s %s\n", u, owner, repo)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepoPerms(owner, repo) } func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { - - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start file lookup for: %+v %+v %s\n", u, b, f)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindFileForRepo(r.Owner, r.Name, f) } -// Status is not supported by the Gogs driver. +// Status is not supported by the bitbucketserver driver. func (*Config) Status(*model.User, *model.Repo, *model.Build, string) error { return nil } @@ -171,13 +167,13 @@ func (c *Config) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) { } func (c *Config) Activate(u *model.User, r *model.Repo, link string) error { - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.CreateHook(r.Owner, r.Name, link) } func (c *Config) Deactivate(u *model.User, r *model.Repo, link string) error { - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.DeleteHook(r.Owner, r.Name, link) } @@ -206,15 +202,14 @@ func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { return repo, build, nil } - -func (c *Config) Consumer() *oauth.Consumer{ +func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer { consumer := oauth.NewRSAConsumer( - c.ConsumerKey, - c.PrivateKey, + ConsumerKey, + PrivateKey, oauth.ServiceProvider{ - RequestTokenUrl: fmt.Sprintf(requestTokenURL, c.URL), - AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, c.URL), - AccessTokenUrl: fmt.Sprintf(accessTokenURL, c.URL), + RequestTokenUrl: fmt.Sprintf(requestTokenURL, URL), + AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, URL), + AccessTokenUrl: fmt.Sprintf(accessTokenURL, URL), HttpMethod: "POST", }) consumer.HttpClient = &http.Client{ diff --git a/remote/bitbucketserver/internal/client.go b/remote/bitbucketserver/internal/client.go index d6ea0243d..657bd34ed 100644 --- a/remote/bitbucketserver/internal/client.go +++ b/remote/bitbucketserver/internal/client.go @@ -1,34 +1,34 @@ package internal import ( - "net/http" - log "github.com/Sirupsen/logrus" - "github.com/mrjones/oauth" - "github.com/drone/drone/model" - "fmt" - "io/ioutil" - "encoding/json" - "strings" - "encoding/hex" - "crypto/md5" - "net/url" "bytes" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/drone/drone/model" + "github.com/mrjones/oauth" + "io/ioutil" + "net/http" + "net/url" + "strings" ) const ( - currentUserId = "%s/plugins/servlet/applinks/whoami" - pathUser = "%s/rest/api/1.0/users/%s" - pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" - pathRepos = "%s/rest/api/1.0/repos?limit=%s" - pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" - pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" - hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" + currentUserId = "%s/plugins/servlet/applinks/whoami" + pathUser = "%s/rest/api/1.0/users/%s" + pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" + pathRepos = "%s/rest/api/1.0/repos?limit=%s" + pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" + pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" + hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled" ) type Client struct { - *http.Client - base string + client *http.Client + base string accessToken string } @@ -36,6 +36,7 @@ func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string var token oauth.AccessToken token.Token = AccessToken client, err := Consumer.MakeHttpClient(&token) + log.Debug(fmt.Printf("Create client: %+v %s\n", token, url)) if err != nil { log.Error(err) } @@ -43,7 +44,7 @@ func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string } func (c *Client) FindCurrentUser() (*model.User, error) { - CurrentUserIdResponse, err := c.Get(fmt.Sprintf(currentUserId, c.base)) + CurrentUserIdResponse, err := c.client.Get(fmt.Sprintf(currentUserId, c.base)) if err != nil { return nil, err } @@ -54,15 +55,14 @@ func (c *Client) FindCurrentUser() (*model.User, error) { } login := string(bits) - // TODO errors should never be ignored like this - CurrentUserResponse, err := c.Get(fmt.Sprintf(pathUser, c.base, login)) + CurrentUserResponse, err := c.client.Get(fmt.Sprintf(pathUser, c.base, login)) if err != nil { return nil, err } defer CurrentUserResponse.Body.Close() contents, err := ioutil.ReadAll(CurrentUserResponse.Body) - if err !=nil { + if err != nil { return nil, err } @@ -71,18 +71,21 @@ func (c *Client) FindCurrentUser() (*model.User, error) { if err != nil { return nil, err } - return &model.User{ + + ModelUser := &model.User{ Login: login, Email: user.EmailAddress, Token: c.accessToken, Avatar: avatarLink(user.EmailAddress), - }, nil + } + log.Debug(fmt.Printf("User information: %+v\n", ModelUser)) + return ModelUser, nil } -func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ +func (c *Client) FindRepo(owner string, name string) (*model.Repo, error) { urlString := fmt.Sprintf(pathRepo, c.base, owner, name) - response, err := c.Get(urlString) + response, err := c.client.Get(urlString) if err != nil { log.Error(err) } @@ -90,7 +93,7 @@ func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ contents, err := ioutil.ReadAll(response.Body) bsRepo := BSRepo{} err = json.Unmarshal(contents, &bsRepo) - if err !=nil { + if err != nil { return nil, err } repo := &model.Repo{ @@ -117,14 +120,16 @@ func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ repo.Link = item.Href } } - + log.Debug(fmt.Printf("Repo: %+v\n", repo)) return repo, nil } func (c *Client) FindRepos() ([]*model.RepoLite, error) { - response, err := c.Get(fmt.Sprintf(pathRepos, c.base)) + requestUrl := fmt.Sprintf(pathRepos, c.base, "1000") + log.Debug(fmt.Printf("request :%s", requestUrl)) + response, err := c.client.Get(requestUrl) if err != nil { - log.Error(err) + return nil, err } defer response.Body.Close() contents, err := ioutil.ReadAll(response.Body) @@ -136,6 +141,7 @@ func (c *Client) FindRepos() ([]*model.RepoLite, error) { if err != nil { return nil, err } + log.Debug(fmt.Printf("repoResponse: %+v\n", repoResponse)) var repos = []*model.RepoLite{} for _, repo := range repoResponse.Values { repos = append(repos, &model.RepoLite{ @@ -144,7 +150,7 @@ func (c *Client) FindRepos() ([]*model.RepoLite, error) { Owner: repo.Project.Key, }) } - + log.Debug(fmt.Printf("repos: %+v\n", repos)) return repos, nil } @@ -156,17 +162,18 @@ func (c *Client) FindRepoPerms(owner string, repo string) (*model.Perm, error) { return perms, err } // Must have admin to be able to list hooks. If have access the enable perms - _, err = c.Get(fmt.Sprintf(pathHook, c.base, owner, repo,hookName)) + _, err = c.client.Get(fmt.Sprintf(pathHook, c.base, owner, repo, hookName)) if err == nil { perms.Push = true perms.Admin = true } perms.Pull = true + log.Debug(fmt.Printf("Perms: %+v\n", perms)) return perms, nil } func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([]byte, error) { - response, err := c.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) + response, err := c.client.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) if err != nil { log.Error(err) } @@ -181,16 +188,16 @@ func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([] return responseBytes, nil } -func(c *Client) CreateHook(owner string, name string, callBackLink string) error { +func (c *Client) CreateHook(owner string, name string, callBackLink string) error { // Set hook //TODO: Check existing and add up to 5 hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, callBackLink)) - return c.doPut(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName), hookBytes) + return c.doPut(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName), hookBytes) } -func(c *Client) DeleteHook(owner string, name string, link string) error { +func (c *Client) DeleteHook(owner string, name string, link string) error { //TODO: eventially should only delete the link callback - return c.doDelete(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName )) + return c.doDelete(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName)) } func avatarLink(email string) (url string) { @@ -198,16 +205,15 @@ func avatarLink(email string) (url string) { hasher.Write([]byte(strings.ToLower(email))) emailHash := fmt.Sprintf("%v", hex.EncodeToString(hasher.Sum(nil))) avatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s.jpg", emailHash) - log.Info(avatarURL) + log.Debug(avatarURL) return avatarURL } - //Helper function to help create the hook -func(c *Client) doPut(url string, body []byte) error { +func (c *Client) doPut(url string, body []byte) error { request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) request.Header.Add("Content-Type", "application/json") - response, err := c.Do(request) + response, err := c.client.Do(request) if err != nil { return err } @@ -217,12 +223,12 @@ func(c *Client) doPut(url string, body []byte) error { } //Helper function to do delete on the hook -func(c *Client) doDelete(url string) error { +func (c *Client) doDelete(url string) error { request, err := http.NewRequest("DELETE", url, nil) - response, err := c.Do(request) + response, err := c.client.Do(request) if err != nil { return err } defer response.Body.Close() return nil -} \ No newline at end of file +} diff --git a/remote/bitbucketserver/internal/types.go b/remote/bitbucketserver/internal/types.go index ef21208c6..ac222043b 100644 --- a/remote/bitbucketserver/internal/types.go +++ b/remote/bitbucketserver/internal/types.go @@ -1,48 +1,46 @@ package internal - type User struct { Active bool `json:"active"` DisplayName string `json:"displayName"` EmailAddress string `json:"emailAddress"` ID int `json:"id"` Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` Name string `json:"name"` Slug string `json:"slug"` Type string `json:"type"` } - type BSRepo struct { Forkable bool `json:"forkable"` ID int `json:"id"` Links struct { - Clone []struct { - Href string `json:"href"` - Name string `json:"name"` - } `json:"clone"` - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` + Clone []struct { + Href string `json:"href"` + Name string `json:"name"` + } `json:"clone"` + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` Name string `json:"name"` Project struct { - Description string `json:"description"` - ID int `json:"id"` - Key string `json:"key"` - Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` - Name string `json:"name"` - Public bool `json:"public"` - Type string `json:"type"` - } `json:"project"` + Description string `json:"description"` + ID int `json:"id"` + Key string `json:"key"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + Name string `json:"name"` + Public bool `json:"public"` + Type string `json:"type"` + } `json:"project"` Public bool `json:"public"` ScmID string `json:"scmId"` Slug string `json:"slug"` @@ -59,28 +57,28 @@ type Repos struct { Forkable bool `json:"forkable"` ID int `json:"id"` Links struct { - Clone []struct { - Href string `json:"href"` - Name string `json:"name"` - } `json:"clone"` - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` + Clone []struct { + Href string `json:"href"` + Name string `json:"name"` + } `json:"clone"` + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` Name string `json:"name"` Project struct { - Description string `json:"description"` - ID int `json:"id"` - Key string `json:"key"` - Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` - Name string `json:"name"` - Public bool `json:"public"` - Type string `json:"type"` - } `json:"project"` + Description string `json:"description"` + ID int `json:"id"` + Key string `json:"key"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + Name string `json:"name"` + Public bool `json:"public"` + Type string `json:"type"` + } `json:"project"` Public bool `json:"public"` ScmID string `json:"scmId"` Slug string `json:"slug"` @@ -101,4 +99,4 @@ type HookDetail struct { Description string `json:"description"` Version string `json:"version"` ConfigFormKey string `json:"configFormKey"` -} \ No newline at end of file +} diff --git a/remote/bitbucketserver/types.go b/remote/bitbucketserver/types.go index 248b8c5ef..d4876ad9a 100644 --- a/remote/bitbucketserver/types.go +++ b/remote/bitbucketserver/types.go @@ -84,10 +84,6 @@ type postHook struct { } `json:"repository"` } - - - - type BSRepo struct { Forkable bool `json:"forkable"` ID int `json:"id"`