Require approval from someone other than PR author

In Bitbucket cloud, the pull request author can approve their own pull
request. Ignore this approval.

Fixes #201
This commit is contained in:
Luke Kysow
2018-07-28 06:57:49 -07:00
parent fc6b4a12eb
commit ff85cbef70
8 changed files with 728 additions and 2 deletions

View File

@@ -15,7 +15,12 @@ projects:
A pull request approval might not be as secure as you'd expect:
* In GitHub **any user with read permissions** to the repo can approve a pull request.
* In GitLab, you [can set](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#editing-approvals) who is allowed to approve.
* In Bitbucket, a user can **approve their own pull request**.
:::
::: tip Note
In Bitbucket Cloud (bitbucket.org), a user can approve their own pull request.
Atlantis does not count that as an approval and requires at least one user that
is not the author of the pull request to approve it.
:::
## Next Steps

View File

@@ -110,7 +110,9 @@ func (b *Client) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool
return false, errors.Wrapf(err, "API response %q was missing fields", string(resp))
}
for _, participant := range pullResp.Participants {
if *participant.Approved {
// Bitbucket allows the author to approve their own pull request. This
// defeats the purpose of approvals so we don't count that approval.
if *participant.Approved && *participant.User.Username != pull.Author {
return true, nil
}
}

View File

@@ -2,8 +2,10 @@ package bitbucketcloud_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/runatlantis/atlantis/server/events/models"
@@ -148,3 +150,66 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) {
Ok(t, err)
Equals(t, []string{"parent/child/file1.txt"}, files)
}
func TestClient_PullIsApproved(t *testing.T) {
cases := []struct {
description string
testdata string
exp bool
}{
{
"no approvers",
"pull-unapproved.json",
false,
},
{
"approver is the author",
"pull-approved-by-author.json",
false,
},
{
"single approver",
"pull-approved.json",
true,
},
{
"two approvers one author",
"pull-approved-multiple.json",
true,
},
}
for _, c := range cases {
t.Run(c.description, func(t *testing.T) {
json, err := ioutil.ReadFile(filepath.Join("testdata", c.testdata))
Ok(t, err)
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.RequestURI {
// The first request should hit this URL.
case "/2.0/repositories/owner/repo/pullrequests/1":
w.Write([]byte(json)) // nolint: errcheck
return
default:
t.Errorf("got unexpected request at %q", r.RequestURI)
http.Error(w, "not found", http.StatusNotFound)
return
}
}))
defer testServer.Close()
client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client.BaseURL = testServer.URL
repo, err := models.NewRepo(models.BitbucketServer, "owner/repo", "https://bitbucket.org/owner/repo.git", "user", "token")
Ok(t, err)
approved, err := client.PullIsApproved(repo, models.PullRequest{
Num: 1,
Branch: "branch",
Author: "author",
BaseRepo: repo,
})
Ok(t, err)
Equals(t, c.exp, approved)
})
}
}

View File

@@ -59,6 +59,9 @@ type Link struct {
}
type Participant struct {
Approved *bool `json:"approved,omitempty" validate:"required"`
User *struct {
Username *string `json:"username,omitempty" validate:"required"`
} `json:"user,omitempty" validate:"required"`
}
type Source struct {
Repository *Repository `json:"repository,omitempty" validate:"required"`

View File

@@ -0,0 +1,166 @@
{
"type": "pullrequest",
"description": "main.tf edited online with Bitbucket",
"links": {
"decline": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline"
},
"commits": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits"
},
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5"
},
"comments": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments"
},
"merge": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5"
},
"activity": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity"
},
"diff": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff"
},
"approve": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve"
},
"statuses": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses"
}
},
"title": "main.tf edited online with Bitbucket",
"close_source_branch": true,
"reviewers": [],
"id": 5,
"destination": {
"commit": {
"hash": "fe607a7f5172",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
},
"avatar": {
"href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "master"
}
},
"created_on": "2018-07-25T12:23:21.100810+00:00",
"summary": {
"raw": "main.tf edited online with Bitbucket",
"markup": "markdown",
"html": "<p>main.tf edited online with Bitbucket</p>",
"type": "rendered"
},
"source": {
"commit": {
"hash": "3428957ade18",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
},
"avatar": {
"href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289"
}
},
"comment_count": 3,
"state": "OPEN",
"task_count": 0,
"participants": [
{
"role": "PARTICIPANT",
"participated_on": "2018-07-28T00:06:42.255492+00:00",
"type": "participant",
"approved": true,
"user": {
"username": "author",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
}
}
],
"reason": "",
"updated_on": "2018-07-28T00:06:42.257659+00:00",
"author": {
"username": "lkysow",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
},
"merge_commit": null,
"closed_by": null
}

View File

