1package providers
2
3import (
4	"context"
5	"net/http"
6	"net/http/httptest"
7	"net/url"
8	"testing"
9
10	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
11	. "github.com/onsi/gomega"
12	"github.com/stretchr/testify/assert"
13)
14
15func testGitHubProvider(hostname string) *GitHubProvider {
16	p := NewGitHubProvider(
17		&ProviderData{
18			ProviderName: "",
19			LoginURL:     &url.URL{},
20			RedeemURL:    &url.URL{},
21			ProfileURL:   &url.URL{},
22			ValidateURL:  &url.URL{},
23			Scope:        ""})
24	if hostname != "" {
25		updateURL(p.Data().LoginURL, hostname)
26		updateURL(p.Data().RedeemURL, hostname)
27		updateURL(p.Data().ProfileURL, hostname)
28		updateURL(p.Data().ValidateURL, hostname)
29	}
30	return p
31}
32
33func testGitHubBackend(payloads map[string][]string) *httptest.Server {
34	pathToQueryMap := map[string][]string{
35		"/repo/oauth2-proxy/oauth2-proxy":                       {""},
36		"/repos/oauth2-proxy/oauth2-proxy/collaborators/mbland": {""},
37		"/user":        {""},
38		"/user/emails": {""},
39		"/user/orgs":   {"page=1&per_page=100", "page=2&per_page=100", "page=3&per_page=100"},
40	}
41
42	return httptest.NewServer(http.HandlerFunc(
43		func(w http.ResponseWriter, r *http.Request) {
44			query, ok := pathToQueryMap[r.URL.Path]
45			validQuery := false
46			index := 0
47			for i, q := range query {
48				if q == r.URL.RawQuery {
49					validQuery = true
50					index = i
51				}
52			}
53			payload := []string{}
54			if ok && validQuery {
55				payload, ok = payloads[r.URL.Path]
56			}
57			if !ok {
58				w.WriteHeader(404)
59			} else if !validQuery {
60				w.WriteHeader(404)
61			} else if payload[index] == "" {
62				w.WriteHeader(204)
63			} else {
64				w.WriteHeader(200)
65				w.Write([]byte(payload[index]))
66			}
67		}))
68}
69
70func TestNewGitHubProvider(t *testing.T) {
71	g := NewWithT(t)
72
73	// Test that defaults are set when calling for a new provider with nothing set
74	providerData := NewGitHubProvider(&ProviderData{}).Data()
75	g.Expect(providerData.ProviderName).To(Equal("GitHub"))
76	g.Expect(providerData.LoginURL.String()).To(Equal("https://github.com/login/oauth/authorize"))
77	g.Expect(providerData.RedeemURL.String()).To(Equal("https://github.com/login/oauth/access_token"))
78	g.Expect(providerData.ProfileURL.String()).To(Equal(""))
79	g.Expect(providerData.ValidateURL.String()).To(Equal("https://api.github.com/"))
80	g.Expect(providerData.Scope).To(Equal("user:email"))
81}
82
83func TestGitHubProviderOverrides(t *testing.T) {
84	p := NewGitHubProvider(
85		&ProviderData{
86			LoginURL: &url.URL{
87				Scheme: "https",
88				Host:   "example.com",
89				Path:   "/login/oauth/authorize"},
90			RedeemURL: &url.URL{
91				Scheme: "https",
92				Host:   "example.com",
93				Path:   "/login/oauth/access_token"},
94			ValidateURL: &url.URL{
95				Scheme: "https",
96				Host:   "api.example.com",
97				Path:   "/"},
98			Scope: "profile"})
99	assert.NotEqual(t, nil, p)
100	assert.Equal(t, "GitHub", p.Data().ProviderName)
101	assert.Equal(t, "https://example.com/login/oauth/authorize",
102		p.Data().LoginURL.String())
103	assert.Equal(t, "https://example.com/login/oauth/access_token",
104		p.Data().RedeemURL.String())
105	assert.Equal(t, "https://api.example.com/",
106		p.Data().ValidateURL.String())
107	assert.Equal(t, "profile", p.Data().Scope)
108}
109
110func TestGitHubProvider_getEmail(t *testing.T) {
111	b := testGitHubBackend(map[string][]string{
112		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
113	})
114	defer b.Close()
115
116	bURL, _ := url.Parse(b.URL)
117	p := testGitHubProvider(bURL.Host)
118
119	session := CreateAuthorizedSession()
120	err := p.getEmail(context.Background(), session)
121	assert.NoError(t, err)
122	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
123}
124
125func TestGitHubProvider_getEmailNotVerified(t *testing.T) {
126	b := testGitHubBackend(map[string][]string{
127		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": false, "primary": true} ]`},
128	})
129	defer b.Close()
130
131	bURL, _ := url.Parse(b.URL)
132	p := testGitHubProvider(bURL.Host)
133
134	session := CreateAuthorizedSession()
135	err := p.getEmail(context.Background(), session)
136	assert.NoError(t, err)
137	assert.Empty(t, session.Email)
138}
139
140func TestGitHubProvider_getEmailWithOrg(t *testing.T) {
141	b := testGitHubBackend(map[string][]string{
142		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
143		"/user/orgs": {
144			`[ {"login":"testorg"} ]`,
145			`[ {"login":"testorg1"} ]`,
146			`[ ]`,
147		},
148	})
149	defer b.Close()
150
151	bURL, _ := url.Parse(b.URL)
152	p := testGitHubProvider(bURL.Host)
153	p.Org = "testorg1"
154
155	session := CreateAuthorizedSession()
156	err := p.getEmail(context.Background(), session)
157	assert.NoError(t, err)
158	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
159}
160
161func TestGitHubProvider_getEmailWithWriteAccessToPublicRepo(t *testing.T) {
162	b := testGitHubBackend(map[string][]string{
163		"/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": true}, "private": false}`},
164		"/user/emails":                    {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
165	})
166	defer b.Close()
167
168	bURL, _ := url.Parse(b.URL)
169	p := testGitHubProvider(bURL.Host)
170	p.SetRepo("oauth2-proxy/oauth2-proxy", "")
171
172	session := CreateAuthorizedSession()
173	err := p.getEmail(context.Background(), session)
174	assert.NoError(t, err)
175	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
176}
177
178func TestGitHubProvider_getEmailWithReadOnlyAccessToPrivateRepo(t *testing.T) {
179	b := testGitHubBackend(map[string][]string{
180		"/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": false}, "private": true}`},
181		"/user/emails":                    {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
182	})
183	defer b.Close()
184
185	bURL, _ := url.Parse(b.URL)
186	p := testGitHubProvider(bURL.Host)
187	p.SetRepo("oauth2-proxy/oauth2-proxy", "")
188
189	session := CreateAuthorizedSession()
190	err := p.getEmail(context.Background(), session)
191	assert.NoError(t, err)
192	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
193}
194
195func TestGitHubProvider_getEmailWithWriteAccessToPrivateRepo(t *testing.T) {
196	b := testGitHubBackend(map[string][]string{
197		"/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": true}, "private": true}`},
198		"/user/emails":                    {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
199	})
200	defer b.Close()
201
202	bURL, _ := url.Parse(b.URL)
203	p := testGitHubProvider(bURL.Host)
204	p.SetRepo("oauth2-proxy/oauth2-proxy", "")
205
206	session := CreateAuthorizedSession()
207	err := p.getEmail(context.Background(), session)
208	assert.NoError(t, err)
209	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
210}
211
212func TestGitHubProvider_getEmailWithNoAccessToPrivateRepo(t *testing.T) {
213	b := testGitHubBackend(map[string][]string{
214		"/repo/oauth2-proxy/oauth2-proxy": {`{}`},
215	})
216	defer b.Close()
217
218	bURL, _ := url.Parse(b.URL)
219	p := testGitHubProvider(bURL.Host)
220	p.SetRepo("oauth2-proxy/oauth2-proxy", "")
221
222	session := CreateAuthorizedSession()
223	err := p.getEmail(context.Background(), session)
224	assert.NoError(t, err)
225	assert.Empty(t, session.Email)
226}
227
228func TestGitHubProvider_getEmailWithToken(t *testing.T) {
229	b := testGitHubBackend(map[string][]string{
230		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
231	})
232	defer b.Close()
233
234	bURL, _ := url.Parse(b.URL)
235	p := testGitHubProvider(bURL.Host)
236	p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
237
238	session := CreateAuthorizedSession()
239	err := p.getEmail(context.Background(), session)
240	assert.NoError(t, err)
241	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
242}
243
244// Note that trying to trigger the "failed building request" case is not
245// practical, since the only way it can fail is if the URL fails to parse.
246func TestGitHubProvider_getEmailFailedRequest(t *testing.T) {
247	b := testGitHubBackend(map[string][]string{})
248	defer b.Close()
249
250	bURL, _ := url.Parse(b.URL)
251	p := testGitHubProvider(bURL.Host)
252
253	// We'll trigger a request failure by using an unexpected access
254	// token. Alternatively, we could allow the parsing of the payload as
255	// JSON to fail.
256	session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
257	err := p.getEmail(context.Background(), session)
258	assert.Error(t, err)
259	assert.Empty(t, session.Email)
260}
261
262func TestGitHubProvider_getEmailNotPresentInPayload(t *testing.T) {
263	b := testGitHubBackend(map[string][]string{
264		"/user/emails": {`{"foo": "bar"}`},
265	})
266	defer b.Close()
267
268	bURL, _ := url.Parse(b.URL)
269	p := testGitHubProvider(bURL.Host)
270
271	session := CreateAuthorizedSession()
272	err := p.getEmail(context.Background(), session)
273	assert.Error(t, err)
274	assert.Empty(t, session.Email)
275}
276
277func TestGitHubProvider_getUser(t *testing.T) {
278	b := testGitHubBackend(map[string][]string{
279		"/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
280	})
281	defer b.Close()
282
283	bURL, _ := url.Parse(b.URL)
284	p := testGitHubProvider(bURL.Host)
285
286	session := CreateAuthorizedSession()
287	err := p.getUser(context.Background(), session)
288	assert.NoError(t, err)
289	assert.Equal(t, "mbland", session.User)
290}
291
292func TestGitHubProvider_getUserWithRepoAndToken(t *testing.T) {
293	b := testGitHubBackend(map[string][]string{
294		"/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
295		"/repos/oauth2-proxy/oauth2-proxy/collaborators/mbland": {""},
296	})
297	defer b.Close()
298
299	bURL, _ := url.Parse(b.URL)
300	p := testGitHubProvider(bURL.Host)
301	p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
302
303	session := CreateAuthorizedSession()
304	err := p.getUser(context.Background(), session)
305	assert.NoError(t, err)
306	assert.Equal(t, "mbland", session.User)
307}
308
309func TestGitHubProvider_getUserWithRepoAndTokenWithoutPushAccess(t *testing.T) {
310	b := testGitHubBackend(map[string][]string{})
311	defer b.Close()
312
313	bURL, _ := url.Parse(b.URL)
314	p := testGitHubProvider(bURL.Host)
315	p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
316
317	session := CreateAuthorizedSession()
318	err := p.getUser(context.Background(), session)
319	assert.Error(t, err)
320	assert.Empty(t, session.User)
321}
322
323func TestGitHubProvider_getEmailWithUsername(t *testing.T) {
324	b := testGitHubBackend(map[string][]string{
325		"/user":        {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
326		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
327	})
328	defer b.Close()
329
330	bURL, _ := url.Parse(b.URL)
331	p := testGitHubProvider(bURL.Host)
332	p.SetUsers([]string{"mbland", "octocat"})
333
334	session := CreateAuthorizedSession()
335	err := p.getEmail(context.Background(), session)
336	assert.NoError(t, err)
337	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
338}
339
340func TestGitHubProvider_getEmailWithNotAllowedUsername(t *testing.T) {
341	b := testGitHubBackend(map[string][]string{
342		"/user":        {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
343		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
344	})
345	defer b.Close()
346
347	bURL, _ := url.Parse(b.URL)
348	p := testGitHubProvider(bURL.Host)
349	p.SetUsers([]string{"octocat"})
350
351	session := CreateAuthorizedSession()
352	err := p.getEmail(context.Background(), session)
353	assert.Error(t, err)
354	assert.Empty(t, session.Email)
355}
356
357func TestGitHubProvider_getEmailWithUsernameAndNotBelongToOrg(t *testing.T) {
358	b := testGitHubBackend(map[string][]string{
359		"/user":        {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
360		"/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
361		"/user/orgs": {
362			`[ {"login":"testorg"} ]`,
363			`[ ]`,
364		},
365	})
366	defer b.Close()
367
368	bURL, _ := url.Parse(b.URL)
369	p := testGitHubProvider(bURL.Host)
370	p.SetOrgTeam("not_belong_to", "")
371	p.SetUsers([]string{"mbland"})
372
373	session := CreateAuthorizedSession()
374	err := p.getEmail(context.Background(), session)
375	assert.NoError(t, err)
376	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
377}
378
379func TestGitHubProvider_getEmailWithUsernameAndNoAccessToPrivateRepo(t *testing.T) {
380	b := testGitHubBackend(map[string][]string{
381		"/user":                           {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
382		"/user/emails":                    {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
383		"/repo/oauth2-proxy/oauth2-proxy": {`{}`},
384	})
385	defer b.Close()
386
387	bURL, _ := url.Parse(b.URL)
388	p := testGitHubProvider(bURL.Host)
389	p.SetRepo("oauth2-proxy/oauth2-proxy", "")
390	p.SetUsers([]string{"mbland"})
391
392	session := CreateAuthorizedSession()
393	err := p.getEmail(context.Background(), session)
394	assert.NoError(t, err)
395	assert.Equal(t, "michael.bland@gsa.gov", session.Email)
396}
397