dex/server/register_test.go
Evan Cordell a418e1c4e7 client: add client manager
adds a client manager to handle business logic, leaving the repo
for basic crud operations. Also adds client to the test script
2016-05-19 16:20:12 -07:00

385 lines
10 KiB
Go

package server
import (
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/kylelemons/godebug/pretty"
"github.com/coreos/dex/pkg/html"
"github.com/coreos/dex/user"
"github.com/coreos/go-oidc/oidc"
)
type testTemplate struct {
tpl Template
data registerTemplateData
}
func (t *testTemplate) Execute(w io.Writer, data interface{}) error {
dataMap, ok := data.(registerTemplateData)
if !ok {
return errors.New("could not cast to registerTemplateData")
}
t.data = dataMap
return t.tpl.Execute(w, data)
}
func TestHandleRegister(t *testing.T) {
testIssuerAuth := testIssuerURL
testIssuerAuth.Path = "/auth"
str := func(s string) []string {
return []string{s}
}
tests := []struct {
// inputs
query url.Values
connID string
attachRemote bool
remoteIdentityEmail string
remoteAlreadyExists bool
// want
wantStatus int
wantFormValues url.Values
wantUserExists bool
wantRedirectURL url.URL
wantRegisterTemplateData *registerTemplateData
}{
{
// User comes in with a valid code, redirected from the connector,
// and is shown the form.
query: url.Values{
"code": []string{"code-2"},
},
connID: "local",
wantStatus: http.StatusOK,
wantFormValues: url.Values{
"code": str("code-3"),
"email": str(""),
"password": str(""),
"validate": str("1"),
},
},
{
// User comes in with a valid code, redirected from the connector,
// user is created with a verified email, because it's a trusted
// email provider.
query: url.Values{
"code": []string{"code-3"},
},
connID: "oidc-trusted",
remoteIdentityEmail: "test@example.com",
attachRemote: true,
wantStatus: http.StatusSeeOther,
wantUserExists: true,
},
{
// User comes in with a valid code, redirected from the connector.
// User is redirected to dex page with msg_code "login-maybe",
// because the remote identity already exists.
query: url.Values{
"code": []string{"code-3"},
},
connID: "oidc-trusted",
remoteIdentityEmail: "test@example.com",
attachRemote: true,
remoteAlreadyExists: true,
wantStatus: http.StatusOK,
wantUserExists: true,
wantRegisterTemplateData: &registerTemplateData{
RemoteExists: &remoteExistsData{
Login: newURLWithParams(testRedirectURL, url.Values{
"code": []string{"code-7"},
"state": []string{""},
}).String(),
Register: newURLWithParams(testIssuerAuth, url.Values{
"client_id": []string{testClientID},
"redirect_uri": []string{testRedirectURL.String()},
"register": []string{"1"},
"scope": []string{"openid"},
"state": []string{""},
}).String(),
},
},
},
{
// User comes in with a valid code, redirected from the connector,
// user is created with a verified email, because it's a trusted
// email provider. In addition, the email provided on the URL is
// ignored, and instead comes from the remote identity.
query: url.Values{
"code": []string{"code-3"},
"email": []string{"sneaky@example.com"},
},
connID: "oidc-trusted",
remoteIdentityEmail: "test@example.com",
attachRemote: true,
wantStatus: http.StatusSeeOther,
wantUserExists: true,
},
{
// User comes in with a valid code, redirected from the connector,
// it's a trusted provider, but no email so no user created, and the
// form comes back with the code.
query: url.Values{
"code": []string{"code-3"},
},
connID: "oidc-trusted",
remoteIdentityEmail: "",
attachRemote: true,
wantStatus: http.StatusOK,
wantUserExists: false,
wantFormValues: url.Values{
"code": str("code-4"),
"email": str(""),
"validate": str("1"),
},
},
{
// User comes in with a valid code, redirected from the connector,
// it's a trusted provider, but the email is invalid, so no user
// created, and the form comes back with the code.
query: url.Values{
"code": []string{"code-3"},
},
connID: "oidc-trusted",
remoteIdentityEmail: "notanemail",
attachRemote: true,
wantStatus: http.StatusOK,
wantUserExists: false,
wantFormValues: url.Values{
"code": str("code-4"),
"email": str(""),
"validate": str("1"),
},
},
{
// User comes in with a valid code, having submitted the form, but
// has a invalid email.
query: url.Values{
"code": []string{"code-2"},
"validate": []string{"1"},
"email": str(""),
"password": str("password"),
},
connID: "local",
wantStatus: http.StatusBadRequest,
wantFormValues: url.Values{
"code": str("code-3"),
"email": str(""),
"password": str("password"),
"validate": str("1"),
},
},
{
// User comes in with a valid code, having submitted the form. A new
// user is created.
query: url.Values{
"code": []string{"code-2"},
"validate": []string{"1"},
"email": str("test@example.com"),
"password": str("password"),
},
connID: "local",
wantStatus: http.StatusSeeOther,
wantUserExists: true,
},
{
// User comes in with spaces in their email, having submitted the
// form. The email is trimmed and the user is created.
query: url.Values{
"code": []string{"code-2"},
"validate": []string{"1"},
"email": str("\t\ntest@example.com "),
"password": str("password"),
},
connID: "local",
wantStatus: http.StatusSeeOther,
wantUserExists: true,
},
{
// User comes in with an invalid email, having submitted the form.
// The email is rejected and the user is not created.
query: url.Values{
"code": []string{"code-2"},
"validate": []string{"1"},
"email": str("aninvalidemail"),
"password": str("password"),
},
connID: "local",
wantStatus: http.StatusBadRequest,
wantFormValues: url.Values{
"code": str("code-3"),
"email": str("aninvalidemail"),
"password": str("password"),
"validate": str("1"),
},
},
{
// User comes in with a valid code, having submitted the form, but
// there's no password.
query: url.Values{
"code": []string{"code-2"},
"validate": []string{"1"},
"email": str("test@example.com"),
},
connID: "local",
wantStatus: http.StatusBadRequest,
wantUserExists: false,
wantFormValues: url.Values{
"code": str("code-3"),
"email": str("test@example.com"),
"password": str(""),
"validate": str("1"),
},
},
{
// User comes in with a valid code, having submitted the form, but
// there's no password, but they don't need one because connector ID
// is oidc.
query: url.Values{
"code": []string{"code-3"},
"validate": []string{"1"},
"email": str("test@example.com"),
},
connID: "oidc",
attachRemote: true,
wantStatus: http.StatusSeeOther,
wantUserExists: true,
},
{
// Same as before, but missing a code.
query: url.Values{
"validate": []string{"1"},
"email": str("test@example.com"),
},
connID: "oidc",
attachRemote: true,
wantStatus: http.StatusUnauthorized,
wantUserExists: false,
},
}
for i, tt := range tests {
f, err := makeTestFixtures()
if err != nil {
t.Fatalf("case %d: could not make test fixtures: %v", i, err)
}
if tt.remoteAlreadyExists {
f.userRepo.Create(nil, user.User{
ID: "register-test-new-user",
Email: tt.remoteIdentityEmail,
EmailVerified: true,
})
f.userRepo.AddRemoteIdentity(nil, "register-test-new-user",
user.RemoteIdentity{
ID: "remoteID",
ConnectorID: tt.connID,
})
}
key, err := f.srv.NewSession(tt.connID, testClientID, "", f.redirectURL, "", true, []string{"openid"})
t.Logf("case %d: key for NewSession: %v", i, key)
if tt.attachRemote {
sesID, err := f.sessionManager.ExchangeKey(key)
if err != nil {
t.Fatalf("case %d: expected non-nil error: %v", i, err)
}
ses, err := f.sessionManager.Get(sesID)
if err != nil {
t.Fatalf("case %d: expected non-nil error: %v", i, err)
}
_, err = f.sessionManager.AttachRemoteIdentity(ses.ID, oidc.Identity{
ID: "remoteID",
Email: tt.remoteIdentityEmail,
})
key, err := f.sessionManager.NewSessionKey(sesID)
if err != nil {
t.Fatalf("case %d: expected non-nil error: %v", i, err)
}
t.Logf("case %d: key for NewSession: %v", i, key)
}
tpl := &testTemplate{tpl: f.srv.RegisterTemplate}
hdlr := handleRegisterFunc(f.srv, tpl)
w := httptest.NewRecorder()
u := "http://server.example.com"
req, err := http.NewRequest("POST", u, strings.NewReader(tt.query.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if err != nil {
t.Errorf("case %d: unable to form HTTP request: %v", i, err)
}
hdlr.ServeHTTP(w, req)
if tt.wantRedirectURL.String() != "" {
locationHdr := w.HeaderMap.Get("Location")
redirURL, err := url.Parse(locationHdr)
if err != nil {
t.Errorf("case %d: unexpected error parsing url %q: %q", i, locationHdr, err)
} else {
if diff := pretty.Compare(*redirURL, tt.wantRedirectURL); diff != "" {
t.Errorf("case %d: Compare(redirURL, tt.wantRedirectURL) = %v", i, diff)
}
}
}
if tt.wantStatus != w.Code {
t.Errorf("case %d: wantStatus=%v, got=%v", i, tt.wantStatus, w.Code)
}
_, err = f.userRepo.GetByEmail(nil, "test@example.com")
if tt.wantUserExists {
if err != nil {
t.Errorf("case %d: user not created: %v", i, err)
}
} else if err != user.ErrorNotFound {
t.Errorf("case %d: unexpected error looking up user: want=%v, got=%v ", i, user.ErrorNotFound, err)
}
values, err := html.FormValues("#registerForm", w.Body)
if err != nil {
t.Errorf("case %d: could not parse form: %v", i, err)
}
if diff := pretty.Compare(tt.wantFormValues, values); diff != "" {
t.Errorf("case %d: Compare(want, got) = %v", i, diff)
}
if tt.wantRegisterTemplateData != nil {
if diff := pretty.Compare(*tt.wantRegisterTemplateData, tpl.data); diff != "" {
t.Errorf("case %d: Compare(tt.wantRegisterTemplateData, tpl.data) = %v",
i, diff)
}
}
}
}
func newURLWithParams(u url.URL, values url.Values) *url.URL {
newU := u
newU.RawQuery = values.Encode()
return &newU
}