package auth
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
)
const (
TestGitHubClientID = "test-client-id"
TestGitHubClientSecret = "test-client-secret"
TestGitHubExternalURL = "http://localhost:8080"
)
func setupGitHubTest(allowedUsers, allowedOrgs []string) (Provider, gin.IRoutes) {
gh := gin.New()
gh.POST("/login/oauth/access_token", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"access_token": "test-access-token",
})
})
tsgh := httptest.NewServer(gh)
ghapi := gin.New()
tsghapi := httptest.NewServer(ghapi)
p, _ := NewGithubProvider(tsgh.URL, tsghapi.URL, TestGitHubClientID, TestGitHubClientSecret, TestGitHubExternalURL, allowedUsers, allowedOrgs)
return p, ghapi
}
func TestGitHubProvider(t *testing.T) {
p, _ := setupGitHubTest([]string{}, []string{})
require.Equal(t, p.Name(), "GitHub")
require.Equal(t, p.Type(), "github")
require.Equal(t, p.RedirectURL(), GitHubCallbackEndpoint)
require.Equal(t, p.AuthURL(), GitHubAuthEndpoint)
// check AuthCodeURL
authCodeURL, err := p.AuthCodeURL("test-state")
require.NoError(t, err)
require.NotEmpty(t, authCodeURL)
authCodeURLObj, err := url.Parse(authCodeURL)
require.NoError(t, err)
require.Equal(t, authCodeURLObj.Path, "/login/oauth/authorize")
require.Equal(t, authCodeURLObj.Query().Get("client_id"), TestGitHubClientID)
require.Equal(t, authCodeURLObj.Query().Get("redirect_uri"), TestGitHubExternalURL+"/.auth/github/callback")
require.Equal(t, authCodeURLObj.Query().Get("response_type"), "code")
require.Equal(t, authCodeURLObj.Query().Get("state"), "test-state")
// Check Exchange
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
req, _ := http.NewRequest("GET", "/?state=test-state&code=test-code", nil)
c.Request = req
_, err = p.Exchange(c, "invalid-state")
require.Error(t, err)
token, err := p.Exchange(c, "test-state")
require.NoError(t, err)
require.NotNil(t, token)
require.Equal(t, token.AccessToken, "test-access-token")
}
func TestGitHubProviderAuthorization(t *testing.T) {
tc := []struct {
name string
allowedUsers []string
allowedOrgs []string
userResp string
teamsResp string
orgsResp string
expect bool
}{
{
name: "allow all users",
allowedUsers: []string{},
allowedOrgs: []string{},
userResp: `{"login": "user1"}`,
orgsResp: `[]`,
teamsResp: `[]`,
expect: true,
},
{
name: "allow single user",
allowedUsers: []string{"user1", "user2"},
allowedOrgs: []string{},
userResp: `{"login": "user1"}`,
orgsResp: `[]`,
teamsResp: `[]`,
expect: true,
},
{
name: "deny single user",
allowedUsers: []string{"user1"},
allowedOrgs: []string{},
userResp: `{"login": "user2"}`,
orgsResp: `[]`,
teamsResp: `[]`,
expect: false,
},
{
name: "allow by org",
allowedUsers: []string{},
allowedOrgs: []string{"org1"},
userResp: `{"login": "user1"}`,
orgsResp: `[{"login": "org1"}]`,
teamsResp: `[]`,
expect: true,
},
{
name: "deny by org",
allowedUsers: []string{},
allowedOrgs: []string{"org1"},
userResp: `{"login": "user1"}`,
orgsResp: `[{"login": "org2"}]`,
teamsResp: `[]`,
expect: false,
},
{
name: "allow by team",
allowedUsers: []string{},
allowedOrgs: []string{"org1:team1"},
userResp: `{"login": "user1"}`,
orgsResp: `[]`,
teamsResp: `[{"organization": {"login": "org1"}, "slug": "team1"}]`,
expect: true,
},
{
name: "deny by team",
allowedUsers: []string{},
allowedOrgs: []string{"org1:team1"},
userResp: `{"login": "user1"}`,
orgsResp: `[]`,
teamsResp: `[{"organization": {"login": "org1"}, "slug": "team2"}]`,
expect: false,
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
p, ghapi := setupGitHubTest(tt.allowedUsers, tt.allowedOrgs)
userResp := tt.userResp
orgsResp := tt.orgsResp
teamsResp := tt.teamsResp
expect := tt.expect
ghapi.GET("/user", func(c *gin.Context) {
c.Data(http.StatusOK, "application/json", []byte(userResp))
})
ghapi.GET("/user/orgs", func(c *gin.Context) {
c.Data(http.StatusOK, "application/json", []byte(orgsResp))
})
ghapi.GET("/user/teams", func(c *gin.Context) {
c.Data(http.StatusOK, "application/json", []byte(teamsResp))
})
// Call the Authorization method
ok, _, _, err := p.Authorization(context.Background(), &oauth2.Token{AccessToken: "test-access-token"})
require.NoError(t, err)
require.Equal(t, expect, ok)
})
}
}
func TestGitHubProviderDefaultEndpoints(t *testing.T) {
p, err := NewGithubProvider("", "", TestGitHubClientID, TestGitHubClientSecret, TestGitHubExternalURL, []string{}, []string{})
require.NoError(t, err)
gp := p.(*githubProvider)
// Default (from oauth2) endpoints are used
require.Equal(t, github.Endpoint.AuthURL, gp.oauth2.Endpoint.AuthURL)
require.Equal(t, github.Endpoint.TokenURL, gp.oauth2.Endpoint.TokenURL)
require.Equal(t, github.Endpoint.DeviceAuthURL, gp.oauth2.Endpoint.DeviceAuthURL)
// And the default github API
require.Equal(t, "https://api.github.com", gp.endpoint)
}