git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge branch 'master' into ap-gh-pagination-with-lastpage
commit535f6b8e63a7f600c9488308d1e202c9867f4a5f authorJoel Speed <Joel.speed@hotmail.co.uk> dateMon 21 Oct 2019, 10:21 (7 years ago) parente34f18e9d0a0c7
8 files changed+158−166
CHANGELOG.md+2 README.md+5−2 docs/2_auth.md+2 providers/azure.go+65 providers/azure_test.go+22−1 validator_test.go+62−13 validator_watcher_copy_test.go−48 validator_watcher_test.go−102
CHANGELOG.md+2
@@ -5,6 +5,8 @@
55- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka)
66- [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio)
77- [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll)
8+- [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider
9+ - This PR adds the IDToken into the session for the Azure provider allowing requests to a backend to be identified as a specific user. As a consequence, if you are using a cookie to store the session the cookie will now exceed the 4kb size limit and be split into multiple cookies. This can cause problems when using nginx as a proxy, resulting in no cookie being passed at all. Either increase the proxy_buffer_size in nginx or implement the redis session storage (see https://pusher.github.io/oauth2_proxy/configuration#redis-storage)
810- [#274](https://github.com/pusher/oauth2_proxy/pull/274) Supports many github teams with api pagination support (@toshi-miura, @apratina)
911
1012# v4.0.0
README.md+5−2
@@ -1,5 +1,10 @@
11# oauth2_proxy
22
3+[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
4+[![Go Report Card](https://goreportcard.com/badge/github.com/pusher/oauth2_proxy)](https://goreportcard.com/report/github.com/pusher/oauth2_proxy)
5+[![GoDoc](https://godoc.org/github.com/pusher/oauth2_proxy?status.svg)](https://godoc.org/github.com/pusher/oauth2_proxy)
6+[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7+
38A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others)
49to validate accounts by email, domain or group.
510
@@ -7,8 +12,6 @@ to validate accounts by email, domain or group.
712Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork.
813A list of changes can be seen in the [CHANGELOG](CHANGELOG.md).
914
10−[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
11−
1215![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
1316
1417## Installation
docs/2_auth.md+2
@@ -81,6 +81,8 @@ Note: The user is checked against the group members list on initial authenticati
8181 --client-secret=<value from step 6>
8282```
8383
84+Note: When using the Azure Auth provider with nginx and the cookie session store you may find the cookie is too large and doesn't get passed through correctly. Increasing the proxy_buffer_size in nginx or implementing the [redis session storage](configuration#redis-storage) should resolve this.
85+
8486### Facebook Auth Provider
8587
86881. Create a new FB App from <https://developers.facebook.com/>
providers/azure.go+65
@@ -1,10 +1,14 @@
11package providers
22
33import (
4+ "bytes"
5+ "encoding/json"
46 "errors"
57 "fmt"
8+ "io/ioutil"
69 "net/http"
710 "net/url"
11+ "time"
812
913 "github.com/bitly/go-simplejson"
1014 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
@@ -65,6 +69,67 @@ func (p *AzureProvider) Configure(tenant string) {
6569 }
6670}
6771
72+func (p *AzureProvider) Redeem(redirectURL, code string) (s *sessions.SessionState, err error) {
73+ if code == "" {
74+ err = errors.New("missing code")
75+ return
76+ }
77+
78+ params := url.Values{}
79+ params.Add("redirect_uri", redirectURL)
80+ params.Add("client_id", p.ClientID)
81+ params.Add("client_secret", p.ClientSecret)
82+ params.Add("code", code)
83+ params.Add("grant_type", "authorization_code")
84+ if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
85+ params.Add("resource", p.ProtectedResource.String())
86+ }
87+
88+ var req *http.Request
89+ req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode()))
90+ if err != nil {
91+ return
92+ }
93+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
94+
95+ var resp *http.Response
96+ resp, err = http.DefaultClient.Do(req)
97+ if err != nil {
98+ return nil, err
99+ }
100+ var body []byte
101+ body, err = ioutil.ReadAll(resp.Body)
102+ resp.Body.Close()
103+ if err != nil {
104+ return
105+ }
106+
107+ if resp.StatusCode != 200 {
108+ err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemURL.String(), body)
109+ return
110+ }
111+
112+ var jsonResponse struct {
113+ AccessToken string `json:"access_token"`
114+ RefreshToken string `json:"refresh_token"`
115+ ExpiresOn int64 `json:"expires_on,string"`
116+ IDToken string `json:"id_token"`
117+ }
118+ err = json.Unmarshal(body, &jsonResponse)
119+ if err != nil {
120+ return
121+ }
122+
123+ s = &sessions.SessionState{
124+ AccessToken: jsonResponse.AccessToken,
125+ IDToken: jsonResponse.IDToken,
126+ CreatedAt: time.Now(),
127+ ExpiresOn: time.Unix(jsonResponse.ExpiresOn, 0),
128+ RefreshToken: jsonResponse.RefreshToken,
129+ }
130+ return
131+}
132+
68133func getAzureHeader(accessToken string) http.Header {
69134 header := make(http.Header)
70135 header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
providers/azure_test.go+22−1
@@ -5,6 +5,7 @@ import (
55 "net/http/httptest"
66 "net/url"
77 "testing"
8+ "time"
89
910 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
1011 "github.com/stretchr/testify/assert"
@@ -20,6 +21,7 @@ func testAzureProvider(hostname string) *AzureProvider {
2021 ValidateURL: &url.URL{},
2122 ProtectedResource: &url.URL{},
2223 Scope: ""})
24+
2325 if hostname != "" {
2426 updateURL(p.Data().LoginURL, hostname)
2527 updateURL(p.Data().RedeemURL, hostname)
@@ -111,8 +113,11 @@ func testAzureBackend(payload string) *httptest.Server {
111113
112114 return httptest.NewServer(http.HandlerFunc(
113115 func(w http.ResponseWriter, r *http.Request) {
114− if r.URL.Path != path || r.URL.RawQuery != query {
116+ if (r.URL.Path != path || r.URL.RawQuery != query) && r.Method != "POST" {
115117 w.WriteHeader(404)
118+ } else if r.Method == "POST" && r.Body != nil {
119+ w.WriteHeader(200)
120+ w.Write([]byte(payload))
116121 } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
117122 w.WriteHeader(403)
118123 } else {
@@ -199,3 +204,19 @@ func TestAzureProviderGetEmailAddressIncorrectOtherMails(t *testing.T) {
199204 assert.Equal(t, "type assertion to string failed", err.Error())
200205 assert.Equal(t, "", email)
201206}
207+
208+func TestAzureProviderRedeemReturnsIdToken(t *testing.T) {
209+ b := testAzureBackend(`{ "id_token": "testtoken1234", "expires_on": "1136239445", "refresh_token": "refresh1234" }`)
210+ defer b.Close()
211+ timestamp, err := time.Parse(time.RFC3339, "2006-01-02T22:04:05Z")
212+ assert.Equal(t, nil, err)
213+
214+ bURL, _ := url.Parse(b.URL)
215+ p := testAzureProvider(bURL.Host)
216+ p.Data().RedeemURL.Path = "/common/oauth2/token"
217+ s, err := p.Redeem("https://localhost", "1234")
218+ assert.Equal(t, nil, err)
219+ assert.Equal(t, "testtoken1234", s.IDToken)
220+ assert.Equal(t, timestamp, s.ExpiresOn.UTC())
221+ assert.Equal(t, "refresh1234", s.RefreshToken)
222+}
validator_test.go+62−13
@@ -8,30 +8,34 @@ import (
88)
99
1010type ValidatorTest struct {
11− authEmailFile *os.File
12− done chan bool
13− updateSeen bool
11+ authEmailFileName string
12+ done chan bool
13+ updateSeen bool
1414}
1515
1616func NewValidatorTest(t *testing.T) *ValidatorTest {
1717 vt := &ValidatorTest{}
1818 var err error
19− vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
19+ f, err := ioutil.TempFile("", "test_auth_emails_")
2020 if err != nil {
21− t.Fatal("failed to create temp file: " + err.Error())
21+ t.Fatalf("failed to create temp file: %v", err)
2222 }
23+ if err := f.Close(); err != nil {
24+ t.Fatalf("failed to close temp file: %v", err)
25+ }
26+ vt.authEmailFileName = f.Name()
2327 vt.done = make(chan bool, 1)
2428 return vt
2529}
2630
2731func (vt *ValidatorTest) TearDown() {
2832 vt.done <- true
29− os.Remove(vt.authEmailFile.Name())
33+ os.Remove(vt.authEmailFileName)
3034}
3135
3236func (vt *ValidatorTest) NewValidator(domains []string,
3337 updated chan<- bool) func(string) bool {
34− return newValidatorImpl(domains, vt.authEmailFile.Name(),
38+ return newValidatorImpl(domains, vt.authEmailFileName,
3539 vt.done, func() {
3640 if vt.updateSeen == false {
3741 updated <- true
@@ -40,13 +44,18 @@ func (vt *ValidatorTest) NewValidator(domains []string,
4044 })
4145}
4246
43−// This will close vt.authEmailFile.
4447func (vt *ValidatorTest) WriteEmails(t *testing.T, emails []string) {
45− defer vt.authEmailFile.Close()
46− vt.authEmailFile.WriteString(strings.Join(emails, "\n"))
47− if err := vt.authEmailFile.Close(); err != nil {
48− t.Fatal("failed to close temp file " +
49− vt.authEmailFile.Name() + ": " + err.Error())
48+ f, err := os.OpenFile(vt.authEmailFileName, os.O_WRONLY, 0600)
49+ if err != nil {
50+ t.Fatalf("failed to open auth email file: %v", err)
51+ }
52+
53+ if _, err := f.WriteString(strings.Join(emails, "\n")); err != nil {
54+ t.Fatalf("failed to write emails to auth email file: %v", err)
55+ }
56+
57+ if err := f.Close(); err != nil {
58+ t.Fatalf("failed to close auth email file: %v", err)
5059 }
5160}
5261
@@ -160,3 +169,43 @@ func TestValidatorIgnoreSpacesInAuthEmails(t *testing.T) {
160169 t.Error("email should validate")
161170 }
162171}
172+
173+func TestValidatorOverwriteEmailListDirectly(t *testing.T) {
174+ vt := NewValidatorTest(t)
175+ defer vt.TearDown()
176+
177+ vt.WriteEmails(t, []string{
178+ "xyzzy@example.com",
179+ "plugh@example.com",
180+ })
181+ domains := []string(nil)
182+ updated := make(chan bool)
183+ validator := vt.NewValidator(domains, updated)
184+
185+ if !validator("xyzzy@example.com") {
186+ t.Error("first email in list should validate")
187+ }
188+ if !validator("plugh@example.com") {
189+ t.Error("second email in list should validate")
190+ }
191+ if validator("xyzzy.plugh@example.com") {
192+ t.Error("email not in list that matches no domains " +
193+ "should not validate")
194+ }
195+
196+ vt.WriteEmails(t, []string{
197+ "xyzzy.plugh@example.com",
198+ "plugh@example.com",
199+ })
200+ <-updated
201+
202+ if validator("xyzzy@example.com") {
203+ t.Error("email removed from list should not validate")
204+ }
205+ if !validator("plugh@example.com") {
206+ t.Error("email retained in list should validate")
207+ }
208+ if !validator("xyzzy.plugh@example.com") {
209+ t.Error("email added to list should validate")
210+ }
211+}
validator_watcher_copy_test.godeleted−48
@@ -1,48 +0,0 @@
1−// +build go1.3,!plan9,!solaris,!windows
2−
3−// Turns out you can't copy over an existing file on Windows.
4−
5−package main
6−
7−import (
8− "io/ioutil"
9− "os"
10− "testing"
11−)
12−
13−func (vt *ValidatorTest) UpdateEmailFileViaCopyingOver(
14− t *testing.T, emails []string) {
15− origFile := vt.authEmailFile
16− var err error
17− vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
18− if err != nil {
19− t.Fatal("failed to create temp file for copy: " + err.Error())
20− }
21− vt.WriteEmails(t, emails)
22− err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
23− if err != nil {
24− t.Fatal("failed to copy over temp file: " + err.Error())
25− }
26− vt.authEmailFile = origFile
27−}
28−
29−func TestValidatorOverwriteEmailListViaCopyingOver(t *testing.T) {
30− vt := NewValidatorTest(t)
31− defer vt.TearDown()
32−
33− vt.WriteEmails(t, []string{"xyzzy@example.com"})
34− domains := []string(nil)
35− updated := make(chan bool)
36− validator := vt.NewValidator(domains, updated)
37−
38− if !validator("xyzzy@example.com") {
39− t.Error("email in list should validate")
40− }
41−
42− vt.UpdateEmailFileViaCopyingOver(t, []string{"plugh@example.com"})
43− <-updated
44−
45− if validator("xyzzy@example.com") {
46− t.Error("email removed from list should not validate")
47− }
48−}
validator_watcher_test.godeleted−102
@@ -1,102 +0,0 @@
1−// +build go1.3,!plan9,!solaris
2−
3−package main
4−
5−import (
6− "io/ioutil"
7− "os"
8− "testing"
9−)
10−
11−func (vt *ValidatorTest) UpdateEmailFile(t *testing.T, emails []string) {
12− var err error
13− vt.authEmailFile, err = os.OpenFile(
14− vt.authEmailFile.Name(), os.O_WRONLY|os.O_CREATE, 0600)
15− if err != nil {
16− t.Fatal("failed to re-open temp file for updates")
17− }
18− vt.WriteEmails(t, emails)
19−}
20−
21−func (vt *ValidatorTest) UpdateEmailFileViaRenameAndReplace(
22− t *testing.T, emails []string) {
23− origFile := vt.authEmailFile
24− var err error
25− vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
26− if err != nil {
27− t.Fatal("failed to create temp file for rename and replace: " +
28− err.Error())
29− }
30− vt.WriteEmails(t, emails)
31−
32− movedName := origFile.Name() + "-moved"
33− err = os.Rename(origFile.Name(), movedName)
34− err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
35− if err != nil {
36− t.Fatal("failed to rename and replace temp file: " +
37− err.Error())
38− }
39− vt.authEmailFile = origFile
40− os.Remove(movedName)
41−}
42−
43−func TestValidatorOverwriteEmailListDirectly(t *testing.T) {
44− vt := NewValidatorTest(t)
45− defer vt.TearDown()
46−
47− vt.WriteEmails(t, []string{
48− "xyzzy@example.com",
49− "plugh@example.com",
50− })
51− domains := []string(nil)
52− updated := make(chan bool)
53− validator := vt.NewValidator(domains, updated)
54−
55− if !validator("xyzzy@example.com") {
56− t.Error("first email in list should validate")
57− }
58− if !validator("plugh@example.com") {
59− t.Error("second email in list should validate")
60− }
61− if validator("xyzzy.plugh@example.com") {
62− t.Error("email not in list that matches no domains " +
63− "should not validate")
64− }
65−
66− vt.UpdateEmailFile(t, []string{
67− "xyzzy.plugh@example.com",
68− "plugh@example.com",
69− })
70− <-updated
71−
72− if validator("xyzzy@example.com") {
73− t.Error("email removed from list should not validate")
74− }
75− if !validator("plugh@example.com") {
76− t.Error("email retained in list should validate")
77− }
78− if !validator("xyzzy.plugh@example.com") {
79− t.Error("email added to list should validate")
80− }
81−}
82−
83−func TestValidatorOverwriteEmailListViaRenameAndReplace(t *testing.T) {
84− vt := NewValidatorTest(t)
85− defer vt.TearDown()
86−
87− vt.WriteEmails(t, []string{"xyzzy@example.com"})
88− domains := []string(nil)
89− updated := make(chan bool, 1)
90− validator := vt.NewValidator(domains, updated)
91−
92− if !validator("xyzzy@example.com") {
93− t.Error("email in list should validate")
94− }
95−
96− vt.UpdateEmailFileViaRenameAndReplace(t, []string{"plugh@example.com"})
97− <-updated
98−
99− if validator("xyzzy@example.com") {
100− t.Error("email removed from list should not validate")
101− }
102−}