@@ -0,0 +1,190 @@
{
"type": "pullrequest",
"description": "main.tf edited online with Bitbucket",
"links": {
"decline": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline"
},
"commits": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits"
},
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5"
},
"comments": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments"
},
"merge": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5"
},
"activity": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity"
},
"diff": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff"
},
"approve": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve"
},
"statuses": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses"
}
},
"title": "main.tf edited online with Bitbucket",
"close_source_branch": true,
"reviewers": [],
"id": 5,
"destination": {
"commit": {
"hash": "fe607a7f5172",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
},
"avatar": {
"href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "master"
}
},
"created_on": "2018-07-25T12:23:21.100810+00:00",
"summary": {
"raw": "main.tf edited online with Bitbucket",
"markup": "markdown",
"html": "<p>main.tf edited online with Bitbucket</p>",
"type": "rendered"
},
"source": {
"commit": {
"hash": "3428957ade18",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
},
"avatar": {
"href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289"
}
},
"comment_count": 3,
"state": "OPEN",
"task_count": 0,
"participants": [
{
"role": "PARTICIPANT",
"participated_on": "2018-07-28T00:06:42.255492+00:00",
"type": "participant",
"approved": true,
"user": {
"username": "author",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
}
},
{
"role": "PARTICIPANT",
"participated_on": "2018-07-28T00:06:42.255492+00:00",
"type": "participant",
"approved": true,
"user": {
"username": "approver",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
}
}
],
"reason": "",
"updated_on": "2018-07-28T00:06:42.257659+00:00",
"author": {
"username": "lkysow",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
},
"merge_commit": null,
"closed_by": null
}

View File

@@ -0,0 +1,160 @@
{
"type": "pullrequest",
"description": "main.tf edited online with Bitbucket",
"links": {
"decline": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline"
},
"commits": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits"
},
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5"
},
"comments": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments"
},
"merge": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5"
},
"activity": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity"
},
"diff": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff"
},
"approve": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve"
},
"statuses": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses"
}
},
"title": "main.tf edited online with Bitbucket",
"close_source_branch": true,
"reviewers": [],
"id": 5,
"destination": {
"commit": {
"hash": "fe607a7f5172",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "master"
}
},
"created_on": "2018-07-25T12:23:21.100810+00:00",
"summary": {
"raw": "main.tf edited online with Bitbucket",
"markup": "markdown",
"html": "<p>main.tf edited online with Bitbucket</p>",
"type": "rendered"
},
"source": {
"commit": {
"hash": "3428957ade18",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289"
}
},
"comment_count": 3,
"state": "OPEN",
"task_count": 0,
"participants": [
{
"role": "PARTICIPANT",
"participated_on": "2018-07-28T00:06:42.255492+00:00",
"type": "participant",
"approved": true,
"user": {
"username": "approver",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
}
}
],
"reason": "",
"updated_on": "2018-07-28T00:06:42.257659+00:00",
"author": {
"username": "lkysow",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
},
"merge_commit": null,
"closed_by": null
}

View File

@@ -0,0 +1,135 @@
{
"type": "pullrequest",
"description": "main.tf edited online with Bitbucket",
"links": {
"decline": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline"
},
"commits": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits"
},
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5"
},
"comments": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments"
},
"merge": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5"
},
"activity": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity"
},
"diff": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff"
},
"approve": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve"
},
"statuses": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses"
}
},
"title": "main.tf edited online with Bitbucket",
"close_source_branch": true,
"reviewers": [],
"id": 5,
"destination": {
"commit": {
"hash": "fe607a7f5172",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "master"
}
},
"created_on": "2018-07-25T12:23:21.100810+00:00",
"summary": {
"raw": "main.tf edited online with Bitbucket",
"markup": "markdown",
"html": "<p>main.tf edited online with Bitbucket</p>",
"type": "rendered"
},
"source": {
"commit": {
"hash": "3428957ade18",
"type": "commit",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18"
}
}
},
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example"
},
"html": {
"href": "https://bitbucket.org/lkysow/atlantis-example"
}
},
"type": "repository",
"name": "atlantis-example",
"full_name": "lkysow/atlantis-example",
"uuid": "{94189367-116b-436a-9f77-2314b97a6067}"
},
"branch": {
"name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289"
}
},
"comment_count": 3,
"state": "OPEN",
"task_count": 0,
"participants": [],
"reason": "",
"updated_on": "2018-07-28T00:06:42.257659+00:00",
"author": {
"username": "lkysow",
"display_name": "Luke",
"account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/users/lkysow"
},
"html": {
"href": "https://bitbucket.org/lkysow/"
},
"avatar": {
"href": "https://bitbucket.org/account/lkysow/avatar/"
}
},
"type": "user",
"uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}"
},
"merge_commit": null,
"closed_by": null
}