mirror of https://github.com/coder/coder.git
147 lines
4.9 KiB
Go
147 lines
4.9 KiB
Go
package httpmw_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/pkg/namesgenerator"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/oauth2"
|
|
|
|
"github.com/coder/coder/v2/coderd/httpmw"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
type testOAuth2Provider struct {
|
|
t testing.TB
|
|
authOpts []oauth2.AuthCodeOption
|
|
}
|
|
|
|
func (p *testOAuth2Provider) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
|
assert.EqualValues(p.t, p.authOpts, opts)
|
|
return "?state=" + url.QueryEscape(state)
|
|
}
|
|
|
|
func (*testOAuth2Provider) Exchange(_ context.Context, _ string, _ ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
|
return &oauth2.Token{
|
|
AccessToken: "hello",
|
|
}, nil
|
|
}
|
|
|
|
func (*testOAuth2Provider) TokenSource(_ context.Context, _ *oauth2.Token) oauth2.TokenSource {
|
|
return nil
|
|
}
|
|
|
|
func newTestOAuth2Provider(t testing.TB, opts ...oauth2.AuthCodeOption) *testOAuth2Provider {
|
|
return &testOAuth2Provider{
|
|
t: t,
|
|
authOpts: opts,
|
|
}
|
|
}
|
|
|
|
// nolint:bodyclose
|
|
func TestOAuth2(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("NotSetup", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
res := httptest.NewRecorder()
|
|
httpmw.ExtractOAuth2(nil, nil, nil)(nil).ServeHTTP(res, req)
|
|
require.Equal(t, http.StatusBadRequest, res.Result().StatusCode)
|
|
})
|
|
t.Run("RedirectWithoutCode", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?redirect="+url.QueryEscape("/dashboard"), nil)
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
|
|
location := res.Header().Get("Location")
|
|
if !assert.NotEmpty(t, location) {
|
|
return
|
|
}
|
|
require.Len(t, res.Result().Cookies(), 2)
|
|
cookie := res.Result().Cookies()[1]
|
|
require.Equal(t, "/dashboard", cookie.Value)
|
|
})
|
|
t.Run("NoState", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?code=something", nil)
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
|
|
require.Equal(t, http.StatusBadRequest, res.Result().StatusCode)
|
|
})
|
|
t.Run("NoStateCookie", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?code=something&state=test", nil)
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
|
|
require.Equal(t, http.StatusUnauthorized, res.Result().StatusCode)
|
|
})
|
|
t.Run("MismatchedState", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?code=something&state=test", nil)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: codersdk.OAuth2StateCookie,
|
|
Value: "mismatch",
|
|
})
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
|
|
require.Equal(t, http.StatusUnauthorized, res.Result().StatusCode)
|
|
})
|
|
t.Run("ExchangeCodeAndState", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?code=test&state=something", nil)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: codersdk.OAuth2StateCookie,
|
|
Value: "something",
|
|
})
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "oauth_redirect",
|
|
Value: "/dashboard",
|
|
})
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
state := httpmw.OAuth2(r)
|
|
require.Equal(t, "/dashboard", state.Redirect)
|
|
})).ServeHTTP(res, req)
|
|
})
|
|
t.Run("CustomAuthCodeOptions", func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequest("GET", "/?redirect="+url.QueryEscape("/dashboard"), nil)
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("foo", "bar"))
|
|
authOpts := map[string]string{"foo": "bar"}
|
|
httpmw.ExtractOAuth2(tp, nil, authOpts)(nil).ServeHTTP(res, req)
|
|
location := res.Header().Get("Location")
|
|
// Ideally we would also assert that the location contains the query params
|
|
// we set in the auth URL but this would essentially be testing the oauth2 package.
|
|
// testOAuth2Provider does this job for us.
|
|
require.NotEmpty(t, location)
|
|
})
|
|
t.Run("PresetConvertState", func(t *testing.T) {
|
|
t.Parallel()
|
|
customState := namesgenerator.GetRandomName(1)
|
|
req := httptest.NewRequest("GET", "/?oidc_merge_state="+customState+"&redirect="+url.QueryEscape("/dashboard"), nil)
|
|
res := httptest.NewRecorder()
|
|
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
|
|
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
|
|
|
|
found := false
|
|
for _, cookie := range res.Result().Cookies() {
|
|
if cookie.Name == codersdk.OAuth2StateCookie {
|
|
require.Equal(t, cookie.Value, customState, "expected state")
|
|
found = true
|
|
}
|
|
}
|
|
require.True(t, found, "expected state cookie")
|
|
})
|
|
}
|