package controllers; import static org.fest.assertions.Assertions.assertThat; import static play.mvc.Http.Status.OK; import static play.test.Helpers.callAction; import static play.test.Helpers.charset; import static play.test.Helpers.contentAsString; import static play.test.Helpers.contentType; import static play.test.Helpers.fakeRequest; import static play.test.Helpers.flash; import static play.test.Helpers.header; import static play.test.Helpers.running; import static play.test.Helpers.session; import static play.test.Helpers.status; import static test.AorraTestUtils.asAdminUser; import static test.AorraTestUtils.fakeAorraApp; import static test.AorraTestUtils.jcrom; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.jcr.RepositoryException; import javax.jcr.Session; import models.GroupManager; import models.User; import models.UserDAO; import org.apache.jackrabbit.api.security.user.Group; import org.jcrom.Jcrom; import org.junit.Test; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import com.google.common.collect.ImmutableMap; import play.Play; import play.api.mvc.Call; import play.libs.F; import play.mvc.Result; import play.test.FakeRequest; import providers.JackrabbitEmailPasswordAuthProvider; import service.GuiceInjectionPlugin; import service.JcrSessionFactory; public class ApplicationTest { @Test public void indexRedirectsToLoginPage() { running(fakeAorraApp(false), new Runnable() { @Override public void run() { { Result result = callAction( controllers.routes.ref.FileStoreController.index()); assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)).isEqualTo("/login"); } { Result result = callAction( controllers.routes.ref.Application.login()); assertThat(status(result)).isEqualTo(OK); assertThat(header("Cache-Control", result)).isEqualTo("max-age=0, must-revalidate"); String pageContent = contentAsString(result); assertThat(pageContent).contains("name=\"email\""); assertThat(pageContent).contains("name=\"password\""); } } }); } @Test public void loginDetectsInvalidEmailAddress() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final Map<String,String> data = new HashMap<String,String>(); data.put("email", "notanemailaddress"); data.put("password", "password"); Result result; { final FakeRequest request = fakeRequest().withFormUrlEncodedBody(data); result = callAction( controllers.routes.ref.Application.postLogin(), request); } assertThat(status(result)).isEqualTo(400); String pageContent = contentAsString(result); assertThat(pageContent).contains("name=\"email\""); assertThat(pageContent).contains("name=\"password\""); } }); } @Test public void missingUserPopulatesFlashMessage() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final Map<String,String> data = new HashMap<String,String>(); data.put("email", "user@domain.com"); data.put("password", "password"); Result result; { final FakeRequest request = fakeRequest().withFormUrlEncodedBody(data); result = callAction( controllers.routes.ref.Application.postLogin(), request); } assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)).isEqualTo("/login"); assertThat(flash(result).get("error")).contains("email address or password"); } }); } @Test public void incorrectPasswordPopulatesFlashMessage() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final String password = "password"; final User user = createNewUser("user@domain.com", "differentpassword"); final Map<String,String> data = new HashMap<String,String>(); data.put("email", user.getEmail()); data.put("password", password); Result result; { final FakeRequest request = fakeRequest().withFormUrlEncodedBody(data); result = callAction( controllers.routes.ref.Application.postLogin(), request); } assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)).isEqualTo("/login"); assertThat(flash(result).get("error")).contains("email address or password"); } }); } @Test public void unvalidatedUserCannotLogin() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final String password = "password"; final String email = "user@domain.com"; createNewUser(email, password); final User user = getSessionFactory().inSession( new F.Function<Session, User>() { @Override public User apply(Session session) throws RepositoryException { final UserDAO dao = getUserDAO(session); final User user = dao.findByEmail(email); user.setVerified(false); getUserDAO(session).update(user); return user; }; }); assertThat(user.isVerified()).isFalse(); final Map<String,String> data = new HashMap<String,String>(); data.put("email", user.getEmail()); data.put("password", password); Result result; { final FakeRequest request = fakeRequest().withFormUrlEncodedBody(data); result = callAction( controllers.routes.ref.Application.postLogin(), request); } assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)) .isEqualTo("/user-unverified/" + user.getEmail()); } }); } @Test public void validatedUserCanLogin() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final String password = "password"; final User user = createNewUser("user@domain.com", password); final Map<String,String> data = new HashMap<String,String>(); data.put("email", user.getEmail()); data.put("password", password); Result result; { final FakeRequest request = fakeRequest().withFormUrlEncodedBody(data); result = callAction( controllers.routes.ref.Application.postLogin(), request); } assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)).isEqualTo("/"); { final FakeRequest request = fakeRequest(); for (final Map.Entry<String, String> e : session(result).entrySet()) { request.withSession(e.getKey(), e.getValue()); } result = callAction( controllers.routes.ref.FileStoreController.index(), request); } assertThat(status(result)).isEqualTo(200); String pageContent = contentAsString(result); assertThat(pageContent).contains(user.getName()); assertThat(pageContent).contains(user.getEmail()); assertThat(pageContent).doesNotContain("name=\"password\""); } }); } @Test public void changePassword() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) { final UserDAO dao = new UserDAO(session, jcrom()); { final Map<String,String> data = ImmutableMap.<String, String>builder() .put("newPassword", "passphrase") .build(); final Result result = callAction( controllers.routes.ref.Application.changePassword(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(400); assertThat(dao.checkPassword(user, "passphrase")).isFalse(); } { final Map<String,String> data = ImmutableMap.<String, String>builder() .put("currentPassword", "notthepassword") .put("newPassword", "passphrase") .build(); final Result result = callAction( controllers.routes.ref.Application.changePassword(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(400); assertThat(dao.checkPassword(user, "passphrase")).isFalse(); } { final Map<String,String> data = ImmutableMap.<String, String>builder() .put("currentPassword", "password") .put("newPassword", "passphrase") .build(); final Result result = callAction( controllers.routes.ref.Application.changePassword(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(200); assertThat(dao.checkPassword(user, "passphrase")).isTrue(); } return session; } }); } @Test public void userCanInviteOthers() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final String inviteUserEmail = "inviteduser@example.test"; final String inviteUserName = "Invited User"; { final Result result = callAction( controllers.routes.ref.Application.invite(), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/html"); assertThat(charset(result)).isEqualTo("utf-8"); final String pageContent = contentAsString(result); assertThat(pageContent).contains("name=\"email\""); assertThat(pageContent).contains("name=\"name\""); assertThat(pageContent).contains("name=\"groups[]\""); } { final Map<String,String> data = new HashMap<String,String>(); data.put("email", inviteUserEmail); data.put("name", inviteUserName); data.put("groups[]", "testgroup"); final Result result = callAction( controllers.routes.ref.Application.postInvite(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)) .isEqualTo("/user-unverified/" + inviteUserEmail); } // Check the user was created properly final UserDAO dao = new UserDAO(session, jcrom()); final User invitedUser = dao.findByEmail(inviteUserEmail); assertThat(invitedUser).isNotNull(); assertThat(invitedUser.getName()).isEqualTo(inviteUserName); assertThat(invitedUser.isVerified()).isFalse(); final Set<Group> groups = (new GroupManager(session)).memberships( invitedUser.getJackrabbitUserId()); assertThat(groups).hasSize(1); for (final Group g : groups) { assertThat(g.getPrincipal().getName()).isEqualTo("testgroup"); } // Get the unverified page referred to { final Map<String,String> data = new HashMap<String,String>(); data.put("email", inviteUserEmail); data.put("name", inviteUserName); data.put("groups[]", "testgroup"); final Result result = callAction( controllers.routes.ref.Application.userUnverified(inviteUserEmail), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/html"); assertThat(charset(result)).isEqualTo("utf-8"); } return session; } }); } @Test public void invitesMustProvideName() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final Map<String,String> data = new HashMap<String,String>(); data.put("email", "inviteduser@example.test"); data.put("name", ""); final Result result = callAction( controllers.routes.ref.Application.postInvite(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(400); return session; } }); } @Test public void passwordReset() { running(fakeAorraApp(), new Runnable() { @Override public void run() { final User user = createNewUser("user@domain.com", "unknownpassword"); // Clear verification token getSessionFactory().inSession(new F.Function<Session, Session>() { @Override public Session apply(Session session) throws Throwable { final UserDAO dao = getUserDAO(session); final User u = dao.findByEmail(user.getEmail()); u.clearVerificationToken(); dao.update(u); return session; } }); assertThat(getToken(user.getEmail())).isNull(); { final Result result = callAction( controllers.routes.ref.Application.login(), fakeRequest()); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/html"); assertThat(charset(result)).isEqualTo("utf-8"); final Document doc = Jsoup.parse(contentAsString(result)); final Call resetRoute = controllers.routes.Application.forgottenPassword(); assertThat(doc.select("a[href="+resetRoute.url()+"]")) .as("forgotten password link").hasSize(1); } { final Result result = callAction( controllers.routes.ref.Application.forgottenPassword(), fakeRequest()); assertThat(status(result)).isEqualTo(200); final Document doc = Jsoup.parse(contentAsString(result)); assertThat(doc.select("input[name=email]")) .as("has email field").hasSize(1); assertThat(doc.select("input[name=email]").first().attr("type")) .isEqualTo("email"); assertThat(doc.select("button[type=submit]")) .as("has submit button").hasSize(1); final Call submitRoute = controllers.routes.Application.postForgottenPassword(); assertThat(doc.select("form").first().attr("action")) .isEqualTo(submitRoute.url()); assertThat(doc.select("form").first().attr("method")) .isEqualTo(submitRoute.method()); } { assertThat(getToken(user.getEmail())).isNull(); final Map<String,String> data = new HashMap<String,String>(); data.put("email", user.getEmail()); final Result result = callAction( controllers.routes.ref.Application.postForgottenPassword(), fakeRequest().withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)) .isEqualTo(controllers.routes.Application.login().url()); assertThat(flash(result).containsKey("info")).isTrue(); assertThat(getToken(user.getEmail())).isNotNull(); } { final Result result = callAction( controllers.routes.ref.Application.verify(user.getEmail(), getToken(user.getEmail())), fakeRequest()); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/html"); assertThat(charset(result)).isEqualTo("utf-8"); final Document doc = Jsoup.parse(contentAsString(result)); assertThat(doc.select("input[name=password]")) .as("has password field").hasSize(1); assertThat(doc.select("input[name=repeatPassword]")) .as("has repeat password field").hasSize(1); } final String token; { assertThat(checkPwd(user.getEmail(), "newpass")).isFalse(); final Map<String,String> data = new HashMap<String,String>(); data.put("password", "newpassword"); data.put("repeatPassword", "newpassword"); token = getToken(user.getEmail()); final Result result = callAction( controllers.routes.ref.Application.postVerify(user.getEmail(), token), fakeRequest().withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(303); assertThat(checkPwd(user.getEmail(), "newpassword")) .as("password has been reset").isTrue(); assertThat(getToken(user.getEmail())) .as("verification token has been cleared").isNull(); } // Check we cannot reuse the link { final Result result = callAction( controllers.routes.ref.Application.verify(user.getEmail(), token), fakeRequest()); assertThat(status(result)).isEqualTo(403); } { final Map<String,String> data = new HashMap<String,String>(); data.put("password", "otherpass"); data.put("repeatPassword", "otherpass"); final Result result = callAction( controllers.routes.ref.Application.postVerify(user.getEmail(), token), fakeRequest().withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(403); assertThat(checkPwd(user.getEmail(), "otherpass")) .as("password was not reset").isFalse(); } } protected boolean checkPwd(final String email, final String password) { return getSessionFactory().inSession(new F.Function<Session, Boolean>(){ @Override public Boolean apply(Session session) throws Throwable { final UserDAO dao = getUserDAO(session); final User u = dao.findByEmail(email); dao.update(u); return dao.checkPassword(u, password); } }); } protected String getToken(final String email) { return getSessionFactory().inSession(new F.Function<Session, String>() { @Override public String apply(Session session) throws Throwable { final UserDAO dao = getUserDAO(session); final User u = dao.findByEmail(email); dao.update(u); return extractToken(u); } public String extractToken(User user) { try { Field vtField = user.getClass() .getDeclaredField("verificationToken"); vtField.setAccessible(true); return (String) vtField.get(user); } catch (Exception e) { throw new RuntimeException(e); } } }); } }); } @Test public void userCannotInviteThemselves() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) { { final Map<String,String> data = new HashMap<String,String>(); data.put("email", user.getEmail()); data.put("name", user.getName()); final Result result = callAction( controllers.routes.ref.Application.postInvite(), newRequest.withFormUrlEncodedBody(data)); assertThat(status(result)).isEqualTo(303); assertThat(header("Location", result)) .isEqualTo("/user-exists/" + user.getEmail()); } { final Result result = callAction( controllers.routes.ref.Application.userExists(user.getEmail()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/html"); assertThat(charset(result)).isEqualTo("utf-8"); } return session; } }); } private User createNewUser(final String email, final String password) { final String name = "Test User"; final User.Invite invite = new User.Invite(email, name); final JackrabbitEmailPasswordAuthProvider authProvider = Play.application().plugin(JackrabbitEmailPasswordAuthProvider.class); authProvider.signup(invite); User user = getSessionFactory().inSession(new F.Function<Session, User>() { @Override public User apply(Session session) throws Throwable { final UserDAO dao = getUserDAO(session); final User user = dao.findByEmail(email); user.setVerified(true); dao.update(user); dao.setPassword(user, password); return user; } }); return user; } private JcrSessionFactory getSessionFactory() { return GuiceInjectionPlugin.getInjector(Play.application()) .getInstance(JcrSessionFactory.class); } private UserDAO getUserDAO(Session session) { final Jcrom jcrom = GuiceInjectionPlugin.getInjector(Play.application()) .getInstance(Jcrom.class); return new UserDAO(session, jcrom); } }