@@ -1,9 +1,12 @@
1package main
1package main
4 "encoding/base64"
5 "encoding/base64"
6+ "github.com/18F/hmacauth"
5 "github.com/bitly/oauth2_proxy/providers"
7 "github.com/bitly/oauth2_proxy/providers"
6 "github.com/bmizerany/assert"
8 "github.com/bmizerany/assert"
7 "io/ioutil"
10 "io/ioutil"
@@ -88,6 +91,45 @@ func TestRobotsTxt(t *testing.T) {
88 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
91 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
94+type TestProvider struct {
95+ *providers.ProviderData
100+func NewTestProvider(provider_url *url.URL, email_address string) *TestProvider {
101+ return &TestProvider{
102+ ProviderData: &providers.ProviderData{
103+ ProviderName: "Test Provider",
106+ Host: provider_url.Host,
107+ Path: "/oauth/authorize",
109+ RedeemURL: &url.URL{
111+ Host: provider_url.Host,
112+ Path: "/oauth/token",
114+ ProfileURL: &url.URL{
116+ Host: provider_url.Host,
117+ Path: "/api/v1/profile",
119+ Scope: "profile.email",
121+ EmailAddress: email_address,
125+func (tp *TestProvider) GetEmailAddress(session *providers.SessionState) (string, error) {
126+ return tp.EmailAddress, nil
129+func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bool {
130+ return tp.ValidToken
91func TestBasicAuthPassword(t *testing.T) {
133func TestBasicAuthPassword(t *testing.T) {
92 provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
134 provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93 log.Printf("%#v", r)
135 log.Printf("%#v", r)
@@ -121,29 +163,7 @@ func TestBasicAuthPassword(t *testing.T) {
121 const email_address = "michael.bland@gsa.gov"
163 const email_address = "michael.bland@gsa.gov"
122 const user_name = "michael.bland"
164 const user_name = "michael.bland"
124− opts.provider = &TestProvider{
166+ opts.provider = NewTestProvider(provider_url, email_address)
125− ProviderData: &providers.ProviderData{
126− ProviderName: "Test Provider",
129− Host: provider_url.Host,
130− Path: "/oauth/authorize",
132− RedeemURL: &url.URL{
134− Host: provider_url.Host,
135− Path: "/oauth/token",
137− ProfileURL: &url.URL{
139− Host: provider_url.Host,
140− Path: "/api/v1/profile",
142− Scope: "profile.email",
144− EmailAddress: email_address,
147 proxy := NewOAuthProxy(opts, func(email string) bool {
167 proxy := NewOAuthProxy(opts, func(email string) bool {
148 return email == email_address
168 return email == email_address
@@ -183,20 +203,6 @@ func TestBasicAuthPassword(t *testing.T) {
183 provider_server.Close()
203 provider_server.Close()
186−type TestProvider struct {
187− *providers.ProviderData
192−func (tp *TestProvider) GetEmailAddress(session *providers.SessionState) (string, error) {
193− return tp.EmailAddress, nil
196−func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bool {
197− return tp.ValidToken
200type PassAccessTokenTest struct {
206type PassAccessTokenTest struct {
201 provider_server *httptest.Server
207 provider_server *httptest.Server
202 proxy *OAuthProxy
208 proxy *OAuthProxy
@@ -242,29 +248,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
242 provider_url, _ := url.Parse(t.provider_server.URL)
248 provider_url, _ := url.Parse(t.provider_server.URL)
243 const email_address = "michael.bland@gsa.gov"
249 const email_address = "michael.bland@gsa.gov"
245− t.opts.provider = &TestProvider{
251+ t.opts.provider = NewTestProvider(provider_url, email_address)
246− ProviderData: &providers.ProviderData{
247− ProviderName: "Test Provider",
250− Host: provider_url.Host,
251− Path: "/oauth/authorize",
253− RedeemURL: &url.URL{
255− Host: provider_url.Host,
256− Path: "/oauth/token",
258− ProfileURL: &url.URL{
260− Host: provider_url.Host,
261− Path: "/api/v1/profile",
263− Scope: "profile.email",
265− EmailAddress: email_address,
268 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
252 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
269 return email == email_address
253 return email == email_address
@@ -559,7 +543,7 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
559func NewAuthOnlyEndpointTest() *ProcessCookieTest {
543func NewAuthOnlyEndpointTest() *ProcessCookieTest {
560 pc_test := NewProcessCookieTestWithDefaults()
544 pc_test := NewProcessCookieTestWithDefaults()
561 pc_test.req, _ = http.NewRequest("GET",
545 pc_test.req, _ = http.NewRequest("GET",
562− pc_test.opts.ProxyPrefix + "/auth", nil)
546+ pc_test.opts.ProxyPrefix+"/auth", nil)
563 return pc_test
547 return pc_test
@@ -610,3 +594,143 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
610 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
594 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
611 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
595 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
598+type SignatureAuthenticator struct {
599+ auth hmacauth.HmacAuth
602+func (v *SignatureAuthenticator) Authenticate(
603+ w http.ResponseWriter, r *http.Request) {
604+ result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
605+ if result == hmacauth.ResultNoSignature {
606+ w.Write([]byte("no signature received"))
607+ } else if result == hmacauth.ResultMatch {
608+ w.Write([]byte("signatures match"))
609+ } else if result == hmacauth.ResultMismatch {
610+ w.Write([]byte("signatures do not match:" +
611+ "\n received: " + headerSig +
612+ "\n computed: " + computedSig))
614+ panic("Unknown result value: " + result.String())
618+type SignatureTest struct {
620+ upstream *httptest.Server
621+ upstream_host string
622+ provider *httptest.Server
624+ rw *httptest.ResponseRecorder
625+ authenticator *SignatureAuthenticator
628+func NewSignatureTest() *SignatureTest {
629+ opts := NewOptions()
630+ opts.CookieSecret = "cookie secret"
631+ opts.ClientID = "client ID"
632+ opts.ClientSecret = "client secret"
633+ opts.EmailDomains = []string{"acm.org"}
635+ authenticator := &SignatureAuthenticator{}
636+ upstream := httptest.NewServer(
637+ http.HandlerFunc(authenticator.Authenticate))
638+ upstream_url, _ := url.Parse(upstream.URL)
639+ opts.Upstreams = append(opts.Upstreams, upstream.URL)
641+ providerHandler := func(w http.ResponseWriter, r *http.Request) {
642+ w.Write([]byte(`{"access_token": "my_auth_token"}`))
644+ provider := httptest.NewServer(http.HandlerFunc(providerHandler))
645+ provider_url, _ := url.Parse(provider.URL)
646+ opts.provider = NewTestProvider(provider_url, "mbland@acm.org")
648+ return &SignatureTest{
654+ httptest.NewRecorder(),
659+func (st *SignatureTest) Close() {
664+// fakeNetConn simulates an http.Request.Body buffer that will be consumed
665+// when it is read by the hmacauth.HmacAuth if not handled properly. See:
666+// https://github.com/18F/hmacauth/pull/4
667+type fakeNetConn struct {
671+func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
672+ if bodyLen := len(fnc.reqBody); bodyLen != 0 {
673+ copy(p, fnc.reqBody)
675+ return bodyLen, io.EOF
680+func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
681+ err := st.opts.Validate()
685+ proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
687+ var bodyBuf io.ReadCloser
689+ bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
691+ req, err := http.NewRequest(method, "/foo/bar", bodyBuf)
695+ req.Header = st.header
697+ state := &providers.SessionState{
698+ Email: "mbland@acm.org", AccessToken: "my_access_token"}
699+ value, err := proxy.provider.CookieForSession(state, proxy.CookieCipher)
703+ cookie := proxy.MakeCookie(req, value, proxy.CookieExpire, time.Now())
704+ req.AddCookie(cookie)
705+ // This is used by the upstream to validate the signature.
706+ st.authenticator.auth = hmacauth.NewHmacAuth(
707+ crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
708+ proxy.ServeHTTP(st.rw, req)
711+func TestNoRequestSignature(t *testing.T) {
712+ st := NewSignatureTest()
714+ st.MakeRequestWithExpectedKey("GET", "", "")
715+ assert.Equal(t, 200, st.rw.Code)
716+ assert.Equal(t, st.rw.Body.String(), "no signature received")
719+func TestRequestSignatureGetRequest(t *testing.T) {
720+ st := NewSignatureTest()
722+ st.opts.SignatureKey = "sha1:foobar"
723+ st.MakeRequestWithExpectedKey("GET", "", "foobar")
724+ assert.Equal(t, 200, st.rw.Code)
725+ assert.Equal(t, st.rw.Body.String(), "signatures match")
728+func TestRequestSignaturePostRequest(t *testing.T) {
729+ st := NewSignatureTest()
731+ st.opts.SignatureKey = "sha1:foobar"
732+ payload := `{ "hello": "world!" }`
733+ st.MakeRequestWithExpectedKey("POST", payload, "foobar")
734+ assert.Equal(t, 200, st.rw.Code)
735+ assert.Equal(t, st.rw.Body.String(), "signatures match")