298 lines
7.4 KiB
Go
298 lines
7.4 KiB
Go
package gitlab
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.google.com/p/goauth2/oauth"
|
|
"github.com/Bugagazavr/go-gitlab-client"
|
|
"github.com/drone/drone/shared/httputil"
|
|
"github.com/drone/drone/shared/model"
|
|
)
|
|
|
|
type Gitlab struct {
|
|
url string
|
|
SkipVerify bool
|
|
Open bool
|
|
Client string
|
|
Secret string
|
|
}
|
|
|
|
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) {
|
|
host := httputil.GetURL(req)
|
|
config := NewOauthConfig(r, host)
|
|
|
|
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, 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(user.Id)
|
|
login.Access = token.AccessToken
|
|
login.Secret = token.RefreshToken
|
|
login.Login = user.Username
|
|
login.Email = user.Email
|
|
return login, nil
|
|
}
|
|
|
|
// GetKind returns the identifier of this remote GitHub instane.
|
|
func (r *Gitlab) GetKind() string {
|
|
return model.RemoteGitlab
|
|
}
|
|
|
|
// GetHost returns the hostname of this remote GitHub instance.
|
|
func (r *Gitlab) 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 *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) {
|
|
|
|
var repos []*model.Repo
|
|
var client = NewClient(r.url, user.Access, r.SkipVerify)
|
|
var list, err = client.AllProjects()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var remote = r.GetKind()
|
|
var hostname = r.GetHost()
|
|
|
|
for _, item := range list {
|
|
var repo = model.Repo{
|
|
UserID: user.ID,
|
|
Remote: remote,
|
|
Host: hostname,
|
|
Owner: item.Namespace.Path,
|
|
Name: item.Path,
|
|
Private: !item.Public,
|
|
CloneURL: item.HttpRepoUrl,
|
|
GitURL: item.HttpRepoUrl,
|
|
SSHURL: item.SshRepoUrl,
|
|
URL: item.Url,
|
|
Role: &model.Perm{},
|
|
}
|
|
|
|
if repo.Private {
|
|
repo.CloneURL = repo.SSHURL
|
|
}
|
|
|
|
// if the user is the owner we can assume full access,
|
|
// otherwise check for the permission items.
|
|
if repo.Owner == user.Login {
|
|
repo.Role = new(model.Perm)
|
|
repo.Role.Admin = true
|
|
repo.Role.Write = true
|
|
repo.Role.Read = true
|
|
} else {
|
|
// Fetch current project
|
|
project, err := client.Project(strconv.Itoa(item.Id))
|
|
if err != nil || project.Permissions == nil {
|
|
continue
|
|
}
|
|
repo.Role.Admin = IsAdmin(project)
|
|
repo.Role.Write = IsWrite(project)
|
|
repo.Role.Read = IsRead(project)
|
|
}
|
|
|
|
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 *Gitlab) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
|
|
var client = NewClient(r.url, user.Access, r.SkipVerify)
|
|
var path = ns(repo.Owner, repo.Name)
|
|
return client.RepoRawFile(path, hook.Sha, ".drone.yml")
|
|
}
|
|
|
|
// Activate activates a repository by adding a Post-commit hook and
|
|
// a Public Deploy key, if applicable.
|
|
func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error {
|
|
var client = NewClient(r.url, user.Access, r.SkipVerify)
|
|
var path = ns(repo.Owner, repo.Name)
|
|
var title, err = GetKeyTitle(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 {
|
|
var err = client.AddProjectDeployKey(path, title, repo.PublicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// append the repo owner / name to the hook url since gitlab
|
|
// doesn't send this detail in the post-commit hook
|
|
link += "?owner=" + repo.Owner + "&name=" + repo.Name
|
|
|
|
// add the hook
|
|
return client.AddProjectHook(path, link, true, false, true)
|
|
}
|
|
|
|
// Deactivate removes a repository by removing all the post-commit hooks
|
|
// which are equal to link and removing the SSH deploy key.
|
|
func (r *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) error {
|
|
var client = NewClient(r.url, user.Access, r.SkipVerify)
|
|
var path = ns(repo.Owner, repo.Name)
|
|
|
|
keys, err := client.ProjectDeployKeys(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var pubkey = strings.TrimSpace(repo.PublicKey)
|
|
for _, k := range keys {
|
|
if pubkey == strings.TrimSpace(k.Key) {
|
|
if err := client.RemoveProjectDeployKey(path, strconv.Itoa(k.Id)); err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
hooks, err := client.ProjectHooks(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
link += "?owner=" + repo.Owner + "&name=" + repo.Name
|
|
for _, h := range hooks {
|
|
if link == h.Url {
|
|
if err := client.RemoveProjectHook(path, strconv.Itoa(h.Id)); err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ParseHook parses the post-commit hook from the Request body
|
|
// and returns the required data in a standard format.
|
|
func (r *Gitlab) ParseHook(req *http.Request) (*model.Hook, error) {
|
|
|
|
defer req.Body.Close()
|
|
var payload, _ = ioutil.ReadAll(req.Body)
|
|
var parsed, err = gogitlab.ParseHook(payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if parsed.ObjectKind == "merge_request" {
|
|
// TODO (bradrydzewski) figure out how to handle merge requests
|
|
return nil, nil
|
|
}
|
|
|
|
if len(parsed.After) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var hook = new(model.Hook)
|
|
hook.Owner = req.FormValue("owner")
|
|
hook.Repo = req.FormValue("name")
|
|
hook.Sha = parsed.After
|
|
hook.Branch = parsed.Branch()
|
|
|
|
var head = parsed.Head()
|
|
hook.Message = head.Message
|
|
hook.Timestamp = head.Timestamp
|
|
|
|
// extracts the commit author (ideally email)
|
|
// from the post-commit hook
|
|
switch {
|
|
case head.Author != nil:
|
|
hook.Author = head.Author.Email
|
|
case head.Author == nil:
|
|
hook.Author = parsed.UserName
|
|
}
|
|
|
|
return hook, nil
|
|
}
|
|
|
|
func (r *Gitlab) OpenRegistration() bool {
|
|
return r.Open
|
|
}
|
|
|
|
func (r *Gitlab) GetToken(user *model.User) (*model.Token, error) {
|
|
expiry := time.Unix(user.TokenExpiry, 0)
|
|
if user.TokenExpiry == 0 && len(user.Access) != 0 ||
|
|
expiry.Sub(time.Now()) > (60*time.Second) {
|
|
return nil, nil
|
|
}
|
|
|
|
t := &oauth.Transport{
|
|
Config: NewOauthConfig(r, ""),
|
|
Token: &oauth.Token{
|
|
AccessToken: user.Access,
|
|
RefreshToken: user.Secret,
|
|
Expiry: expiry,
|
|
},
|
|
Transport: &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: r.SkipVerify},
|
|
},
|
|
}
|
|
|
|
if err := t.Refresh(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var token = new(model.Token)
|
|
token.AccessToken = t.Token.AccessToken
|
|
token.RefreshToken = t.Token.RefreshToken
|
|
token.Expiry = t.Token.Expiry.Unix()
|
|
return token, nil
|
|
}
|