Skip to content
Snippets Groups Projects
Commit b7ddd7a9 authored by Will Billingsley's avatar Will Billingsley
Browse files

Added a solution, not polished

parent b5a53be3
Branches solution
No related tags found
No related merge requests found
...@@ -3,6 +3,7 @@ package controllers; ...@@ -3,6 +3,7 @@ package controllers;
import actors.Setup; import actors.Setup;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import model.User;
import play.mvc.Controller; import play.mvc.Controller;
import play.mvc.Result; import play.mvc.Result;
import scala.compat.java8.FutureConverters; import scala.compat.java8.FutureConverters;
...@@ -18,26 +19,23 @@ public class Application extends Controller { ...@@ -18,26 +19,23 @@ public class Application extends Controller {
WSClient wsClient; WSClient wsClient;
UserController userController;
@Inject @Inject
public Application(Setup actorSetup, WSClient wsClient) { public Application(Setup actorSetup, WSClient wsClient, UserController userController) {
this.actorSetup = actorSetup; this.actorSetup = actorSetup;
this.wsClient = wsClient; this.wsClient = wsClient;
this.userController = userController;
} }
/** /**
* Play framework suppors asynchronous controller methods -- that is, methods that instead of returning a Result * Play framework suppors asynchronous controller methods -- that is, methods that instead of returning a Result
* return a CompletionStage, which will produce a Result some time in the future * return a CompletionStage, which will produce a Result some time in the future
*/ */
public CompletionStage<Result> index() { public Result index() {
/* User u = userController.getLoggedInUser();
* This code might look a little complex.
* return ok(views.html.application.index.render(u));
* ask sends a message to an ActorRef, and then returns a Future that will eventually contain the response.
* But Future is a Scala class, so FutureConverters.toJava converts it into a CompletionStage, which is Java's equivalent.
* thenApply is a method on CompletionStage that means "when you get the result, do this with it, and return a new CompletionStage"
*/
return FutureConverters.toJava(ask(actorSetup.marshallActor, "Report!", 1000))
.thenApply(response -> ok(response.toString()));
} }
/** /**
......
package controllers;
import com.google.inject.Singleton;
import model.Session;
import model.SessionService;
import model.User;
import model.UserService;
import org.mindrot.jbcrypt.BCrypt;
import play.mvc.Controller;
import play.mvc.Result;
import java.util.UUID;
/**
* Controller for logging in, registering, logging out, etc.
*/
@Singleton
public class UserController extends Controller {
public final static String sessionVar = "MY_SESSION";
protected UserService getUserService() {
return UserService.instance;
}
protected SessionService getSessionService() {
return SessionService.instance;
}
protected String getSessionId() {
String id = session(sessionVar);
/*
* Because we're not persisting the sessions in a database, it's possible the
* browser already has a session ID that no longer corresponds to one we remember.
*
* So we also check for whether the session exists in the "database"
*/
if (id == null || getSessionService().get(id) == null) {
Session s = new Session(request().remoteAddress());
SessionService.instance.put(s);
id = s.getId();
session(sessionVar, id);
}
return id;
}
public User getLoggedInUser() {
return getSessionService().getUserFromSession(getSessionId());
}
public Result loginForm() {
return ok(views.html.application.login.render(getLoggedInUser(), null));
}
public Result registerForm() {
return ok(views.html.application.register.render(
getLoggedInUser(),
null
));
}
public Result doLogin() {
String email = request().body().asFormUrlEncoded().get("email")[0];
String password = request().body().asFormUrlEncoded().get("password")[0];
String sessionId = getSessionId();
User u = getUserService().getUser(email, password);
if (u != null) {
SessionService.instance.setUserIdForSession(sessionId, u.getId());
return redirect("/mysessions");
} else {
return ok(views.html.application.login.render(
getLoggedInUser(),
"Email address or password was incorrect")
);
}
}
public Result doRegister() {
String email = request().body().asFormUrlEncoded().get("email")[0];
String password = request().body().asFormUrlEncoded().get("password")[0];
String id = UUID.randomUUID().toString();
User u = new User(id , email, BCrypt.hashpw(password, BCrypt.gensalt()));
try {
getUserService().registerUser(u);
} catch (IllegalStateException ex) {
return ok(views.html.application.register.render(
getLoggedInUser(),
ex.getMessage())
);
}
return redirect("/login");
}
public Result mySessions() {
String sessionId = getSessionId();
User loggedInUser = SessionService.instance.getUserFromSession(sessionId);
Session[] sessions = SessionService.instance.getSessionsForUser(loggedInUser);
return ok(views.html.application.mysessions.render(getLoggedInUser(), sessions));
}
public Result logoutSession() {
String sessionId = request().body().asFormUrlEncoded().get("sessionId")[0];
getSessionService().logout(sessionId);
return redirect("/mysessions");
}
}
package model;
import java.util.Date;
/**
* Reprepresents a user's logged in session in our application
*/
public class Session {
String id;
String ipAddress;
long since;
public Session(String ipAddress) {
this.id = java.util.UUID.randomUUID().toString();
this.since = System.currentTimeMillis();
this.ipAddress = ipAddress;
}
public String getIpAddress() {
return this.ipAddress;
}
public String getId() {
return id;
}
public long getSince() {
return this.since;
}
public String getSinceAsString() {
Date d = new Date(since);
return d.toString();
}
}
package model;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Because we don't have a database we store the sessions in memory
*/
public class SessionService {
public static final SessionService instance = new SessionService();
protected ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();
protected ConcurrentHashMap<String, String> sessionToUser = new ConcurrentHashMap<>();
public Session get(String id) {
return sessions.get(id);
}
public void put(Session s) {
sessions.put(s.getId(), s);
}
public String getUserIdForSession(String sessionId) {
return sessionToUser.get(sessionId);
}
/** Associates the session with a particular user */
public void setUserIdForSession(String sessionId, String userId) {
Session s = sessions.get(sessionId);
if (s == null) {
throw new IllegalStateException("The user had a session ID that was not in the database");
}
// Associate the session
sessionToUser.put(sessionId, userId);
}
public void logout(String sessionId) {
sessionToUser.remove(sessionId);
}
public User getUserFromSession(String sessionId) {
return UserService.instance.getUser(getUserIdForSession(sessionId));
}
public Session[] getSessionsForUser(User u) {
if (u == null) {
return new Session[0];
} else {
ArrayList<Session> foundSessions = new ArrayList<>();
for (Map.Entry<String, String> entry : sessionToUser.entrySet()) {
if (entry.getValue().equals(u.getId())) {
foundSessions.add(sessions.get(entry.getKey()));
}
}
return foundSessions.toArray(new Session[0]);
}
}
}
package model;
/**
* A simple model for a User
*/
public class User {
String id;
String email;
String hash;
public User(String id, String email, String hash) {
this.id = id;
this.email = email;
this.hash = hash;
}
public String getId() {
return this.id;
}
public String getEmail() {
return this.email;
}
public String getHash() {
return this.hash;
}
}
\ No newline at end of file
package model;
import org.mindrot.jbcrypt.BCrypt;
import java.util.concurrent.ConcurrentHashMap;
/**
* Stores all our users because we are not using a database in this tutorial
*/
public class UserService {
public static final UserService instance = new UserService();
/** Stand-in database */
ConcurrentHashMap<String, User> users = new ConcurrentHashMap<>();
/**
* Registers a new user
*/
public User registerUser(User u) {
users.values().forEach((user) -> {
if (user.getEmail().equals(u.getEmail())) {
throw new IllegalStateException("User already exists");
}
});
users.put(u.getId(), u);
return u;
}
/**
* Gets a user from their User ID.
*/
public User getUser(String id) {
if (id != null) {
return users.get(id);
} else return null;
}
/**
* Gets a user from the database if their email address and password matches
*/
public User getUser(String email, String password) {
for (User u : users.values()) {
if (u.getEmail().equals(email) && BCrypt.checkpw(password, u.getHash())) {
return u;
}
}
return null;
}
}
\ No newline at end of file
@(user:model.User)
<div class="container">
<a href="/">Example app</a>
<span class="pull-right">
@if(user != null){
@{user.getEmail()}
} else {
<a href="/login">log in</a>
}
<a href="/mysessions">My Sessions</a>
</span>
</div>
\ No newline at end of file
@()
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
@(indexes:Array[Int]) @(user:model.User)
<!DOCTYPE html> <!DOCTYPE html>
@{fragments.header(user)}
@{fragments.includeBootstrap()}
<html> <html>
<head lang="en"> <head lang="en">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
...@@ -10,22 +13,15 @@ ...@@ -10,22 +13,15 @@
</head> </head>
<body> <body>
<h1>Tick all the beagles:</h1> <div class="container">
<h2>Tutorial 5 example</h2>
<form action="matches" method="POST"> Pages:
@for(idx <- indexes) { <ul>
<div> <li><a href="/login">log in</a></li>
<input name="sent" value="@idx" type="hidden" /> <li><a href="/register">register</a></li>
<input name="beagle" value="@idx" type="checkbox" /> <li><a href="/mysessions">list your sessions (and log them out)</a></li>
<img src="@model.Captcha.getPhoto(idx)" style="max-width: 400px; max-height: 200px;" /> </ul>
</div> </div>
}
<!--
TODO: For each index in the array, show a checkbox with the value of the index, and the relevant img
-->
<button type="submit">Submit</button>
</form>
</body> </body>
</html> </html>
\ No newline at end of file
@(user:model.User, error:String)
<!DOCTYPE html>
<html>
@{fragments.header(user)}
@{fragments.includeBootstrap()}
<div class="container">
<h2>Log in</h2>
<form action="/login" method="POST">
<div class="form-group">
<label>Email address</label>
<input class="form-control" type="email" name="email" />
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" name="password" />
</div>
@if(error != null) {
<div class="alert alert-danger">@error</div>
}
<button class="btn btn-primary" type="submit">Log In</button>
</form>
<div>
or <a href="/register">register a new account</a>.
</div>
</div>
</html>
@(user:model.User, sessions:Array[model.Session])
@{fragments.header(user)}
@{fragments.includeBootstrap()}
<!DOCTYPE html>
<html>
<div class="container">
<h2>My sessions</h2>
<ul>
@for(session <- sessions){
<li>
@session.getIpAddress() since @session.getSinceAsString()
<form action="/logoutSession" method="POST">
<input type="hidden" name="sessionId" value="@{session.getId()}" />
<button class="btn btn-link" type="submit">logout</button>
</form>
</li>
}
</ul>
</div>
</html>
@(user:model.User, error:String)
<!DOCTYPE html>
<html>
@{fragments.header(user)}
@{fragments.includeBootstrap()}
<div class="container">
<h2>Register</h2>
<form action="/register" method="POST">
<div class="form-group">
<label>Email address</label>
<input class="form-control" type="email" name="email" />
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" name="password" />
</div>
@if(error != null) {
<div class="alert alert-danger">@error</div>
}
<button class="btn btn-primary" type="submit">Register</button>
</form>
</div>
</html>
...@@ -5,5 +5,12 @@ ...@@ -5,5 +5,12 @@
GET / controllers.Application.index() GET / controllers.Application.index()
GET /ws controllers.Application.whatDidGitLabSay() GET /ws controllers.Application.whatDidGitLabSay()
GET /login controllers.UserController.loginForm()
GET /register controllers.UserController.registerForm()
GET /mysessions controllers.UserController.mySessions()
POST /login controllers.UserController.doLogin()
POST /register controllers.UserController.doRegister()
POST /logoutSession controllers.UserController.logoutSession()
# Map static resources from the /public folder to the /assets URL path # Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment