mirror of
https://github.com/harness/drone.git
synced 2025-05-11 14:40:05 +08:00
Merge pull request #820 from dcarley/github_whitelist_orgs
Support org whitelists for GitHub+GHE remotes
This commit is contained in:
commit
f79762177c
@ -100,12 +100,14 @@ open=true
|
|||||||
[github]
|
[github]
|
||||||
client=""
|
client=""
|
||||||
secret=""
|
secret=""
|
||||||
|
orgs=[]
|
||||||
|
|
||||||
[github_enterprise]
|
[github_enterprise]
|
||||||
client=""
|
client=""
|
||||||
secret=""
|
secret=""
|
||||||
api=""
|
api=""
|
||||||
url=""
|
url=""
|
||||||
|
orgs=[]
|
||||||
private_mode=false
|
private_mode=false
|
||||||
|
|
||||||
[bitbucket]
|
[bitbucket]
|
||||||
|
@ -37,12 +37,14 @@ datasource="/var/lib/drone/drone.sqlite"
|
|||||||
# [github]
|
# [github]
|
||||||
# client=""
|
# client=""
|
||||||
# secret=""
|
# secret=""
|
||||||
|
# orgs=[]
|
||||||
|
|
||||||
# [github_enterprise]
|
# [github_enterprise]
|
||||||
# client=""
|
# client=""
|
||||||
# secret=""
|
# secret=""
|
||||||
# api=""
|
# api=""
|
||||||
# url=""
|
# url=""
|
||||||
|
# orgs=[]
|
||||||
# private_mode=false
|
# private_mode=false
|
||||||
|
|
||||||
# [bitbucket]
|
# [bitbucket]
|
||||||
|
@ -27,9 +27,10 @@ type GitHub struct {
|
|||||||
Secret string
|
Secret string
|
||||||
Private bool
|
Private bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
|
Orgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(url, api, client, secret string, private, skipVerify bool) *GitHub {
|
func New(url, api, client, secret string, private, skipVerify bool, orgs []string) *GitHub {
|
||||||
var github = GitHub{
|
var github = GitHub{
|
||||||
URL: url,
|
URL: url,
|
||||||
API: api,
|
API: api,
|
||||||
@ -37,6 +38,7 @@ func New(url, api, client, secret string, private, skipVerify bool) *GitHub {
|
|||||||
Secret: secret,
|
Secret: secret,
|
||||||
Private: private,
|
Private: private,
|
||||||
SkipVerify: skipVerify,
|
SkipVerify: skipVerify,
|
||||||
|
Orgs: orgs,
|
||||||
}
|
}
|
||||||
// the API must have a trailing slash
|
// the API must have a trailing slash
|
||||||
if !strings.HasSuffix(github.API, "/") {
|
if !strings.HasSuffix(github.API, "/") {
|
||||||
@ -49,8 +51,8 @@ func New(url, api, client, secret string, private, skipVerify bool) *GitHub {
|
|||||||
return &github
|
return &github
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(client, secret string) *GitHub {
|
func NewDefault(client, secret string, orgs []string) *GitHub {
|
||||||
return New(DefaultURL, DefaultAPI, client, secret, false, false)
|
return New(DefaultURL, DefaultAPI, client, secret, false, false, orgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorize handles GitHub API Authorization.
|
// Authorize handles GitHub API Authorization.
|
||||||
@ -92,6 +94,16 @@ func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.L
|
|||||||
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(r.Orgs) > 0 {
|
||||||
|
allowedOrg, err := UserBelongsToOrg(client, r.Orgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not check org membership. %s", err)
|
||||||
|
}
|
||||||
|
if !allowedOrg {
|
||||||
|
return nil, fmt.Errorf("User does not belong to correct org")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var login = new(model.Login)
|
var login = new(model.Login)
|
||||||
login.ID = int64(*useremail.ID)
|
login.ID = int64(*useremail.ID)
|
||||||
login.Access = token.AccessToken
|
login.Access = token.AccessToken
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package github
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote/github/testdata"
|
"github.com/drone/drone/plugin/remote/github/testdata"
|
||||||
@ -90,5 +93,40 @@ func Test_Github(t *testing.T) {
|
|||||||
g.It("Should parse a commit hook")
|
g.It("Should parse a commit hook")
|
||||||
|
|
||||||
g.It("Should parse a pull request hook")
|
g.It("Should parse a pull request hook")
|
||||||
|
|
||||||
|
g.Describe("Authorize", func() {
|
||||||
|
g.AfterEach(func() {
|
||||||
|
github.Orgs = []string{}
|
||||||
|
})
|
||||||
|
|
||||||
|
var resp = httptest.NewRecorder()
|
||||||
|
var state = "validstate"
|
||||||
|
var req, _ = http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
fmt.Sprintf("%s/?code=sekret&state=%s", server.URL, state),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
req.AddCookie(&http.Cookie{Name: "github_state", Value: state})
|
||||||
|
|
||||||
|
g.It("Should authorize a valid user with no org restrictions", func() {
|
||||||
|
var login, err = github.Authorize(resp, req)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(login == nil).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should authorize a valid user in the correct org", func() {
|
||||||
|
github.Orgs = []string{"octocats-inc"}
|
||||||
|
var login, err = github.Authorize(resp, req)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(login == nil).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not authorize a valid user in the wrong org", func() {
|
||||||
|
github.Orgs = []string{"acme"}
|
||||||
|
var login, err = github.Authorize(resp, req)
|
||||||
|
g.Assert(err != nil).IsTrue()
|
||||||
|
g.Assert(login == nil).IsTrue()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgs is a helper function that returns a list of
|
// GetOrgs is a helper function that returns a list of
|
||||||
// all org repositories.
|
// all orgs that a user belongs to.
|
||||||
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
||||||
orgs, _, err := client.Organizations.List("", nil)
|
orgs, _, err := client.Organizations.List("", nil)
|
||||||
return orgs, err
|
return orgs, err
|
||||||
@ -270,3 +270,25 @@ func GetPayload(req *http.Request) []byte {
|
|||||||
}
|
}
|
||||||
return []byte(payload)
|
return []byte(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserBelongsToOrg returns true if the currently authenticated user is a
|
||||||
|
// member of any of the organizations provided.
|
||||||
|
func UserBelongsToOrg(client *github.Client, permittedOrgs []string) (bool, error) {
|
||||||
|
userOrgs, err := GetOrgs(client)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userOrgSet := make(map[string]struct{}, len(userOrgs))
|
||||||
|
for _, org := range userOrgs {
|
||||||
|
userOrgSet[*org.Login] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range permittedOrgs {
|
||||||
|
if _, ok := userOrgSet[org]; ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
@ -12,13 +12,22 @@ func Test_Helper(t *testing.T) {
|
|||||||
var server = testdata.NewServer()
|
var server = testdata.NewServer()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
var client = NewClient(server.URL, "sekret", false)
|
||||||
|
|
||||||
g := goblin.Goblin(t)
|
g := goblin.Goblin(t)
|
||||||
g.Describe("GitHub Helper Functions", func() {
|
g.Describe("GitHub Helper Functions", func() {
|
||||||
|
|
||||||
g.It("Should Get a User")
|
g.It("Should Get a User")
|
||||||
g.It("Should Get a User Primary Email")
|
g.It("Should Get a User Primary Email")
|
||||||
g.It("Should Get a User + Primary Email")
|
g.It("Should Get a User + Primary Email")
|
||||||
g.It("Should Get a list of Orgs")
|
|
||||||
|
g.It("Should Get a list of Orgs", func() {
|
||||||
|
var orgs, err = GetOrgs(client)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(orgs)).Equal(1)
|
||||||
|
g.Assert(*orgs[0].Login).Equal("octocats-inc")
|
||||||
|
})
|
||||||
|
|
||||||
g.It("Should Get a list of User Repos")
|
g.It("Should Get a list of User Repos")
|
||||||
g.It("Should Get a list of Org Repos")
|
g.It("Should Get a list of Org Repos")
|
||||||
g.It("Should Get a list of All Repos")
|
g.It("Should Get a list of All Repos")
|
||||||
@ -30,5 +39,20 @@ func Test_Helper(t *testing.T) {
|
|||||||
g.It("Should Create or Update a Repo Hook")
|
g.It("Should Create or Update a Repo Hook")
|
||||||
g.It("Should Get a Repo File")
|
g.It("Should Get a Repo File")
|
||||||
|
|
||||||
|
g.Describe("UserBelongsToOrg", func() {
|
||||||
|
g.It("Should confirm user does belong to 'octocats-inc' org", func() {
|
||||||
|
var requiredOrgs = []string{"one", "octocats-inc", "two"}
|
||||||
|
var member, err = UserBelongsToOrg(client, requiredOrgs)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(member).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should confirm user not does belong to 'octocats-inc' org", func() {
|
||||||
|
var requiredOrgs = []string{"one", "two"}
|
||||||
|
var member, err = UserBelongsToOrg(client, requiredOrgs)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(member).IsFalse()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ var (
|
|||||||
// GitHub cloud configuration details
|
// GitHub cloud configuration details
|
||||||
githubClient = config.String("github-client", "")
|
githubClient = config.String("github-client", "")
|
||||||
githubSecret = config.String("github-secret", "")
|
githubSecret = config.String("github-secret", "")
|
||||||
|
githubOrgs = config.Strings("github-orgs")
|
||||||
|
|
||||||
// GitHub Enterprise configuration details
|
// GitHub Enterprise configuration details
|
||||||
githubEnterpriseURL = config.String("github-enterprise-url", "")
|
githubEnterpriseURL = config.String("github-enterprise-url", "")
|
||||||
@ -17,6 +18,7 @@ var (
|
|||||||
githubEnterpriseSecret = config.String("github-enterprise-secret", "")
|
githubEnterpriseSecret = config.String("github-enterprise-secret", "")
|
||||||
githubEnterprisePrivate = config.Bool("github-enterprise-private-mode", true)
|
githubEnterprisePrivate = config.Bool("github-enterprise-private-mode", true)
|
||||||
githubEnterpriseSkipVerify = config.Bool("github-enterprise-skip-verify", false)
|
githubEnterpriseSkipVerify = config.Bool("github-enterprise-skip-verify", false)
|
||||||
|
githubEnterpriseOrgs = config.Strings("github-enterprise-orgs")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registers the GitHub plugins using the default
|
// Registers the GitHub plugins using the default
|
||||||
@ -33,7 +35,7 @@ func registerGitHub() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
remote.Register(
|
remote.Register(
|
||||||
NewDefault(*githubClient, *githubSecret),
|
NewDefault(*githubClient, *githubSecret, *githubOrgs),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,7 @@ func registerGitHubEnterprise() {
|
|||||||
*githubEnterpriseSecret,
|
*githubEnterpriseSecret,
|
||||||
*githubEnterprisePrivate,
|
*githubEnterprisePrivate,
|
||||||
*githubEnterpriseSkipVerify,
|
*githubEnterpriseSkipVerify,
|
||||||
|
*githubEnterpriseOrgs,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
71
plugin/remote/github/testdata/testdata.go
vendored
71
plugin/remote/github/testdata/testdata.go
vendored
@ -15,13 +15,22 @@ func NewServer() *httptest.Server {
|
|||||||
|
|
||||||
// evaluate the path to serve a dummy data file
|
// evaluate the path to serve a dummy data file
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
|
case "/login/oauth/access_token":
|
||||||
|
w.Write(accessTokenPayload)
|
||||||
|
return
|
||||||
|
case "/user":
|
||||||
|
w.Write(userPayload)
|
||||||
|
return
|
||||||
|
case "/user/emails":
|
||||||
|
w.Write(userEmailsPayload)
|
||||||
|
return
|
||||||
case "/user/repos":
|
case "/user/repos":
|
||||||
w.Write(userReposPayload)
|
w.Write(userReposPayload)
|
||||||
return
|
return
|
||||||
case "/user/orgs":
|
case "/user/orgs":
|
||||||
w.Write(userOrgsPayload)
|
w.Write(userOrgsPayload)
|
||||||
return
|
return
|
||||||
case "/orgs/github/repos":
|
case "/orgs/octocats-inc/repos":
|
||||||
w.Write(userReposPayload)
|
w.Write(userReposPayload)
|
||||||
return
|
return
|
||||||
case "/repos/octocat/Hello-World/contents/.drone.yml":
|
case "/repos/octocat/Hello-World/contents/.drone.yml":
|
||||||
@ -56,6 +65,64 @@ func NewServer() *httptest.Server {
|
|||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessTokenPayload = []byte(`access_token=sekret&scope=repo%2Cuser%3Aemail&token_type=bearer`)
|
||||||
|
|
||||||
|
var userPayload = []byte(`
|
||||||
|
{
|
||||||
|
"login": "octocat",
|
||||||
|
"id": 1,
|
||||||
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/octocat",
|
||||||
|
"html_url": "https://github.com/octocat",
|
||||||
|
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||||
|
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||||
|
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false,
|
||||||
|
"name": "monalisa octocat",
|
||||||
|
"company": "GitHub",
|
||||||
|
"blog": "https://github.com/blog",
|
||||||
|
"location": "San Francisco",
|
||||||
|
"email": "octocat@github.com",
|
||||||
|
"hireable": false,
|
||||||
|
"bio": "There once was...",
|
||||||
|
"public_repos": 2,
|
||||||
|
"public_gists": 1,
|
||||||
|
"followers": 20,
|
||||||
|
"following": 0,
|
||||||
|
"created_at": "2008-01-14T04:33:35Z",
|
||||||
|
"updated_at": "2008-01-14T04:33:35Z",
|
||||||
|
"total_private_repos": 100,
|
||||||
|
"owned_private_repos": 100,
|
||||||
|
"private_gists": 81,
|
||||||
|
"disk_usage": 10000,
|
||||||
|
"collaborators": 8,
|
||||||
|
"plan": {
|
||||||
|
"name": "Medium",
|
||||||
|
"space": 400,
|
||||||
|
"private_repos": 20,
|
||||||
|
"collaborators": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var userEmailsPayload = []byte(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"email": "octocat@github.com",
|
||||||
|
"verified": true,
|
||||||
|
"primary": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
// sample repository list
|
// sample repository list
|
||||||
var userReposPayload = []byte(`
|
var userReposPayload = []byte(`
|
||||||
[
|
[
|
||||||
@ -108,7 +175,7 @@ var emptyObjPayload = []byte(`{}`)
|
|||||||
// sample org list response
|
// sample org list response
|
||||||
var userOrgsPayload = []byte(`
|
var userOrgsPayload = []byte(`
|
||||||
[
|
[
|
||||||
{ "login": "github", "id": 1 }
|
{ "login": "octocats-inc", "id": 1 }
|
||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user