@@ -1,12 +1,17 @@
11package main
22
33import (
4+ "github.com/bitly/go-simplejson"
5+ "github.com/bitly/google_auth_proxy/providers"
6+ "github.com/bmizerany/assert"
47 "io/ioutil"
58 "net"
69 "net/http"
710 "net/http/httptest"
811 "net/url"
12+ "strings"
913 "testing"
14+ "time"
1015)
1116
1217func TestNewReverseProxy(t *testing.T) {
@@ -18,8 +23,7 @@ func TestNewReverseProxy(t *testing.T) {
1823 defer backend.Close()
1924
2025 backendURL, _ := url.Parse(backend.URL)
21− backendHostname := "upstream.127.0.0.1.xip.io"
22− _, backendPort, _ := net.SplitHostPort(backendURL.Host)
26+ backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
2327 backendHost := net.JoinHostPort(backendHostname, backendPort)
2428 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
2529
@@ -61,3 +65,175 @@ func TestEncodedSlashes(t *testing.T) {
6165 t.Errorf("got bad request %q expected %q", seen, encodedPath)
6266 }
6367}
68+
69+type TestProvider struct {
70+ *providers.ProviderData
71+ EmailAddress string
72+}
73+
74+func (tp *TestProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
75+ unused_access_token string) (string, error) {
76+ return tp.EmailAddress, nil
77+}
78+
79+type PassAccessTokenTest struct {
80+ provider_server *httptest.Server
81+ proxy *OauthProxy
82+ opts *Options
83+}
84+
85+type PassAccessTokenTestOptions struct {
86+ PassAccessToken bool
87+}
88+
89+func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
90+ t := &PassAccessTokenTest{}
91+
92+ t.provider_server = httptest.NewServer(
93+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
94+ url := r.URL
95+ payload := ""
96+ switch url.Path {
97+ case "/oauth/token":
98+ payload = `{"access_token": "my_auth_token"}`
99+ default:
100+ token_header := r.Header["X-Forwarded-Access-Token"]
101+ if len(token_header) != 0 {
102+ payload = token_header[0]
103+ } else {
104+ payload = "No access token found."
105+ }
106+ }
107+ w.WriteHeader(200)
108+ w.Write([]byte(payload))
109+ }))
110+
111+ t.opts = NewOptions()
112+ t.opts.Upstreams = append(t.opts.Upstreams, t.provider_server.URL)
113+ // The CookieSecret must be 32 bytes in order to create the AES
114+ // cipher.
115+ t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
116+ t.opts.ClientID = "bazquux"
117+ t.opts.ClientSecret = "foobar"
118+ t.opts.CookieSecure = false
119+ t.opts.PassAccessToken = opts.PassAccessToken
120+ t.opts.Validate()
121+
122+ provider_url, _ := url.Parse(t.provider_server.URL)
123+ const email_address = "michael.bland@gsa.gov"
124+
125+ t.opts.provider = &TestProvider{
126+ ProviderData: &providers.ProviderData{
127+ ProviderName: "Test Provider",
128+ LoginUrl: &url.URL{
129+ Scheme: "http",
130+ Host: provider_url.Host,
131+ Path: "/oauth/authorize",
132+ },
133+ RedeemUrl: &url.URL{
134+ Scheme: "http",
135+ Host: provider_url.Host,
136+ Path: "/oauth/token",
137+ },
138+ ProfileUrl: &url.URL{
139+ Scheme: "http",
140+ Host: provider_url.Host,
141+ Path: "/api/v1/profile",
142+ },
143+ Scope: "profile.email",
144+ },
145+ EmailAddress: email_address,
146+ }
147+
148+ t.proxy = NewOauthProxy(t.opts, func(email string) bool {
149+ return email == email_address
150+ })
151+ return t
152+}
153+
154+func Close(t *PassAccessTokenTest) {
155+ t.provider_server.Close()
156+}
157+
158+func getCallbackEndpoint(pac_test *PassAccessTokenTest) (http_code int, cookie string) {
159+ rw := httptest.NewRecorder()
160+ req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code",
161+ strings.NewReader(""))
162+ if err != nil {
163+ return 0, ""
164+ }
165+ pac_test.proxy.ServeHTTP(rw, req)
166+ return rw.Code, rw.HeaderMap["Set-Cookie"][0]
167+}
168+
169+func getRootEndpoint(pac_test *PassAccessTokenTest, cookie string) (http_code int,
170+ access_token string) {
171+ cookie_key := pac_test.proxy.CookieKey
172+ var value string
173+ key_prefix := cookie_key + "="
174+
175+ for _, field := range strings.Split(cookie, "; ") {
176+ value = strings.TrimPrefix(field, key_prefix)
177+ if value != field {
178+ break
179+ } else {
180+ value = ""
181+ }
182+ }
183+ if value == "" {
184+ return 0, ""
185+ }
186+
187+ req, err := http.NewRequest("GET", "/", strings.NewReader(""))
188+ if err != nil {
189+ return 0, ""
190+ }
191+ req.AddCookie(&http.Cookie{
192+ Name: cookie_key,
193+ Value: value,
194+ Path: "/",
195+ Expires: time.Now().Add(time.Duration(24)),
196+ HttpOnly: true,
197+ })
198+
199+ rw := httptest.NewRecorder()
200+ pac_test.proxy.ServeHTTP(rw, req)
201+ return rw.Code, rw.Body.String()
202+}
203+
204+func TestForwardAccessTokenUpstream(t *testing.T) {
205+ pac_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
206+ PassAccessToken: true,
207+ })
208+ defer Close(pac_test)
209+
210+ // A successful validation will redirect and set the auth cookie.
211+ code, cookie := getCallbackEndpoint(pac_test)
212+ assert.Equal(t, 302, code)
213+ assert.NotEqual(t, nil, cookie)
214+
215+ // Now we make a regular request; the access_token from the cookie is
216+ // forwarded as the "X-Forwarded-Access-Token" header. The token is
217+ // read by the test provider server and written in the response body.
218+ code, payload := getRootEndpoint(pac_test, cookie)
219+ assert.Equal(t, 200, code)
220+ assert.Equal(t, "my_auth_token", payload)
221+}
222+
223+func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
224+ pac_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
225+ PassAccessToken: false,
226+ })
227+ defer Close(pac_test)
228+
229+ // A successful validation will redirect and set the auth cookie.
230+ code, cookie := getCallbackEndpoint(pac_test)
231+ assert.Equal(t, 302, code)
232+ assert.NotEqual(t, nil, cookie)
233+
234+ // Now we make a regular request, but the access token header should
235+ // not be present.
236+ code, payload := getRootEndpoint(pac_test, cookie)
237+ assert.Equal(t, 200, code)
238+ assert.Equal(t, "No access token found.", payload)
239+}