2015-01-04 20:33:53 +02:00
|
|
|
package auth
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
import (
|
2015-04-03 20:49:28 +02:00
|
|
|
"bytes"
|
2015-02-25 01:01:56 +02:00
|
|
|
"errors"
|
|
|
|
"html/template"
|
2015-03-30 18:55:03 +02:00
|
|
|
"io/ioutil"
|
2015-02-25 01:01:56 +02:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"gopkg.in/authboss.v0"
|
|
|
|
"gopkg.in/authboss.v0/internal/mocks"
|
|
|
|
)
|
|
|
|
|
|
|
|
func testSetup() (a *Auth, s *mocks.MockStorer) {
|
|
|
|
s = mocks.NewMockStorer()
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ab := authboss.New()
|
|
|
|
ab.LogWriter = ioutil.Discard
|
|
|
|
ab.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
|
|
|
|
ab.Storer = s
|
|
|
|
ab.XSRFName = "xsrf"
|
|
|
|
ab.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
|
2015-02-25 01:01:56 +02:00
|
|
|
return "xsrfvalue"
|
|
|
|
}
|
2015-04-01 00:27:47 +02:00
|
|
|
ab.PrimaryID = authboss.StoreUsername
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
a = &Auth{}
|
2015-04-01 00:27:47 +02:00
|
|
|
if err := a.Initialize(ab); err != nil {
|
2015-02-25 01:01:56 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return a, s
|
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
func testRequest(ab *authboss.Authboss, method string, postFormValues ...string) (*authboss.Context, *httptest.ResponseRecorder, *http.Request, authboss.ClientStorerErr) {
|
2015-02-25 01:01:56 +02:00
|
|
|
r, err := http.NewRequest(method, "", nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sessionStorer := mocks.NewMockClientStorer()
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx := mocks.MockRequestContext(ab, postFormValues...)
|
2015-02-25 01:01:56 +02:00
|
|
|
ctx.SessionStorer = sessionStorer
|
|
|
|
|
|
|
|
return ctx, httptest.NewRecorder(), r, sessionStorer
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, _ := testSetup()
|
|
|
|
|
|
|
|
storage := a.Storage()
|
2015-04-01 00:27:47 +02:00
|
|
|
if storage[a.PrimaryID] != authboss.String {
|
|
|
|
t.Error("Expected storage KV:", a.PrimaryID, authboss.String)
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
if storage[authboss.StorePassword] != authboss.String {
|
|
|
|
t.Error("Expected storage KV:", authboss.StorePassword, authboss.String)
|
|
|
|
}
|
|
|
|
|
|
|
|
routes := a.Routes()
|
2015-02-25 20:22:55 +02:00
|
|
|
if routes["/login"] == nil {
|
|
|
|
t.Error("Expected route '/login' with handleFunc")
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
2015-02-25 20:22:55 +02:00
|
|
|
if routes["/logout"] == nil {
|
|
|
|
t.Error("Expected route '/logout' with handleFunc")
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_GET(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, _ := testSetup()
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, _ := testRequest(a.Authboss, "GET")
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
body := w.Body.String()
|
|
|
|
if !strings.Contains(body, "<form") {
|
|
|
|
t.Error("Should have rendered a form")
|
|
|
|
}
|
2015-04-01 00:27:47 +02:00
|
|
|
if !strings.Contains(body, `name="`+a.PrimaryID) {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Form should contain the primary ID field:", body)
|
|
|
|
}
|
|
|
|
if !strings.Contains(body, `name="password"`) {
|
|
|
|
t.Error("Form should contain password field:", body)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_POST_ReturnsErrorOnCallbackFailure(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-26 09:05:14 +02:00
|
|
|
a, storer := testSetup()
|
|
|
|
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
|
2015-02-25 01:01:56 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
a.Callbacks = authboss.NewCallbacks()
|
|
|
|
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
|
2015-02-25 01:01:56 +02:00
|
|
|
return authboss.InterruptNone, errors.New("explode")
|
|
|
|
})
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err.Error() != "explode" {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-26 09:05:14 +02:00
|
|
|
a, storer := testSetup()
|
|
|
|
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
|
2015-02-25 01:01:56 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
a.Callbacks = authboss.NewCallbacks()
|
|
|
|
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
|
2015-02-25 01:01:56 +02:00
|
|
|
return authboss.InterruptAccountLocked, nil
|
|
|
|
})
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, sessionStore := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusFound {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
loc := w.Header().Get("Location")
|
2015-04-01 00:27:47 +02:00
|
|
|
if loc != a.AuthLoginFailPath {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Unexpeced location:", loc)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedMsg := "Your account has been locked."
|
|
|
|
if msg, ok := sessionStore.Get(authboss.FlashErrorKey); !ok || msg != expectedMsg {
|
|
|
|
t.Error("Expected error flash message:", expectedMsg)
|
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
a.Callbacks = authboss.NewCallbacks()
|
|
|
|
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
|
2015-02-25 01:01:56 +02:00
|
|
|
return authboss.InterruptAccountNotConfirmed, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusFound {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
loc = w.Header().Get("Location")
|
2015-04-01 00:27:47 +02:00
|
|
|
if loc != a.AuthLoginFailPath {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Unexpeced location:", loc)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedMsg = "Your account has not been confirmed."
|
|
|
|
if msg, ok := sessionStore.Get(authboss.FlashErrorKey); !ok || msg != expectedMsg {
|
|
|
|
t.Error("Expected error flash message:", expectedMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_POST_AuthenticationFailure(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, _ := testSetup()
|
|
|
|
|
2015-04-03 20:49:28 +02:00
|
|
|
log := &bytes.Buffer{}
|
|
|
|
a.LogWriter = log
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1")
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
body := w.Body.String()
|
|
|
|
if !strings.Contains(body, "invalid username and/or password") {
|
|
|
|
t.Error("Should have rendered with error")
|
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, _ = testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
body = w.Body.String()
|
2015-07-02 03:07:26 +02:00
|
|
|
if !strings.Contains(body, "invalid username and/or password") {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Should have rendered with error")
|
|
|
|
}
|
2015-04-03 20:49:28 +02:00
|
|
|
|
2015-07-02 03:07:26 +02:00
|
|
|
ctx, w, r, _ = testRequest(a.Authboss, "POST", "username", "jake", "password", "1")
|
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
body = w.Body.String()
|
|
|
|
if !strings.Contains(body, "invalid username and/or password") {
|
|
|
|
t.Error("Should have rendered with error")
|
2015-04-03 20:49:28 +02:00
|
|
|
}
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_POST(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, storer := testSetup()
|
|
|
|
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
|
2015-02-25 01:01:56 +02:00
|
|
|
cb := mocks.NewMockAfterCallback()
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
a.Callbacks = authboss.NewCallbacks()
|
|
|
|
a.Callbacks.After(authboss.EventAuth, cb.Fn)
|
|
|
|
a.AuthLoginOKPath = "/dashboard"
|
2015-02-26 09:05:14 +02:00
|
|
|
|
|
|
|
sessions := mocks.NewMockClientStorer()
|
|
|
|
ctx.SessionStorer = sessions
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !cb.HasBeenCalled {
|
|
|
|
t.Error("Expected after callback to have been called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Code != http.StatusFound {
|
|
|
|
t.Error("Unexpected status:", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
loc := w.Header().Get("Location")
|
2015-04-01 00:27:47 +02:00
|
|
|
if loc != a.AuthLoginOKPath {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Unexpeced location:", loc)
|
|
|
|
}
|
2015-02-26 09:05:14 +02:00
|
|
|
|
|
|
|
val, ok := sessions.Values[authboss.SessionKey]
|
|
|
|
if !ok {
|
|
|
|
t.Error("Expected session to be set")
|
|
|
|
} else if val != "john" {
|
|
|
|
t.Error("Expected session value to be authed username")
|
|
|
|
}
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_loginHandlerFunc_OtherMethods(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, _ := testSetup()
|
|
|
|
methods := []string{"HEAD", "PUT", "DELETE", "TRACE", "CONNECT"}
|
|
|
|
|
|
|
|
for i, method := range methods {
|
|
|
|
r, err := http.NewRequest(method, "/login", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%d> Unexpected error '%s'", i, err)
|
|
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
if err := a.loginHandlerFunc(nil, w, r); err != nil {
|
|
|
|
t.Errorf("%d> Unexpected error: %s", i, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if http.StatusMethodNotAllowed != w.Code {
|
|
|
|
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_validateCredentials(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
ab := authboss.New()
|
2015-02-25 01:01:56 +02:00
|
|
|
storer := mocks.NewMockStorer()
|
2015-04-01 00:27:47 +02:00
|
|
|
ab.Storer = storer
|
2015-02-25 01:01:56 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx := ab.NewContext()
|
2015-07-02 03:07:26 +02:00
|
|
|
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$pgFsuQwdhwOdZp/v52dvHeEi53ZaI7dGmtwK4bAzGGN5A4nT6doqm"}
|
|
|
|
if _, err := validateCredentials(ctx, "john", "a"); err != nil {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
2015-07-02 03:07:26 +02:00
|
|
|
ctx = ab.NewContext()
|
|
|
|
if valid, err := validateCredentials(ctx, "jake", "a"); err != nil {
|
|
|
|
t.Error("Expect no error when user not found:", err)
|
|
|
|
} else if valid {
|
|
|
|
t.Error("Expect invalid when not user found")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = ab.NewContext()
|
|
|
|
storer.GetErr = "Failed to load user"
|
|
|
|
if _, err := validateCredentials(ctx, "", ""); err.Error() != "Failed to load user" {
|
2015-02-25 01:01:56 +02:00
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
2015-07-02 03:07:26 +02:00
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_logoutHandlerFunc_GET(t *testing.T) {
|
2015-04-01 00:27:47 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2015-02-25 01:01:56 +02:00
|
|
|
a, _ := testSetup()
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
a.AuthLogoutOKPath = "/dashboard"
|
2015-02-25 01:01:56 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
ctx, w, r, sessionStorer := testRequest(a.Authboss, "GET")
|
2015-02-25 01:01:56 +02:00
|
|
|
sessionStorer.Put(authboss.SessionKey, "asdf")
|
2015-03-03 08:09:32 +02:00
|
|
|
sessionStorer.Put(authboss.SessionLastAction, "1234")
|
|
|
|
|
|
|
|
cookieStorer := mocks.NewMockClientStorer(authboss.CookieRemember, "qwert")
|
|
|
|
ctx.CookieStorer = cookieStorer
|
2015-02-25 01:01:56 +02:00
|
|
|
|
|
|
|
if err := a.logoutHandlerFunc(ctx, w, r); err != nil {
|
|
|
|
t.Error("Unexpected error:", err)
|
|
|
|
}
|
|
|
|
|
2015-03-03 08:09:32 +02:00
|
|
|
if val, ok := sessionStorer.Get(authboss.SessionKey); ok {
|
2015-03-16 23:51:44 +02:00
|
|
|
t.Error("Unexpected session key:", val)
|
2015-03-03 08:09:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if val, ok := sessionStorer.Get(authboss.SessionLastAction); ok {
|
2015-03-16 23:51:44 +02:00
|
|
|
t.Error("Unexpected last action:", val)
|
2015-03-03 08:09:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if val, ok := cookieStorer.Get(authboss.CookieRemember); ok {
|
2015-03-16 23:51:44 +02:00
|
|
|
t.Error("Unexpected rm cookie:", val)
|
2015-02-25 01:01:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if http.StatusFound != w.Code {
|
|
|
|
t.Errorf("Expected status code %d, got %d", http.StatusFound, w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
location := w.Header().Get("Location")
|
|
|
|
if location != "/dashboard" {
|
|
|
|
t.Errorf("Expected lcoation %s, got %s", "/dashboard", location)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAuth_logoutHandlerFunc_OtherMethods(t *testing.T) {
|
|
|
|
a, _ := testSetup()
|
|
|
|
|
|
|
|
methods := []string{"HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}
|
|
|
|
|
|
|
|
for i, method := range methods {
|
|
|
|
r, err := http.NewRequest(method, "/logout", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%d> Unexpected error '%s'", i, err)
|
|
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
if err := a.logoutHandlerFunc(nil, w, r); err != nil {
|
|
|
|
t.Errorf("%d> Unexpected error: %s", i, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if http.StatusMethodNotAllowed != w.Code {
|
|
|
|
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|