You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

149 lines
6.1 KiB

package web
import (
"errors"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"webfilebrowser/internal/auth"
)
func TestNonAdminUsersAreForbiddenFromEveryAdminRoute(t *testing.T) {
store, handler := newTestApplication(t)
created, err := store.CreateUser("reader@example.com", "Temporary-1!", auth.RoleUser)
if err != nil {
t.Fatalf("CreateUser() error = %v", err)
}
userCookie := completeUserPasswordChange(t, handler, created.Email, "Temporary-1!", "Reader-password-2!")
for _, target := range []struct {
method string
path string
}{
{method: http.MethodGet, path: "/admin"},
{method: http.MethodGet, path: adminUsersPath},
{method: http.MethodPost, path: adminCreateUserPath},
{method: http.MethodPost, path: adminResetUserPath},
{method: http.MethodPost, path: adminDisableUserPath},
{method: http.MethodPost, path: adminDeleteUserPath},
} {
t.Run(target.method+" "+target.path, func(t *testing.T) {
response := postOrGetWithCookie(handler, target.method, target.path, userCookie)
if response.Code != http.StatusForbidden {
t.Errorf("%s %s status = %d, want %d", target.method, target.path, response.Code, http.StatusForbidden)
}
if response.Header().Get("Location") != "" {
t.Errorf("%s %s redirected to %q, want no redirect", target.method, target.path, response.Header().Get("Location"))
}
})
}
}
func TestAdminCanManageUsersAndTemporaryPasswordsRequireChange(t *testing.T) {
store, handler := newTestApplication(t)
adminCookie := completeSeedPasswordChange(t, handler)
usersResponse := getWithCookie(handler, adminUsersPath, adminCookie)
if usersResponse.Code != http.StatusOK || !strings.Contains(usersResponse.Body.String(), "Create user") {
t.Fatalf("GET %s = (%d, %q), want admin management page", adminUsersPath, usersResponse.Code, usersResponse.Body.String())
}
createResponse := postForm(handler, adminCreateUserPath, url.Values{
"email": {"new-reader@example.com"},
"temporary_password": {"Temporary-1!"},
"role": {string(auth.RoleUser)},
}, adminCookie)
if createResponse.Code != http.StatusSeeOther || createResponse.Header().Get("Location") != adminUsersPath {
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminCreateUserPath, createResponse.Code, createResponse.Header().Get("Location"), adminUsersPath)
}
created := managedUser(t, store, "new-reader@example.com")
loginResponse := postForm(handler, loginPath, url.Values{
"email": {created.Email},
"password": {"Temporary-1!"},
}, nil)
if loginResponse.Code != http.StatusSeeOther || loginResponse.Header().Get("Location") != changePasswordPath {
t.Fatalf("new user login = (%d, %q), want redirect to %s", loginResponse.Code, loginResponse.Header().Get("Location"), changePasswordPath)
}
resetResponse := postForm(handler, adminResetUserPath, url.Values{
"id": {strconv.FormatInt(created.ID, 10)},
"temporary_password": {"Replacement-2!"},
}, adminCookie)
if resetResponse.Code != http.StatusSeeOther || resetResponse.Header().Get("Location") != adminUsersPath {
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminResetUserPath, resetResponse.Code, resetResponse.Header().Get("Location"), adminUsersPath)
}
if _, err := store.Authenticate(created.Email, "Replacement-2!"); err != nil {
t.Fatalf("Authenticate(reset user) error = %v", err)
}
disableResponse := postForm(handler, adminDisableUserPath, url.Values{"id": {strconv.FormatInt(created.ID, 10)}}, adminCookie)
if disableResponse.Code != http.StatusSeeOther || disableResponse.Header().Get("Location") != adminUsersPath {
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminDisableUserPath, disableResponse.Code, disableResponse.Header().Get("Location"), adminUsersPath)
}
if _, err := store.Authenticate(created.Email, "Replacement-2!"); !errors.Is(err, auth.ErrInvalidCredentials) {
t.Errorf("Authenticate(disabled user) error = %v, want invalid credentials", err)
}
deleteResponse := postForm(handler, adminDeleteUserPath, url.Values{"id": {strconv.FormatInt(created.ID, 10)}}, adminCookie)
if deleteResponse.Code != http.StatusSeeOther || deleteResponse.Header().Get("Location") != adminUsersPath {
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminDeleteUserPath, deleteResponse.Code, deleteResponse.Header().Get("Location"), adminUsersPath)
}
if got := managedUserOrNil(t, store, created.Email); got != nil {
t.Errorf("deleted user remains in management list: %#v", got)
}
}
func completeUserPasswordChange(t *testing.T, handler http.Handler, email, temporaryPassword, newPassword string) *http.Cookie {
t.Helper()
loginResponse := postForm(handler, loginPath, url.Values{
"email": {email},
"password": {temporaryPassword},
}, nil)
if loginResponse.Code != http.StatusSeeOther || loginResponse.Header().Get("Location") != changePasswordPath {
t.Fatalf("temporary user login = (%d, %q), want redirect to %s", loginResponse.Code, loginResponse.Header().Get("Location"), changePasswordPath)
}
cookie := responseCookie(t, loginResponse, sessionCookieName)
changeResponse := postForm(handler, changePasswordPath, url.Values{
"current_password": {temporaryPassword},
"new_password": {newPassword},
}, cookie)
if changeResponse.Code != http.StatusSeeOther {
t.Fatalf("POST %s = %d, want %d", changePasswordPath, changeResponse.Code, http.StatusSeeOther)
}
return cookie
}
func postOrGetWithCookie(handler http.Handler, method, target string, cookie *http.Cookie) *httptest.ResponseRecorder {
if method == http.MethodGet {
return getWithCookie(handler, target, cookie)
}
return postForm(handler, target, url.Values{}, cookie)
}
func managedUser(t *testing.T, store *auth.Store, email string) auth.User {
t.Helper()
user := managedUserOrNil(t, store, email)
if user == nil {
t.Fatalf("ListUsers() did not return %q", email)
}
return *user
}
func managedUserOrNil(t *testing.T, store *auth.Store, email string) *auth.User {
t.Helper()
users, err := store.ListUsers()
if err != nil {
t.Fatalf("ListUsers() error = %v", err)
}
for index := range users {
if users[index].Email == email {
return &users[index]
}
}
return nil
}