diff --git a/engine/api/project/dao.go b/engine/api/project/dao.go index 8629452a99..a2e775e3de 100644 --- a/engine/api/project/dao.go +++ b/engine/api/project/dao.go @@ -16,6 +16,7 @@ import ( "github.com/ovh/cds/engine/api/group" "github.com/ovh/cds/engine/api/keys" "github.com/ovh/cds/engine/api/repositoriesmanager" + "github.com/ovh/cds/engine/api/vcs" "github.com/ovh/cds/engine/cache" "github.com/ovh/cds/engine/gorpmapper" "github.com/ovh/cds/sdk" @@ -314,11 +315,34 @@ func unwrap(ctx context.Context, db gorp.SqlExecutor, p *dbProject, opts []LoadO end() } - vcsServers, err := repositoriesmanager.LoadAllProjectVCSServerLinksByProjectID(ctx, db, p.ID) + vcsProjects, err := vcs.LoadAllVCSByProject(ctx, db, p.Key) if err != nil { return nil, err } - proj.VCSServers = vcsServers + + proj.VCSServers = vcsProjects + + // DEPRECATED VCS + vcsServersDeprecated, err := repositoriesmanager.LoadAllProjectVCSServerLinksByProjectID(ctx, db, p.ID) + if err != nil { + return nil, err + } + + for _, vcsDeprecated := range vcsServersDeprecated { + var found bool + for _, v := range vcsProjects { + if vcsDeprecated.Name == v.Name { + found = true + break + } + } + if !found { + toadd := sdk.VCSProject{ + Name: vcsDeprecated.Name, + } + proj.VCSServers = append(proj.VCSServers, toadd) + } + } return &proj, nil } diff --git a/engine/api/repositoriesmanager/repositories_manager.go b/engine/api/repositoriesmanager/repositories_manager.go index 869ef5d776..43b8bdc69d 100644 --- a/engine/api/repositoriesmanager/repositories_manager.go +++ b/engine/api/repositoriesmanager/repositories_manager.go @@ -3,6 +3,7 @@ package repositoriesmanager import ( "context" "encoding/base64" + "encoding/json" "fmt" "io" "net/http" @@ -23,16 +24,22 @@ import ( "github.com/ovh/cds/sdk/telemetry" ) -func LoadByName(ctx context.Context, db gorp.SqlExecutor, vcsName string) (sdk.VCSConfiguration, error) { +func (c *vcsClient) IsGerrit(ctx context.Context, db gorp.SqlExecutor) (bool, error) { + if c.vcsProject != nil { + return c.vcsProject.Type == "gerrit", nil + } + + // DEPRECATED VCS var vcsServer sdk.VCSConfiguration srvs, err := services.LoadAllByType(ctx, db, sdk.TypeVCS) if err != nil { - return vcsServer, sdk.WrapError(err, "Unable to load services") + return false, sdk.WrapError(err, "Unable to load services") } - if _, _, err := services.NewClient(db, srvs).DoJSONRequest(ctx, "GET", fmt.Sprintf("/vcs/%s", vcsName), nil, &vcsServer); err != nil { - return vcsServer, sdk.WithStack(err) + if _, _, err := services.NewClient(db, srvs).DoJSONRequest(ctx, "GET", fmt.Sprintf("/vcs/%s", c.name), nil, &vcsServer); err != nil { + return false, sdk.WrapError(err, "error on requesting vcs service") } - return vcsServer, nil + + return vcsServer.Type == "gerrit", nil } //LoadAll Load all RepositoriesManager from the database @@ -241,10 +248,20 @@ func deprecatedAuthorizedClient(ctx context.Context, db gorpmapper.SqlExecutorWi func (c *vcsClient) doJSONRequest(ctx context.Context, method, path string, in interface{}, out interface{}) (int, error) { headers, code, err := services.NewClient(c.db, c.srvs).DoJSONRequest(ctx, method, path, in, out, func(req *http.Request) { - req.Header.Set(sdk.HeaderXAccessToken, base64.StdEncoding.EncodeToString([]byte(c.token))) - req.Header.Set(sdk.HeaderXAccessTokenSecret, base64.StdEncoding.EncodeToString([]byte(c.secret))) - if c.created != 0 { - req.Header.Set(sdk.HeaderXAccessTokenCreated, base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%d", c.created)))) + if c.vcsProject != nil { + log.Debug(ctx, "requesting vcs via vcs project") + btes, err := json.Marshal(c.vcsProject) + if err != nil { + log.Error(ctx, "invalid vcs project conf. err: %v", err) + } + req.Header.Set(sdk.HeaderXVCSProjectConf, base64.StdEncoding.EncodeToString(btes)) + } else { + log.Debug(ctx, "requesting vcs via vcs oauth2") + req.Header.Set(sdk.HeaderXAccessToken, base64.StdEncoding.EncodeToString([]byte(c.token))) + req.Header.Set(sdk.HeaderXAccessTokenSecret, base64.StdEncoding.EncodeToString([]byte(c.secret))) + if c.created != 0 { + req.Header.Set(sdk.HeaderXAccessTokenCreated, base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%d", c.created)))) + } } }) @@ -274,8 +291,14 @@ func (c *vcsClient) doJSONRequest(ctx context.Context, method, path string, in i func (c *vcsClient) postBinary(ctx context.Context, path string, fileLength int, r io.Reader, out interface{}) (int, error) { return services.PostBinary(ctx, c.srvs, path, r, out, func(req *http.Request) { - req.Header.Set(sdk.HeaderXAccessToken, base64.StdEncoding.EncodeToString([]byte(c.token))) - req.Header.Set(sdk.HeaderXAccessTokenSecret, base64.StdEncoding.EncodeToString([]byte(c.secret))) + if c.vcsProject != nil { + if token, ok := c.vcsProject.Auth["token"]; ok { + req.Header.Set(sdk.HeaderXVCSProjectConf, base64.StdEncoding.EncodeToString([]byte(token))) + } + } else { + req.Header.Set(sdk.HeaderXAccessToken, base64.StdEncoding.EncodeToString([]byte(c.token))) + req.Header.Set(sdk.HeaderXAccessTokenSecret, base64.StdEncoding.EncodeToString([]byte(c.secret))) + } req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Content-Length", strconv.Itoa(fileLength)) }) diff --git a/engine/api/v2_project.go b/engine/api/v2_project.go index 0c326aef04..ec899c4c46 100644 --- a/engine/api/v2_project.go +++ b/engine/api/v2_project.go @@ -154,7 +154,7 @@ func (api *API) getVCSProjectHandler() ([]service.RbacChecker, service.Handler) } defer tx.Rollback() // nolint - vcsProject, err := vcs.LoadVCSByProject(context.Background(), tx, pKey, vcsProjectName, gorpmapping.GetOptions.WithDecryption) + vcsProject, err := vcs.LoadVCSByProject(context.Background(), tx, pKey, vcsProjectName) if err != nil { return err } diff --git a/engine/api/workflow/workflow_run_event.go b/engine/api/workflow/workflow_run_event.go index d5933979f7..be00aa2b49 100644 --- a/engine/api/workflow/workflow_run_event.go +++ b/engine/api/workflow/workflow_run_event.go @@ -93,14 +93,19 @@ loopNotif: repoFullName := wr.Workflow.Applications[node.Context.ApplicationID].RepositoryFullname //Get the RepositoriesManager Client + log.Info(ctx, "######## SendVCSEvent: AAAA") if e.vcsClient == nil { + log.Info(ctx, "######## SendVCSEvent: BBBBB") var err error e.vcsClient, err = repositoriesmanager.AuthorizedClient(ctx, tx, store, proj.Key, vcsServerName) if err != nil { - return err + log.Info(ctx, "######## SendVCSEvent: CCCC") + return sdk.WrapError(err, "can't get AuthorizedClient for %v/%v", proj.Key, vcsServerName) } + log.Info(ctx, "######## SendVCSEvent: DDDDD") } + log.Info(ctx, "######## SendVCSEvent: EEEEE") ref := nodeRun.VCSHash if nodeRun.VCSTag != "" { ref = nodeRun.VCSTag @@ -111,7 +116,7 @@ loopNotif: var err error statuses, err = e.vcsClient.ListStatuses(ctx, repoFullName, ref) if err != nil { - return err + return sdk.WrapError(err, "can't ListStatuses for %v with vcs %v/%v", repoFullName, proj.Key, vcsServerName) } e.commitsStatuses[ref] = statuses } @@ -129,7 +134,7 @@ loopNotif: if statusFound == nil || statusFound.State == "" { if err := e.sendVCSEventStatus(ctx, tx, store, proj.Key, wr, &nodeRun, notif, vcsServerName); err != nil { - return err + return sdk.WrapError(err, "can't sendVCSEventStatus vcs %v/%v", proj.Key, vcsServerName) } } else { skipStatus := false @@ -154,7 +159,7 @@ loopNotif: if !skipStatus { if err := e.sendVCSEventStatus(ctx, tx, store, proj.Key, wr, &nodeRun, notif, vcsServerName); err != nil { - return err + return sdk.WrapError(err, "can't sendVCSEventStatus vcs %v/%v", proj.Key, vcsServerName) } } } @@ -163,7 +168,7 @@ loopNotif: return nil } if err := e.sendVCSPullRequestComment(ctx, tx, wr, &nodeRun, notif, vcsServerName); err != nil { - return err + return sdk.WrapError(err, "can't sendVCSPullRequestComment vcs %v/%v", proj.Key, vcsServerName) } if err := tx.Commit(); err != nil { @@ -237,12 +242,11 @@ func (e *VCSEventMessenger) sendVCSEventStatus(ctx context.Context, db gorp.SqlE } // Check if it's a gerrit or not - vcsConf, err := repositoriesmanager.LoadByName(ctx, db, vcsServerName) + isGerrit, err := e.vcsClient.IsGerrit(ctx, db) if err != nil { return err } - - if vcsConf.Type == "gerrit" { + if isGerrit { // Get gerrit variable var project, changeID, branch, revision, url string projectParam := sdk.ParameterFind(nodeRun.BuildParameters, "git.repository") @@ -275,7 +279,6 @@ func (e *VCSEventMessenger) sendVCSEventStatus(ctx context.Context, db gorp.SqlE URL: url, } } - } payload, _ := json.Marshal(eventWNR) @@ -329,12 +332,6 @@ func (e *VCSEventMessenger) sendVCSPullRequestComment(ctx context.Context, db go return err } - // Check if it's a gerrit or not - vcsConf, err := repositoriesmanager.LoadByName(ctx, db, vcsServerName) - if err != nil { - return err - } - var changeID string changeIDParam := sdk.ParameterFind(nodeRun.BuildParameters, "gerrit.change.id") if changeIDParam != nil { @@ -350,13 +347,17 @@ func (e *VCSEventMessenger) sendVCSPullRequestComment(ctx context.Context, db go reqComment := sdk.VCSPullRequestCommentRequest{Message: report} reqComment.Revision = revision - // If we are on Gerrit - if changeID != "" && vcsConf.Type == "gerrit" { + isGerrit, err := e.vcsClient.IsGerrit(ctx, db) + if err != nil { + return err + } + + if changeID != "" && isGerrit { reqComment.ChangeID = changeID if err := e.vcsClient.PullRequestComment(ctx, app.RepositoryFullname, reqComment); err != nil { return err } - } else if vcsConf.Type != "gerrit" { + } else if !isGerrit { //Check if this branch and this commit is a pullrequest prs, err := e.vcsClient.PullRequests(ctx, app.RepositoryFullname) if err != nil { diff --git a/engine/api/workflow_event.go b/engine/api/workflow_event.go index 8daa76a494..376d8c25cb 100644 --- a/engine/api/workflow_event.go +++ b/engine/api/workflow_event.go @@ -61,7 +61,7 @@ func (api *API) WorkflowSendEvent(ctx context.Context, proj sdk.Project, report event.PublishWorkflowNodeRun(ctx, *nr, wr.Workflow, eventsNotif) e := &workflow.VCSEventMessenger{} if err := e.SendVCSEvent(ctx, db, api.Cache, proj, *wr, wnr); err != nil { - log.Warn(ctx, "WorkflowSendEvent> Cannot send vcs notification") + log.Warn(ctx, "WorkflowSendEvent> Cannot send vcs notification err:%v", err) } } diff --git a/engine/api/workflow_queue_test.go b/engine/api/workflow_queue_test.go index 79a14153ca..6023de2665 100644 --- a/engine/api/workflow_queue_test.go +++ b/engine/api/workflow_queue_test.go @@ -1171,9 +1171,6 @@ func TestInsertNewCodeCoverageReport(t *testing.T) { })) u.Groups = append(u.Groups, proj.ProjectGroups[0].Group) - // Add repo manager - proj.VCSServers = make([]sdk.ProjectVCSServerLink, 0, 1) - vcsServer := sdk.ProjectVCSServerLink{ ProjectID: proj.ID, Name: "repoManServ", diff --git a/engine/vcs/bitbucketcloud/bitbucketcloud.go b/engine/vcs/bitbucketcloud/bitbucketcloud.go index 0fa82f737e..d67a077713 100644 --- a/engine/vcs/bitbucketcloud/bitbucketcloud.go +++ b/engine/vcs/bitbucketcloud/bitbucketcloud.go @@ -36,7 +36,17 @@ type bitbucketcloudConsumer struct { } //New creates a new GithubConsumer -func New(ClientID, ClientSecret, apiURL, uiURL, proxyURL string, store cache.Store, disableStatus, disableStatusDetail bool) sdk.VCSServer { +func New(apiURL, uiURL, proxyURL string, store cache.Store) sdk.VCSServer { + return &bitbucketcloudConsumer{ + Cache: store, + apiURL: apiURL, + uiURL: uiURL, + proxyURL: proxyURL, + } +} + +// DEPRECATED VCS +func NewDeprecated(ClientID, ClientSecret, apiURL, uiURL, proxyURL string, store cache.Store, disableStatus, disableStatusDetail bool) sdk.VCSServer { return &bitbucketcloudConsumer{ ClientID: ClientID, ClientSecret: ClientSecret, diff --git a/engine/vcs/bitbucketcloud/bitbucketcloud_test.go b/engine/vcs/bitbucketcloud/bitbucketcloud_test.go index f724d3ea5d..02d0acb491 100644 --- a/engine/vcs/bitbucketcloud/bitbucketcloud_test.go +++ b/engine/vcs/bitbucketcloud/bitbucketcloud_test.go @@ -44,7 +44,7 @@ func getNewConsumer(t *testing.T) sdk.VCSServer { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - bbConsumer := New(clientID, clientSecret, "http://localhost", "", "", cache, true, true) + bbConsumer := NewDeprecated(clientID, clientSecret, "http://localhost", "", "", cache, true, true) return bbConsumer } @@ -66,7 +66,7 @@ func getNewAuthorizedClient(t *testing.T) sdk.VCSAuthorizedClient { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - bbConsumer := New(clientID, clientSecret, "http://localhost", "", "", cache, true, true) + bbConsumer := NewDeprecated(clientID, clientSecret, "http://localhost", "", "", cache, true, true) vcsAuth := sdk.VCSAuth{ AccessToken: currentAccessToken, AccessTokenSecret: currentRefreshToken, diff --git a/engine/vcs/bitbucketcloud/oauth_consumer.go b/engine/vcs/bitbucketcloud/oauth_consumer.go index 492b33626c..ad9716714a 100644 --- a/engine/vcs/bitbucketcloud/oauth_consumer.go +++ b/engine/vcs/bitbucketcloud/oauth_consumer.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/url" - "time" "github.com/rockbears/log" @@ -92,62 +91,39 @@ func (consumer *bitbucketcloudConsumer) RefreshToken(ctx context.Context, refres return resp.AccessToken, resp.RefreshToken, nil } -//keep client in memory -var instancesAuthorizedClient = map[string]*bitbucketcloudClient{} - //GetAuthorized returns an authorized client func (consumer *bitbucketcloudConsumer) GetAuthorizedClient(ctx context.Context, vcsAuth sdk.VCSAuth) (sdk.VCSAuthorizedClient, error) { - token := vcsAuth.PersonalAccessTokens - if vcsAuth.PersonalAccessTokens == "" { // DEPRECATED - token = vcsAuth.AccessToken - } - - createdTime := time.Unix(vcsAuth.AccessTokenCreated, 0) - - c, ok := instancesAuthorizedClient[token] - if createdTime.Add(2 * time.Hour).Before(time.Now()) { - if ok { - delete(instancesAuthorizedClient, token) - } - var newAccessToken string - if vcsAuth.AccessToken != "" { // DEPRECATED - var err error - newAccessToken, _, err = consumer.RefreshToken(ctx, vcsAuth.AccessTokenSecret) - if err != nil { - return nil, sdk.WrapError(err, "cannot refresh token") - } - } - - c = &bitbucketcloudClient{ - PersonalAccessToken: vcsAuth.PersonalAccessTokens, - ClientID: consumer.ClientID, // DEPRECATED - OAuthToken: newAccessToken, // DEPRECATED - RefreshToken: vcsAuth.AccessTokenSecret, // DEPRECATED + if vcsAuth.VCSProject != nil { + c := &bitbucketcloudClient{ + PersonalAccessToken: vcsAuth.VCSProject.Auth["token"], Cache: consumer.Cache, apiURL: consumer.apiURL, uiURL: consumer.uiURL, - DisableStatus: consumer.disableStatus, - DisableStatusDetail: consumer.disableStatusDetail, - proxyURL: consumer.proxyURL, } - instancesAuthorizedClient[newAccessToken] = c - } else { - if !ok { - c = &bitbucketcloudClient{ - PersonalAccessToken: vcsAuth.PersonalAccessTokens, - ClientID: consumer.ClientID, // DEPRECATED - OAuthToken: vcsAuth.AccessToken, // DEPRECATED - RefreshToken: vcsAuth.AccessTokenSecret, // DEPRECATED - Cache: consumer.Cache, - apiURL: consumer.apiURL, - uiURL: consumer.uiURL, - DisableStatus: consumer.disableStatus, - DisableStatusDetail: consumer.disableStatusDetail, - proxyURL: consumer.proxyURL, - } - instancesAuthorizedClient[token] = c + return c, nil + } + + // DEPRECATED VCS + var newAccessToken string + if vcsAuth.AccessToken != "" { + var err error + newAccessToken, _, err = consumer.RefreshToken(ctx, vcsAuth.AccessTokenSecret) + if err != nil { + return nil, sdk.WrapError(err, "cannot refresh token") } } + c := &bitbucketcloudClient{ + ClientID: consumer.ClientID, + OAuthToken: newAccessToken, + RefreshToken: vcsAuth.AccessTokenSecret, + Cache: consumer.Cache, + apiURL: consumer.apiURL, + uiURL: consumer.uiURL, + DisableStatus: consumer.disableStatus, + DisableStatusDetail: consumer.disableStatusDetail, + proxyURL: consumer.proxyURL, + } + return c, nil } diff --git a/engine/vcs/bitbucketserver/bitbucketserver.go b/engine/vcs/bitbucketserver/bitbucketserver.go index b2bb00c521..5cf7d062bc 100644 --- a/engine/vcs/bitbucketserver/bitbucketserver.go +++ b/engine/vcs/bitbucketserver/bitbucketserver.go @@ -11,14 +11,16 @@ import ( // bitbucketClient is a bitbucket wrapper for CDS vcs. interface type bitbucketClient struct { - consumer bitbucketConsumer - accessToken string - accessTokenSecret string - proxyURL string - username string - token string + consumer bitbucketConsumer // DEPRECATED VCS + vcsProject *sdk.VCSProject + accessToken string // DEPRECATED VCS + accessTokenSecret string // DEPRECATED VCS + proxyURL string // DEPRECATED VCS + username string // DEPRECATED VCS + token string // DEPRECATED VCS } +// DEPRECATED VCS //bitbucketConsumer implements vcs.Server and it's used to instantiate a bitbucketClient type bitbucketConsumer struct { ConsumerKey string `json:"consumer_key"` @@ -38,7 +40,18 @@ type bitbucketConsumer struct { } //New creates a new bitbucketConsumer -func New(consumerKey string, privateKey []byte, URL, apiURL, uiURL, proxyURL, username, token string, store cache.Store, disableStatus bool) sdk.VCSServer { +func New(URL, apiURL, uiURL, proxyURL string, store cache.Store) sdk.VCSServer { + return &bitbucketConsumer{ + URL: URL, + apiURL: apiURL, + uiURL: uiURL, + proxyURL: proxyURL, + cache: store, + } +} + +// DEPRECATED VCS +func NewDeprecated(consumerKey string, privateKey []byte, URL, apiURL, uiURL, proxyURL, username, token string, store cache.Store, disableStatus bool) sdk.VCSServer { return &bitbucketConsumer{ ConsumerKey: consumerKey, PrivateKey: privateKey, diff --git a/engine/vcs/bitbucketserver/bitbucketserver_test.go b/engine/vcs/bitbucketserver/bitbucketserver_test.go index 38dccf9329..d89f81f722 100644 --- a/engine/vcs/bitbucketserver/bitbucketserver_test.go +++ b/engine/vcs/bitbucketserver/bitbucketserver_test.go @@ -38,7 +38,7 @@ func getNewConsumer(t *testing.T) sdk.VCSServer { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - ghConsummer := New(consumerKey, []byte(consumerPrivateKey), url, "", "", "", "", "", cache, true) + ghConsummer := NewDeprecated(consumerKey, []byte(consumerPrivateKey), url, "", "", "", "", "", cache, true) return ghConsummer } @@ -65,7 +65,7 @@ func getAuthorizedClient(t *testing.T) sdk.VCSAuthorizedClient { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - consumer := New(consumerKey, []byte(privateKey), url, "", "", "", username, password, cache, true) + consumer := NewDeprecated(consumerKey, []byte(privateKey), url, "", "", "", username, password, cache, true) vcsAuth := sdk.VCSAuth{ AccessToken: token, diff --git a/engine/vcs/bitbucketserver/http.go b/engine/vcs/bitbucketserver/http.go index 8cb8868505..b70b094785 100644 --- a/engine/vcs/bitbucketserver/http.go +++ b/engine/vcs/bitbucketserver/http.go @@ -112,11 +112,13 @@ func (c *bitbucketClient) do(ctx context.Context, method, api, path string, para } var token string - if opts != nil && opts.asUser && c.token != "" { + if c.vcsProject != nil && c.vcsProject.Auth["token"] != "" { + token = c.vcsProject.Auth["token"] + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } else if opts != nil && opts.asUser && c.token != "" { // DEPRECATED VCS token = c.token - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) - } else { - // Deprecated. This section use the vcs configured with oauth2 on a cds project + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } else { // DEPRECATED VCS accessToken := NewAccessToken(c.accessToken, c.accessTokenSecret, nil) token = accessToken.token if err := c.consumer.Sign(req, accessToken); err != nil { diff --git a/engine/vcs/bitbucketserver/oauth_consumer.go b/engine/vcs/bitbucketserver/oauth_consumer.go index a9737f81c0..c1f9747c4d 100644 --- a/engine/vcs/bitbucketserver/oauth_consumer.go +++ b/engine/vcs/bitbucketserver/oauth_consumer.go @@ -73,10 +73,10 @@ func (g *bitbucketConsumer) AuthorizeToken(ctx context.Context, token, verifier //GetAuthorized returns an authorized client func (g *bitbucketConsumer) GetAuthorizedClient(ctx context.Context, vcsAuth sdk.VCSAuth) (sdk.VCSAuthorizedClient, error) { - if vcsAuth.PersonalAccessTokens != "" { + if vcsAuth.VCSProject != nil { return &bitbucketClient{ - consumer: *g, - token: vcsAuth.PersonalAccessTokens, + consumer: *g, + vcsProject: vcsAuth.VCSProject, }, nil } return &bitbucketClient{ diff --git a/engine/vcs/bitbucketserver/oauth_consumer_token.go b/engine/vcs/bitbucketserver/oauth_consumer_token.go index ce52728815..8ef0b7fab0 100644 --- a/engine/vcs/bitbucketserver/oauth_consumer_token.go +++ b/engine/vcs/bitbucketserver/oauth_consumer_token.go @@ -6,6 +6,8 @@ import ( "net/http" "net/url" "strconv" + + "github.com/ovh/cds/sdk" ) // Token is an interface for RequestToken and AccessToken @@ -118,7 +120,7 @@ func ParseRequestToken(reader io.ReadCloser) (*RequestToken, error) { body, err := io.ReadAll(reader) reader.Close() if err != nil { - return nil, err + return nil, sdk.WithStack(err) } return ParseRequestTokenStr(string(body)) @@ -130,7 +132,7 @@ func ParseRequestTokenStr(str string) (*RequestToken, error) { //parse the request token from the body parts, err := url.ParseQuery(str) if err != nil { - return nil, err + return nil, sdk.WithStack(err) } token := RequestToken{} diff --git a/engine/vcs/gerrit/gerrit.go b/engine/vcs/gerrit/gerrit.go index 0e53c4c810..dc118b4642 100644 --- a/engine/vcs/gerrit/gerrit.go +++ b/engine/vcs/gerrit/gerrit.go @@ -32,7 +32,18 @@ type gerritConsumer struct { } // New instantiate a new gerrit consumer -func New(URL string, store cache.Store, disableStatus bool, disableStatusDetail bool, sshPort int, reviewerName, reviewerToken string) sdk.VCSServer { +func New(URL string, store cache.Store, sshPort int, reviewerName, reviewerToken string) sdk.VCSServer { + return &gerritConsumer{ + URL: URL, + cache: store, + sshPort: sshPort, + reviewerName: reviewerName, + reviewerToken: reviewerToken, + } +} + +// DEPRECATED VCS +func NewDeprecated(URL string, store cache.Store, disableStatus bool, disableStatusDetail bool, sshPort int, reviewerName, reviewerToken string) sdk.VCSServer { return &gerritConsumer{ URL: URL, cache: store, diff --git a/engine/vcs/github/github.go b/engine/vcs/github/github.go index 2fc4710acc..dd848ba326 100644 --- a/engine/vcs/github/github.go +++ b/engine/vcs/github/github.go @@ -41,7 +41,31 @@ type githubConsumer struct { } //New creates a new GithubConsumer -func New(ClientID, ClientSecret, githubURL, githubAPIURL, apiURL, uiURL, proxyURL, username, token string, store cache.Store, disableStatus, disableStatusDetail bool) sdk.VCSServer { +func New(githubURL, githubAPIURL, apiURL, uiURL, proxyURL string, store cache.Store) sdk.VCSServer { + //Github const + const ( + publicURL = "https://github.com" + publicAPIURL = "https://api.github.com" + ) + // if the githubURL is passed as an empty string default it to public GitHub + if githubURL == "" { + githubURL = publicURL + } + // if the githubAPIURL is passed as an empty string default it to public GitHub + if githubAPIURL == "" { + githubAPIURL = publicAPIURL + } + return &githubConsumer{ + GitHubURL: githubURL, + GitHubAPIURL: githubAPIURL, + Cache: store, + apiURL: apiURL, + uiURL: uiURL, + proxyURL: proxyURL, + } +} + +func NewDeprecated(ClientID, ClientSecret, githubURL, githubAPIURL, apiURL, uiURL, proxyURL, username, token string, store cache.Store, disableStatus, disableStatusDetail bool) sdk.VCSServer { //Github const const ( publicURL = "https://github.com" diff --git a/engine/vcs/github/github_test.go b/engine/vcs/github/github_test.go index c47945bc33..87c8ca9832 100644 --- a/engine/vcs/github/github_test.go +++ b/engine/vcs/github/github_test.go @@ -40,7 +40,7 @@ func getNewConsumer(t *testing.T) sdk.VCSServer { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - ghConsummer := New(clientID, clientSecret, "", "", "http://localhost", "", "", "", "", cache, true, true) + ghConsummer := NewDeprecated(clientID, clientSecret, "", "", "http://localhost", "", "", "", "", cache, true, true) return ghConsummer } @@ -63,7 +63,7 @@ func getNewAuthorizedClient(t *testing.T) sdk.VCSAuthorizedClient { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - ghConsummer := New(clientID, clientSecret, "", "", "http://localhost", "", "", "", "", cache, true, true) + ghConsummer := NewDeprecated(clientID, clientSecret, "", "", "http://localhost", "", "", "", "", cache, true, true) vcsAuth := sdk.VCSAuth{ AccessToken: accessToken, } diff --git a/engine/vcs/github/oauth_consumer.go b/engine/vcs/github/oauth_consumer.go index 06cb8661b6..25336370f4 100644 --- a/engine/vcs/github/oauth_consumer.go +++ b/engine/vcs/github/oauth_consumer.go @@ -82,30 +82,38 @@ func (g *githubConsumer) AuthorizeToken(ctx context.Context, state, code string) return ghResponse["access_token"], state, nil } -//keep client in memory -var instancesAuthorizedClient = map[string]*githubClient{} - //GetAuthorized returns an authorized client func (g *githubConsumer) GetAuthorizedClient(ctx context.Context, vcsAuth sdk.VCSAuth) (sdk.VCSAuthorizedClient, error) { - token := vcsAuth.PersonalAccessTokens - c, ok := instancesAuthorizedClient[token] - if !ok { - c = &githubClient{ - ClientID: g.ClientID, - OAuthToken: vcsAuth.AccessToken, // DEPRECATED + if vcsAuth.VCSProject != nil { + c := &githubClient{ GitHubURL: g.GitHubURL, GitHubAPIURL: g.GitHubAPIURL, Cache: g.Cache, uiURL: g.uiURL, - DisableStatus: g.disableStatus, - DisableStatusDetail: g.disableStatusDetail, apiURL: g.apiURL, proxyURL: g.proxyURL, - username: g.username, // used by a "cds user on github" to write comment on PR - token: g.token, // used by a "cds user on github" to write comment on PR - personalAccessToken: vcsAuth.PersonalAccessTokens, + username: vcsAuth.VCSProject.Auth["usename"], + personalAccessToken: vcsAuth.VCSProject.Auth["token"], } - instancesAuthorizedClient[token] = c + + return c, c.RateLimit(ctx) } + + // DEPRECATED VCS + c := &githubClient{ + ClientID: g.ClientID, + OAuthToken: vcsAuth.AccessToken, // DEPRECATED + GitHubURL: g.GitHubURL, + GitHubAPIURL: g.GitHubAPIURL, + Cache: g.Cache, + uiURL: g.uiURL, + DisableStatus: g.disableStatus, + DisableStatusDetail: g.disableStatusDetail, + apiURL: g.apiURL, + proxyURL: g.proxyURL, + username: g.username, // used by a "cds user on github" to write comment on PR + token: g.token, // used by a "cds user on github" to write comment on PR + } + return c, c.RateLimit(ctx) } diff --git a/engine/vcs/gitlab/gitlab.go b/engine/vcs/gitlab/gitlab.go index 3347e65254..f293f2c0d6 100644 --- a/engine/vcs/gitlab/gitlab.go +++ b/engine/vcs/gitlab/gitlab.go @@ -35,10 +35,23 @@ type gitlabConsumer struct { proxyURL string disableStatus bool disableStatusDetail bool + username string + personalAccessToken string } // New instantiate a new gitlab consumer -func New(appID, clientSecret, URL, callbackURL, uiURL, proxyURL string, store cache.Store, disableStatus bool, disableStatusDetail bool) sdk.VCSServer { +func New(URL, uiURL, proxyURL string, store cache.Store, username, token string) sdk.VCSServer { + return &gitlabConsumer{ + URL: URL, + cache: store, + uiURL: uiURL, + proxyURL: proxyURL, + username: username, + personalAccessToken: token, + } +} + +func NewDeprecated(appID, clientSecret, URL, callbackURL, uiURL, proxyURL string, store cache.Store, disableStatus bool, disableStatusDetail bool) sdk.VCSServer { return &gitlabConsumer{ URL: URL, secret: clientSecret, diff --git a/engine/vcs/gitlab/gitlab_test.go b/engine/vcs/gitlab/gitlab_test.go index 3ead38b202..667293804a 100644 --- a/engine/vcs/gitlab/gitlab_test.go +++ b/engine/vcs/gitlab/gitlab_test.go @@ -40,7 +40,7 @@ func getNewConsumer(t *testing.T) sdk.VCSServer { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - glConsummer := New(appID, secret, "https://gitlab.com", "http://localhost:8081", "", "", cache, true, true) + glConsummer := NewDeprecated(appID, secret, "https://gitlab.com", "http://localhost:8081", "", "", cache, true, true) return glConsummer } @@ -63,7 +63,7 @@ func getNewAuthorizedClient(t *testing.T) sdk.VCSAuthorizedClient { t.Fatalf("Unable to init cache (%s): %v", redisHost, err) } - glConsummer := New(appID, secret, "https://gitlab.com", "http://localhost:8081", "", "", cache, true, true) + glConsummer := NewDeprecated(appID, secret, "https://gitlab.com", "http://localhost:8081", "", "", cache, true, true) vcsAuth := sdk.VCSAuth{ AccessToken: accessToken, diff --git a/engine/vcs/gitlab/oauth_consumer.go b/engine/vcs/gitlab/oauth_consumer.go index 291d75d9e1..3fbe849737 100644 --- a/engine/vcs/gitlab/oauth_consumer.go +++ b/engine/vcs/gitlab/oauth_consumer.go @@ -119,31 +119,31 @@ func (g *gitlabConsumer) AuthorizeToken(ctx context.Context, state, code string) return glResponse.AccessToken, state, nil } -var instancesAuthorizedClient = map[string]*gitlabClient{} - //GetAuthorized returns an authorized client func (g *gitlabConsumer) GetAuthorizedClient(ctx context.Context, vcsAuth sdk.VCSAuth) (sdk.VCSAuthorizedClient, error) { - token := vcsAuth.PersonalAccessTokens - - c, ok := instancesAuthorizedClient[token] httpClient := &http.Client{ Timeout: 60 * time.Second, } - if !ok { - var gclient *gitlab.Client - if vcsAuth.PersonalAccessTokens != "" { - gclient = gitlab.NewClient(httpClient, vcsAuth.PersonalAccessTokens) - } else { - gclient = gitlab.NewOAuthClient(httpClient, vcsAuth.AccessToken) - } - c = &gitlabClient{ + if vcsAuth.VCSProject != nil { + gclient := gitlab.NewClient(httpClient, vcsAuth.VCSProject.Auth["token"]) + c := &gitlabClient{ client: gclient, uiURL: g.uiURL, disableStatus: g.disableStatus, disableStatusDetail: g.disableStatusDetail, } c.client.SetBaseURL(g.URL + "/api/v4") - instancesAuthorizedClient[token] = c + return c, nil + } + + // DEPRECATED VCS + gclient := gitlab.NewOAuthClient(httpClient, vcsAuth.AccessToken) + c := &gitlabClient{ + client: gclient, + uiURL: g.uiURL, + disableStatus: g.disableStatus, + disableStatusDetail: g.disableStatusDetail, } + c.client.SetBaseURL(g.URL + "/api/v4") return c, nil } diff --git a/engine/vcs/types.go b/engine/vcs/types.go index 26c9824dfa..799c7de179 100644 --- a/engine/vcs/types.go +++ b/engine/vcs/types.go @@ -36,7 +36,8 @@ type Configuration struct { Password string `toml:"password" json:"-"` } `toml:"redis" json:"redis"` } `toml:"cache" comment:"######################\n CDS VCS Cache Settings \n######################" json:"cache"` - Servers map[string]ServerConfiguration `toml:"servers" comment:"######################\n CDS VCS Server Settings \n######################" json:"servers"` + ProxyWebhook string `toml:"proxyWebhook" default:"" commented:"true" comment:"If you want to have a reverse proxy url for your repository webhook, for example if you put https://myproxy.com it will generate a webhook URL like this https://myproxy.com/UUID_OF_YOUR_WEBHOOK" json:"proxy_webhook"` + Servers map[string]ServerConfiguration `toml:"servers" comment:"######################\n CDS VCS Server Settings \n######################" json:"servers"` } // ServerConfiguration is the configuration for a VCS server diff --git a/engine/vcs/vcs.go b/engine/vcs/vcs.go index d68c1d7c69..82548b97ca 100644 --- a/engine/vcs/vcs.go +++ b/engine/vcs/vcs.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "net/http" + "strconv" + "strings" "time" "github.com/gorilla/mux" @@ -80,14 +82,66 @@ func (s *Service) CheckConfiguration(config interface{}) error { return nil } -func (s *Service) getConsumer(name string) (sdk.VCSServer, error) { +func (s *Service) getConsumer(name string, vcsAuth sdk.VCSAuth) (sdk.VCSServer, error) { + if vcsAuth.VCSProject != nil { + switch vcsAuth.VCSProject.Type { + case "bitbucketcloud": + return bitbucketcloud.New( + strings.TrimSuffix(vcsAuth.VCSProject.URL, "/"), + s.UI.HTTP.URL, + s.Cfg.ProxyWebhook, + s.Cache, + ), nil + case "bitbucketserver": + return bitbucketserver.New( + strings.TrimSuffix(vcsAuth.VCSProject.URL, "/"), + s.Cfg.API.HTTP.URL, + s.UI.HTTP.URL, + s.Cfg.ProxyWebhook, + s.Cache, + ), nil + case "gerrit": + sshPort, err := strconv.Atoi(vcsAuth.VCSProject.Auth["ssh-port"]) + if err != nil { + return nil, sdk.WrapError(err, "invalid gerrit ssh port configuration") + } + return gerrit.New( + vcsAuth.VCSProject.URL, + s.Cache, + sshPort, + vcsAuth.VCSProject.Auth["reviewer-user"], + vcsAuth.VCSProject.Auth["reviewer-token"], + ), nil + case "github": + return github.New( + vcsAuth.VCSProject.URL, + vcsAuth.VCSProject.Auth["github-api-url"], + s.Cfg.API.HTTP.URL, + s.UI.HTTP.URL, + s.Cfg.ProxyWebhook, + s.Cache, + ), nil + case "gitlab": + return gitlab.New( + vcsAuth.VCSProject.URL, + s.UI.HTTP.URL, + s.Cfg.ProxyWebhook, + s.Cache, + vcsAuth.VCSProject.Auth["username"], + vcsAuth.VCSProject.Auth["token"], + ), nil + } + return nil, sdk.WithStack(sdk.ErrNotFound) + } + + // DEPRECATED VCS serverCfg, has := s.Cfg.Servers[name] if !has { return nil, sdk.WithStack(sdk.ErrNotFound) } if serverCfg.Github != nil { - return github.New( + return github.NewDeprecated( serverCfg.Github.ClientID, serverCfg.Github.ClientSecret, serverCfg.URL, @@ -103,9 +157,9 @@ func (s *Service) getConsumer(name string) (sdk.VCSServer, error) { ), nil } if serverCfg.Bitbucket != nil { - return bitbucketserver.New(serverCfg.Bitbucket.ConsumerKey, + return bitbucketserver.NewDeprecated(serverCfg.Bitbucket.ConsumerKey, []byte(serverCfg.Bitbucket.PrivateKey), - serverCfg.URL, + strings.TrimSuffix(serverCfg.URL, "/"), s.Cfg.API.HTTP.URL, s.UI.HTTP.URL, serverCfg.Bitbucket.ProxyWebhook, @@ -116,7 +170,7 @@ func (s *Service) getConsumer(name string) (sdk.VCSServer, error) { ), nil } if serverCfg.BitbucketCloud != nil { - return bitbucketcloud.New(serverCfg.BitbucketCloud.ClientID, + return bitbucketcloud.NewDeprecated(serverCfg.BitbucketCloud.ClientID, serverCfg.BitbucketCloud.ClientSecret, serverCfg.URL, s.UI.HTTP.URL, @@ -127,7 +181,7 @@ func (s *Service) getConsumer(name string) (sdk.VCSServer, error) { ), nil } if serverCfg.Gitlab != nil { - return gitlab.New(serverCfg.Gitlab.AppID, + return gitlab.NewDeprecated(serverCfg.Gitlab.AppID, serverCfg.Gitlab.Secret, serverCfg.URL, serverCfg.Gitlab.CallbackURL, @@ -139,7 +193,7 @@ func (s *Service) getConsumer(name string) (sdk.VCSServer, error) { ), nil } if serverCfg.Gerrit != nil { - return gerrit.New( + return gerrit.NewDeprecated( serverCfg.URL, s.Cache, serverCfg.Gerrit.Status.Disable, diff --git a/engine/vcs/vcs_auth.go b/engine/vcs/vcs_auth.go index 7e2b98b288..3038d4a869 100644 --- a/engine/vcs/vcs_auth.go +++ b/engine/vcs/vcs_auth.go @@ -3,6 +3,7 @@ package vcs import ( "context" "encoding/base64" + "encoding/json" "fmt" "net/http" "strconv" @@ -15,13 +16,29 @@ import ( type contextKey string var ( - contextKeyPersonalAccessToken contextKey = "personnal-access-token" - contextKeyAccessToken contextKey = "access-token" - contextKeyAccessTokenCreated contextKey = "access-token-created" - contextKeyAccessTokenSecret contextKey = "access-token-secret" + contextKeyVCSProjectConf contextKey = "vcs-project-conf" + contextKeyAccessToken contextKey = "access-token" + contextKeyAccessTokenCreated contextKey = "access-token-created" + contextKeyAccessTokenSecret contextKey = "access-token-secret" ) func (s *Service) authMiddleware(ctx context.Context, w http.ResponseWriter, req *http.Request, rc *service.HandlerConfig) (context.Context, error) { + encodedVCSProjectConf := req.Header.Get(sdk.HeaderXVCSProjectConf) + if encodedVCSProjectConf != "" { + vcsProjectConf, err := base64.StdEncoding.DecodeString(encodedVCSProjectConf) + if err != nil { + return ctx, fmt.Errorf("bad header syntax: %s", err) + } + if len(vcsProjectConf) != 0 { + var vcsProject sdk.VCSProject + if err := json.Unmarshal(vcsProjectConf, &vcsProject); err != nil { + return nil, sdk.WrapError(sdk.ErrUnauthorized, "invalid vcs project configuration err:%v", err) + } + ctx = context.WithValue(ctx, contextKeyVCSProjectConf, vcsProject) + } + return ctx, nil + } + encodedAccessToken := req.Header.Get(sdk.HeaderXAccessToken) accessToken, err := base64.StdEncoding.DecodeString(encodedAccessToken) if err != nil { @@ -52,17 +69,15 @@ func (s *Service) authMiddleware(ctx context.Context, w http.ResponseWriter, req return ctx, nil } -func getAccessTokens(ctx context.Context) (sdk.VCSAuth, error) { +func getVCSAuth(ctx context.Context) (sdk.VCSAuth, error) { var vcsAuth sdk.VCSAuth - - personalAccessTokens, _ := ctx.Value(contextKeyPersonalAccessToken).(string) - vcsAuth.PersonalAccessTokens = personalAccessTokens - - if vcsAuth.PersonalAccessTokens != "" { + vcsProject, ok := ctx.Value(contextKeyVCSProjectConf).(sdk.VCSProject) + if ok { + vcsAuth.VCSProject = &vcsProject return vcsAuth, nil } - // DEPRECATED + // DEPRECATED VCS accessToken, _ := ctx.Value(contextKeyAccessToken).(string) vcsAuth.AccessToken = accessToken @@ -73,7 +88,7 @@ func getAccessTokens(ctx context.Context) (sdk.VCSAuth, error) { if accessTokenCreated != "" { created, err := strconv.ParseInt(accessTokenCreated, 10, 64) if err != nil { - return vcsAuth, sdk.WrapError(sdk.ErrUnauthorized, "invalid token created header: %v err:%v", accessTokenCreated, err) + return sdk.VCSAuth{}, sdk.WrapError(sdk.ErrUnauthorized, "invalid token created header: %v err:%v", accessTokenCreated, err) } vcsAuth.AccessTokenCreated = created } @@ -84,5 +99,5 @@ func getAccessTokens(ctx context.Context) (sdk.VCSAuth, error) { return vcsAuth, nil } - return vcsAuth, sdk.WrapError(sdk.ErrUnauthorized, "invalid access token headers") + return sdk.VCSAuth{}, sdk.WrapError(sdk.ErrUnauthorized, "invalid access token headers") } diff --git a/engine/vcs/vcs_handlers.go b/engine/vcs/vcs_handlers.go index 9ef31731c5..44377a64c6 100644 --- a/engine/vcs/vcs_handlers.go +++ b/engine/vcs/vcs_handlers.go @@ -83,6 +83,61 @@ func (s *Service) getVCSServersHandler() service.Handler { func (s *Service) getVCSServersHooksHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + vcsAuth, err := getVCSAuth(ctx) + if err != nil { + return sdk.WrapError(err, "unable to get access token header") + } + + if vcsAuth.VCSProject != nil { + res := struct { + WebhooksSupported bool `json:"webhooks_supported"` + WebhooksDisabled bool `json:"webhooks_disabled"` + WebhooksIcon string `json:"webhooks_icon"` + GerritHookDisabled bool `json:"gerrithook_disabled"` + Events []string `json:"events"` + }{} + + switch { + case vcsAuth.VCSProject.Type == "bitbucketserver": + res.WebhooksSupported = true + res.WebhooksIcon = sdk.BitbucketIcon + // https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html + res.Events = sdk.BitbucketEvents + case vcsAuth.VCSProject.Type == "bitbucketcloud": + res.WebhooksSupported = true + res.WebhooksIcon = sdk.BitbucketIcon + // https://developer.atlassian.com/bitbucket/api/2/reference/resource/hook_events/%7Bsubject_type%7D + res.Events = sdk.BitbucketCloudEvents + case vcsAuth.VCSProject.Type == "github": + res.WebhooksSupported = true + res.WebhooksIcon = sdk.GitHubIcon + // https://developer.github.com/v3/activity/events/types/ + res.Events = sdk.GitHubEvents + case vcsAuth.VCSProject.Type == "gitlab": + res.WebhooksSupported = true + res.WebhooksIcon = sdk.GitlabIcon + // https://docs.gitlab.com/ee/user/project/integrations/webhooks.html + res.Events = []string{ + string(gitlab.EventTypePush), + string(gitlab.EventTypeTagPush), + string(gitlab.EventTypeIssue), + string(gitlab.EventTypeNote), + string(gitlab.EventTypeMergeRequest), + string(gitlab.EventTypeWikiPage), + string(gitlab.EventTypePipeline), + "Job Hook", // TODO update gitlab sdk + } + case vcsAuth.VCSProject.Type == "gerrit": + res.WebhooksSupported = false + res.WebhooksIcon = sdk.GerritIcon + // https://git.eclipse.org/r/Documentation/cmd-stream-events.html#events + res.Events = sdk.GerritEvents + } + + return service.WriteJSON(w, res, http.StatusOK) + } + + // DEPRECATED VCS name := muxVar(r, "name") cfg, ok := s.Cfg.Servers[name] if !ok { @@ -144,6 +199,32 @@ func (s *Service) getVCSServersHooksHandler() service.Handler { func (s *Service) getVCSServersPollingHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + vcsAuth, err := getVCSAuth(ctx) + if err != nil { + return sdk.WrapError(err, "unable to get access token header") + } + + if vcsAuth.VCSProject != nil { + res := struct { + PollingSupported bool `json:"polling_supported"` + PollingDisabled bool `json:"polling_disabled"` + }{} + + switch { + case vcsAuth.VCSProject.Type == "bitbucketserver": + res.PollingSupported = false + case vcsAuth.VCSProject.Type == "bitbucketcloud": + res.PollingSupported = false + case vcsAuth.VCSProject.Type == "github": + res.PollingSupported = true + case vcsAuth.VCSProject.Type == "gitlab": + res.PollingSupported = false + } + + return service.WriteJSON(w, res, http.StatusOK) + } + + // DEPRECATED VCS name := muxVar(r, "name") cfg, ok := s.Cfg.Servers[name] if !ok { @@ -175,7 +256,7 @@ func (s *Service) getVCSServersPollingHandler() service.Handler { func (s *Service) getAuthorizeHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { name := muxVar(r, "name") - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, sdk.VCSAuth{}) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s", name) } @@ -192,10 +273,10 @@ func (s *Service) getAuthorizeHandler() service.Handler { } } -func (s *Service) postAuhorizeHandler() service.Handler { +func (s *Service) postAuthorizeHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { name := muxVar(r, "name") - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, sdk.VCSAuth{}) if err != nil { return sdk.WrapError(err, "VCS server unavailable") } @@ -221,12 +302,12 @@ func (s *Service) getReposHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { name := muxVar(r, "name") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { - return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") + return sdk.WrapError(err, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable") } @@ -255,12 +336,12 @@ func (s *Service) getRepoHandler() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -299,12 +380,12 @@ func (s *Service) getBranchesHandler() service.Handler { limit = int64(l) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -340,12 +421,12 @@ func (s *Service) getBranchHandler() service.Handler { defaultBranch = true } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -375,12 +456,12 @@ func (s *Service) getTagsHandler() service.Handler { log.Debug(ctx, "getTagsHandler>") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -413,12 +494,12 @@ func (s *Service) getCommitsHandler() service.Handler { log.Debug(ctx, "getCommitsHandler>") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -450,12 +531,12 @@ func (s *Service) getCommitsBetweenRefsHandler() service.Handler { log.Debug(ctx, "getCommitsBetweenRefsHandler>") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -484,12 +565,12 @@ func (s *Service) getCommitHandler() service.Handler { repo := muxVar(r, "repo") commit := muxVar(r, "commit") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -518,12 +599,12 @@ func (s *Service) getCommitStatusHandler() service.Handler { repo := muxVar(r, "repo") commit := muxVar(r, "commit") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -553,12 +634,12 @@ func (s *Service) getPullRequestHandler() service.Handler { repo := muxVar(r, "repo") id := muxVar(r, "id") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -592,12 +673,12 @@ func (s *Service) getPullRequestsHandler() service.Handler { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid given pull request state %s", state) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -632,12 +713,12 @@ func (s *Service) postPullRequestsHandler() service.Handler { return sdk.WithStack(err) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -670,12 +751,12 @@ func (s *Service) postPullRequestCommentHandler() service.Handler { return sdk.WithStack(err) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -713,12 +794,12 @@ func (s *Service) getEventsHandler() service.Handler { dateRef = time.Unix(int64(dateRefInt), 0) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -758,12 +839,12 @@ func (s *Service) postFilterEventsHandler() service.Handler { return sdk.WrapError(err, "Unable to read body") } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -819,12 +900,12 @@ func (s *Service) postStatusHandler() service.Handler { return sdk.WrapError(err, "Unable to read body") } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable") } @@ -852,12 +933,12 @@ func (s *Service) postReleaseHandler() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -913,12 +994,12 @@ func (s *Service) postUploadReleaseFileHandler() service.Handler { return sdk.WithStack(err) } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -950,12 +1031,12 @@ func (s *Service) getHookHandler() service.Handler { return err } - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -984,12 +1065,12 @@ func (s *Service) putHookHandler() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -1021,12 +1102,12 @@ func (s *Service) postHookHandler() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -1065,12 +1146,12 @@ func (s *Service) deleteHookHandler() service.Handler { hookID := r.URL.Query().Get("id") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -1108,12 +1189,12 @@ func (s *Service) getListForks() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } @@ -1160,12 +1241,12 @@ func (s *Service) postRepoGrantHandler() service.Handler { owner := muxVar(r, "owner") repo := muxVar(r, "repo") - vcsAuth, err := getAccessTokens(ctx) + vcsAuth, err := getVCSAuth(ctx) if err != nil { return sdk.WrapError(sdk.ErrUnauthorized, "unable to get access token header") } - consumer, err := s.getConsumer(name) + consumer, err := s.getConsumer(name, vcsAuth) if err != nil { return sdk.WrapError(err, "VCS server unavailable %s %s/%s", name, owner, repo) } diff --git a/engine/vcs/vcs_router.go b/engine/vcs/vcs_router.go index 162df3d831..2916bfe6c0 100644 --- a/engine/vcs/vcs_router.go +++ b/engine/vcs/vcs_router.go @@ -29,7 +29,7 @@ func (s *Service) initRouter(ctx context.Context) { r.Handle("/vcs/{name}/webhooks", nil, r.GET(s.getVCSServersHooksHandler)) r.Handle("/vcs/{name}/polling", nil, r.GET(s.getVCSServersPollingHandler)) - r.Handle("/vcs/{name}/authorize", nil, r.GET(s.getAuthorizeHandler), r.POST(s.postAuhorizeHandler)) + r.Handle("/vcs/{name}/authorize", nil, r.GET(s.getAuthorizeHandler), r.POST(s.postAuthorizeHandler)) r.Handle("/vcs/{name}/repos", nil, r.GET(s.getReposHandler)) r.Handle("/vcs/{name}/repos/{owner}/{repo}", nil, r.GET(s.getRepoHandler)) diff --git a/sdk/project.go b/sdk/project.go index 93c11bbe0a..b7b3701a79 100644 --- a/sdk/project.go +++ b/sdk/project.go @@ -35,26 +35,26 @@ type Project struct { Created time.Time `json:"created" yaml:"created" db:"created" ` LastModified time.Time `json:"last_modified" yaml:"last_modified" db:"last_modified"` // aggregates - Workflows []Workflow `json:"workflows,omitempty" yaml:"workflows,omitempty" db:"-" cli:"-"` - WorkflowNames IDNames `json:"workflow_names,omitempty" yaml:"workflow_names,omitempty" db:"-" cli:"-"` - Pipelines []Pipeline `json:"pipelines,omitempty" yaml:"pipelines,omitempty" db:"-" cli:"-"` - PipelineNames IDNames `json:"pipeline_names,omitempty" yaml:"pipeline_names,omitempty" db:"-" cli:"-"` - Applications []Application `json:"applications,omitempty" yaml:"applications,omitempty" db:"-" cli:"-"` - ApplicationNames IDNames `json:"application_names,omitempty" yaml:"application_names,omitempty" db:"-" cli:"-"` - ProjectGroups GroupPermissions `json:"groups,omitempty" yaml:"permissions,omitempty" db:"-" cli:"-"` - Variables []ProjectVariable `json:"variables,omitempty" yaml:"variables,omitempty" db:"-" cli:"-"` - Environments []Environment `json:"environments,omitempty" yaml:"environments,omitempty" db:"-" cli:"-"` - EnvironmentNames IDNames `json:"environment_names,omitempty" yaml:"environment_names,omitempty" db:"-" cli:"-"` - Labels []Label `json:"labels,omitempty" yaml:"labels,omitempty" db:"-" cli:"-"` - Permissions Permissions `json:"permissions" yaml:"-" db:"-" cli:"-"` - Metadata Metadata `json:"metadata" yaml:"metadata" db:"metadata" cli:"-"` - Keys []ProjectKey `json:"keys,omitempty" yaml:"keys" db:"-" cli:"-"` - VCSServers []ProjectVCSServerLink `json:"vcs_servers" yaml:"vcs_servers" db:"-" cli:"-"` - Integrations []ProjectIntegration `json:"integrations" yaml:"integrations" db:"-" cli:"-"` - Features map[string]bool `json:"features" yaml:"features" db:"-" cli:"-"` - Favorite bool `json:"favorite" yaml:"favorite" db:"-" cli:"favorite"` - URLs URL `json:"urls" yaml:"-" db:"-" cli:"-"` - Organization string `json:"organization" yaml:"-" db:"-" cli:"-"` + Workflows []Workflow `json:"workflows,omitempty" yaml:"workflows,omitempty" db:"-" cli:"-"` + WorkflowNames IDNames `json:"workflow_names,omitempty" yaml:"workflow_names,omitempty" db:"-" cli:"-"` + Pipelines []Pipeline `json:"pipelines,omitempty" yaml:"pipelines,omitempty" db:"-" cli:"-"` + PipelineNames IDNames `json:"pipeline_names,omitempty" yaml:"pipeline_names,omitempty" db:"-" cli:"-"` + Applications []Application `json:"applications,omitempty" yaml:"applications,omitempty" db:"-" cli:"-"` + ApplicationNames IDNames `json:"application_names,omitempty" yaml:"application_names,omitempty" db:"-" cli:"-"` + ProjectGroups GroupPermissions `json:"groups,omitempty" yaml:"permissions,omitempty" db:"-" cli:"-"` + Variables []ProjectVariable `json:"variables,omitempty" yaml:"variables,omitempty" db:"-" cli:"-"` + Environments []Environment `json:"environments,omitempty" yaml:"environments,omitempty" db:"-" cli:"-"` + EnvironmentNames IDNames `json:"environment_names,omitempty" yaml:"environment_names,omitempty" db:"-" cli:"-"` + Labels []Label `json:"labels,omitempty" yaml:"labels,omitempty" db:"-" cli:"-"` + Permissions Permissions `json:"permissions" yaml:"-" db:"-" cli:"-"` + Metadata Metadata `json:"metadata" yaml:"metadata" db:"metadata" cli:"-"` + Keys []ProjectKey `json:"keys,omitempty" yaml:"keys" db:"-" cli:"-"` + VCSServers []VCSProject `json:"vcs_servers" yaml:"vcs_servers" db:"-" cli:"-"` + Integrations []ProjectIntegration `json:"integrations" yaml:"integrations" db:"-" cli:"-"` + Features map[string]bool `json:"features" yaml:"features" db:"-" cli:"-"` + Favorite bool `json:"favorite" yaml:"favorite" db:"-" cli:"favorite"` + URLs URL `json:"urls" yaml:"-" db:"-" cli:"-"` + Organization string `json:"organization" yaml:"-" db:"-" cli:"-"` } type GroupPermissions []GroupPermission diff --git a/sdk/vcs.go b/sdk/vcs.go index cf12b46b68..bbbb2cbaf1 100644 --- a/sdk/vcs.go +++ b/sdk/vcs.go @@ -6,13 +6,16 @@ import ( "io" "net/http" "time" + + "github.com/go-gorp/gorp" ) // HTTP Headers const ( - HeaderXAccessToken = "X-CDS-ACCESS-TOKEN" - HeaderXAccessTokenCreated = "X-CDS-ACCESS-TOKEN-CREATED" - HeaderXAccessTokenSecret = "X-CDS-ACCESS-TOKEN-SECRET" + HeaderXVCSProjectConf = "X-CDS-PERSONAL-ACCESS-TOKEN" + HeaderXAccessToken = "X-CDS-ACCESS-TOKEN" // DEPRECATED + HeaderXAccessTokenCreated = "X-CDS-ACCESS-TOKEN-CREATED" // DEPRECATED + HeaderXAccessTokenSecret = "X-CDS-ACCESS-TOKEN-SECRET" // DEPRECATED ) var ( @@ -188,7 +191,7 @@ type VCSProject struct { ProjectID int64 `json:"-" db:"project_id"` Description string `json:"description" db:"description"` URL string `json:"url" db:"url"` - Auth map[string]string `json:"-" db:"auth" gorpmapping:"encrypted,ProjectID"` + Auth map[string]string `json:"auth" db:"auth" gorpmapping:"encrypted,ProjectID"` } // VCSConfiguration represent a small vcs configuration @@ -207,7 +210,7 @@ type VCSServerCommon interface { // VCSAuth contains tokens (oauth2 tokens or personalAccessToken) type VCSAuth struct { - PersonalAccessTokens string // DEPRECATED + VCSProject *VCSProject AccessToken string // DEPRECATED AccessTokenSecret string // DEPRECATED @@ -296,6 +299,7 @@ type VCSAuthorizedClient interface { type VCSAuthorizedClientService interface { VCSAuthorizedClientCommon PullRequests(ctx context.Context, repo string, mods ...VCSRequestModifier) ([]VCSPullRequest, error) + IsGerrit(ctx context.Context, db gorp.SqlExecutor) (bool, error) } type VCSRequestModifier func(r *http.Request)