summaryrefslogtreecommitdiff
path: root/src/com/p4square/grow/frontend
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2013-08-27 08:28:16 -0700
committerJesse Morgan <jesse@jesterpm.net>2013-08-27 08:28:16 -0700
commit1cdb43bb3e432040aed18c05e129f0131ee7d20a (patch)
treea4c5ad41d183b3874c990de0c5416d1810a1dc85 /src/com/p4square/grow/frontend
parent9b33aaf27cd8f73402ee9967c6b0fd76a90f8ebe (diff)
Introducing F1 Authentication and Adding Site Content.
This change introduced the f1oauth and jesterpm oauth packages for interacting with Fellowship One's developer API. I have also reworked the login authentication to verify credentials through F1 and added session management to track logged in users. The Authenticator chain works as follows: on every page load we check for a session cookie, if the cookie exists, the Request is marked as authenticated and the OAuthUser object is restored in ClientInfo. If this request is going to an account page, we require authentication. The LoginFormAuthenticator checks if the user is already authenticated (via cookie) and if not redirects the user to the login page. When the login form is submitted, LoginFormAuthenticator catches the POST request and authenticates the user through F1. I'm also adding a new account page, but it is currently a work in progress. This commit also adds Allen's content to the site.
Diffstat (limited to 'src/com/p4square/grow/frontend')
-rw-r--r--src/com/p4square/grow/frontend/ErrorPage.java25
-rw-r--r--src/com/p4square/grow/frontend/GrowFrontend.java71
-rw-r--r--src/com/p4square/grow/frontend/LoginFormAuthenticator.java122
-rw-r--r--src/com/p4square/grow/frontend/LoginPageResource.java40
-rw-r--r--src/com/p4square/grow/frontend/NewAccountResource.java115
-rw-r--r--src/com/p4square/grow/frontend/SurveyPageResource.java6
-rw-r--r--src/com/p4square/grow/frontend/TrainingPageResource.java6
-rw-r--r--src/com/p4square/grow/frontend/VideosResource.java4
-rw-r--r--src/com/p4square/grow/frontend/session/Session.java55
-rw-r--r--src/com/p4square/grow/frontend/session/SessionAuthenticator.java (renamed from src/com/p4square/grow/frontend/LoginAuthenticator.java)28
-rw-r--r--src/com/p4square/grow/frontend/session/SessionCheckingAuthenticator.java38
-rw-r--r--src/com/p4square/grow/frontend/session/SessionCookieAuthenticator.java59
-rw-r--r--src/com/p4square/grow/frontend/session/SessionCreatingAuthenticator.java45
-rw-r--r--src/com/p4square/grow/frontend/session/Sessions.java80
14 files changed, 618 insertions, 76 deletions
diff --git a/src/com/p4square/grow/frontend/ErrorPage.java b/src/com/p4square/grow/frontend/ErrorPage.java
new file mode 100644
index 0000000..4a9380f
--- /dev/null
+++ b/src/com/p4square/grow/frontend/ErrorPage.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend;
+
+import org.restlet.representation.StringRepresentation;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class ErrorPage extends StringRepresentation {
+ public static final ErrorPage TEMPLATE_NOT_FOUND = new ErrorPage();
+ public static final ErrorPage RENDER_ERROR = new ErrorPage();
+
+
+ public ErrorPage() {
+ super("TODO");
+ }
+
+ public ErrorPage(String s) {
+ super(s);
+ }
+}
diff --git a/src/com/p4square/grow/frontend/GrowFrontend.java b/src/com/p4square/grow/frontend/GrowFrontend.java
index 5c49fe2..36e7544 100644
--- a/src/com/p4square/grow/frontend/GrowFrontend.java
+++ b/src/com/p4square/grow/frontend/GrowFrontend.java
@@ -7,20 +7,32 @@ package com.p4square.grow.frontend;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.UUID;
+
import org.restlet.Application;
import org.restlet.Component;
+import org.restlet.Client;
+import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.data.Protocol;
import org.restlet.resource.Directory;
import org.restlet.routing.Router;
+import org.restlet.security.Authenticator;
import org.apache.log4j.Logger;
-import net.jesterpm.fmfacade.FMFacade;
-import net.jesterpm.fmfacade.FreeMarkerPageResource;
+import com.p4square.fmfacade.FMFacade;
+import com.p4square.fmfacade.FreeMarkerPageResource;
import com.p4square.grow.config.Config;
+import com.p4square.f1oauth.F1OAuthHelper;
+import com.p4square.f1oauth.SecondPartyVerifier;
+
+import com.p4square.grow.frontend.session.SessionCheckingAuthenticator;
+import com.p4square.grow.frontend.session.SessionCreatingAuthenticator;
+
/**
* This is the Restlet Application implementing the Grow project front-end.
* It's implemented as an extension of FMFacade that connects interactive pages
@@ -34,6 +46,8 @@ public class GrowFrontend extends FMFacade {
private Config mConfig;
+ private F1OAuthHelper mHelper;
+
public GrowFrontend() {
mConfig = new Config();
}
@@ -62,14 +76,23 @@ public class GrowFrontend extends FMFacade {
}
}
+ F1OAuthHelper getHelper() {
+ if (mHelper == null) {
+ mHelper = new F1OAuthHelper(getContext(), mConfig.getString("f1ConsumerKey", ""),
+ mConfig.getString("f1ConsumerSecret", ""),
+ mConfig.getString("f1BaseUrl", "staging.fellowshiponeapi.com"),
+ mConfig.getString("f1ChurchCode", "pfseawa"),
+ F1OAuthHelper.UserType.WEBLINK);
+ }
+
+ return mHelper;
+ }
+
@Override
protected Router createRouter() {
Router router = new Router(getContext());
- final String loginPage = getConfig().getString("dynamicRoot", "") + "/login.html";
-
- final LoginAuthenticator defaultGuard =
- new LoginAuthenticator(getContext(), true, loginPage);
+ final Authenticator defaultGuard = new SessionCheckingAuthenticator(getContext(), true);
defaultGuard.setNext(FreeMarkerPageResource.class);
router.attachDefault(defaultGuard);
router.attach("/login.html", LoginPageResource.class);
@@ -81,14 +104,36 @@ public class GrowFrontend extends FMFacade {
accountRouter.attach("/training/{chapter}", TrainingPageResource.class);
accountRouter.attach("/training", TrainingPageResource.class);
- final LoginAuthenticator accountGuard =
- new LoginAuthenticator(getContext(), false, loginPage);
- accountGuard.setNext(accountRouter);
+ final Authenticator accountGuard = createAuthenticatorChain(accountRouter);
router.attach("/account", accountGuard);
return router;
}
+ private Authenticator createAuthenticatorChain(Restlet last) {
+ final Context context = getContext();
+ final String loginPage = getConfig().getString("dynamicRoot", "") + "/login.html";
+
+ // This is used to check for an existing session
+ SessionCheckingAuthenticator sessionChk = new SessionCheckingAuthenticator(context, true);
+
+ // This is used to authenticate the user
+ SecondPartyVerifier f1Verifier = new SecondPartyVerifier(getHelper());
+ LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, f1Verifier);
+ loginAuth.setLoginFormUrl(loginPage);
+ loginAuth.setLoginPostUrl("/account/authenticate");
+
+ // This is used to create a new session for a newly authenticated user.
+ SessionCreatingAuthenticator sessionCreate = new SessionCreatingAuthenticator(context);
+
+ sessionChk.setNext(loginAuth);
+ loginAuth.setNext(sessionCreate);
+
+ sessionCreate.setNext(last);
+
+ return sessionChk;
+ }
+
/**
* Stand-alone main for testing.
*/
@@ -98,14 +143,16 @@ public class GrowFrontend extends FMFacade {
component.getServers().add(Protocol.HTTP, 8085);
component.getClients().add(Protocol.HTTP);
component.getClients().add(Protocol.FILE);
-
+ component.getClients().add(new Client(null, Arrays.asList(Protocol.HTTPS), "org.restlet.ext.httpclient.HttpClientHelper"));
+
// Static content
try {
component.getDefaultHost().attach("/images/", new FileServingApp("./build/images/"));
component.getDefaultHost().attach("/scripts", new FileServingApp("./build/scripts"));
component.getDefaultHost().attach("/style.css", new FileServingApp("./build/style.css"));
+ component.getDefaultHost().attach("/favicon.ico", new FileServingApp("./build/favicon.ico"));
} catch (IOException e) {
- cLog.error("Could not create directory for static resources: "
+ cLog.error("Could not create directory for static resources: "
+ e.getMessage(), e);
}
@@ -139,7 +186,7 @@ public class GrowFrontend extends FMFacade {
cLog.fatal("Could not start: " + e.getMessage(), e);
}
}
-
+
private static class FileServingApp extends Application {
private final String mPath;
diff --git a/src/com/p4square/grow/frontend/LoginFormAuthenticator.java b/src/com/p4square/grow/frontend/LoginFormAuthenticator.java
new file mode 100644
index 0000000..d5a3c22
--- /dev/null
+++ b/src/com/p4square/grow/frontend/LoginFormAuthenticator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Form;
+import org.restlet.data.Reference;
+import org.restlet.security.Authenticator;
+import org.restlet.security.Verifier;
+
+/**
+ * LoginFormAuthenticator changes
+ *
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class LoginFormAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(LoginFormAuthenticator.class);
+
+ private final Verifier mVerifier;
+
+ private String mLoginPage = "/login.html";
+ private String mLoginPostUrl = "/authenticate";
+ private String mDefaultRedirect = "/index.html";
+
+ public LoginFormAuthenticator(Context context, boolean optional, Verifier verifier) {
+ super(context, false, optional, null);
+
+ mVerifier = verifier;
+ }
+
+ public void setLoginFormUrl(String url) {
+ mLoginPage = url;
+ }
+
+ public void setLoginPostUrl(String url) {
+ mLoginPostUrl = url;
+ }
+
+ @Override
+ protected int beforeHandle(Request request, Response response) {
+ if (request.getClientInfo().isAuthenticated()) {
+ // TODO: Logout
+ LOG.debug("Already authenticated. Skipping");
+ return CONTINUE;
+
+ } else {
+ return super.beforeHandle(request, response);
+ }
+ }
+
+
+ @Override
+ protected boolean authenticate(Request request, Response response) {
+ String requestPath = request.getResourceRef().getPath();
+ boolean isLoginAttempt = mLoginPostUrl.equals(requestPath);
+
+ Form query = request.getOriginalRef().getQueryAsForm();
+ String redirect = query.getFirstValue("redirect");
+ if (redirect == null) {
+ if (isLoginAttempt) {
+ redirect = mDefaultRedirect;
+ } else {
+ redirect = request.getResourceRef().getRelativePart();
+ }
+ }
+
+ boolean authenticationFailed = false;
+
+ if (isLoginAttempt) {
+ LOG.debug("Attempting authentication");
+
+ // Process login form
+ final Form form = new Form(request.getEntity());
+ final String email = form.getFirstValue("email");
+ final String password = form.getFirstValue("password");
+
+ boolean authenticated = false;
+
+ if (email != null && !"".equals(email) &&
+ password != null && !"".equals(password)) {
+
+ LOG.debug("Got login request from " + email);
+
+ request.setChallengeResponse(
+ new ChallengeResponse(ChallengeScheme.HTTP_BASIC, email, password.toCharArray()));
+
+ // We expect the verifier to setup the User object.
+ int result = mVerifier.verify(request, response);
+ if (result == Verifier.RESULT_VALID) {
+ // TODO: Ensure redirect is a relative url.
+ response.redirectSeeOther(redirect);
+ return true;
+ }
+ }
+
+ authenticationFailed = true;
+ }
+
+ if (!isOptional() || authenticationFailed) {
+ Reference ref = new Reference(mLoginPage);
+ ref.addQueryParameter("redirect", redirect);
+
+ if (authenticationFailed) {
+ ref.addQueryParameter("retry", "t");
+ }
+
+ LOG.debug("Redirecting to " + ref.toString());
+ response.redirectSeeOther(ref.toString());
+ }
+ LOG.debug("Failing authentication.");
+ return false;
+ }
+}
diff --git a/src/com/p4square/grow/frontend/LoginPageResource.java b/src/com/p4square/grow/frontend/LoginPageResource.java
index 70caa3e..e645c1b 100644
--- a/src/com/p4square/grow/frontend/LoginPageResource.java
+++ b/src/com/p4square/grow/frontend/LoginPageResource.java
@@ -17,7 +17,7 @@ import org.restlet.ext.freemarker.TemplateRepresentation;
import org.apache.log4j.Logger;
-import net.jesterpm.fmfacade.FreeMarkerPageResource;
+import com.p4square.fmfacade.FreeMarkerPageResource;
/**
* LoginPageResource presents a login page template and processes the response.
@@ -57,7 +57,11 @@ public class LoginPageResource extends FreeMarkerPageResource {
Map<String, Object> root = getRootObject();
- root.put("errorMessage", mErrorMessage);
+ Form query = getRequest().getOriginalRef().getQueryAsForm();
+ String retry = query.getFirstValue("retry");
+ if ("t".equals("retry")) {
+ root.put("errorMessage", "Invalid email or password.");
+ }
return new TemplateRepresentation(t, root, MediaType.TEXT_HTML);
@@ -68,36 +72,4 @@ public class LoginPageResource extends FreeMarkerPageResource {
}
}
- /**
- * Process login and authenticate the user.
- */
- @Override
- protected Representation post(Representation entity) {
- final Form form = new Form(entity);
- final String email = form.getFirstValue("email");
- final String password = form.getFirstValue("password");
-
- boolean authenticated = false;
-
- // TODO: Do something real here
- if (email != null && !"".equals(email)) {
- cLog.debug("Got login request from " + email);
-
- // TODO: Encrypt user info
- getResponse().getCookieSettings().add(LoginAuthenticator.COOKIE_NAME, email);
-
- authenticated = true;
- }
-
- if (authenticated) {
- // TODO: Better return url.
- getResponse().redirectSeeOther(mGrowFrontend.getConfig().getString("dynamicRoot", "") + "/index.html");
- return null;
-
- } else {
- // Send them back to the login page...
- mErrorMessage = "Incorrect Email or Password.";
- return get();
- }
- }
}
diff --git a/src/com/p4square/grow/frontend/NewAccountResource.java b/src/com/p4square/grow/frontend/NewAccountResource.java
new file mode 100644
index 0000000..b72680a
--- /dev/null
+++ b/src/com/p4square/grow/frontend/NewAccountResource.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend;
+
+import java.util.Map;
+
+import freemarker.template.Template;
+
+import org.restlet.data.Form;
+import org.restlet.data.MediaType;
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.ext.freemarker.TemplateRepresentation;
+
+import org.apache.log4j.Logger;
+
+import com.p4square.f1oauth.F1OAuthHelper;
+import com.p4square.restlet.oauth.OAuthException;
+
+import com.p4square.fmfacade.FreeMarkerPageResource;
+
+/**
+ * This resource creates a new InFellowship account.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class NewAccountResource extends FreeMarkerPageResource {
+ private static Logger LOG = Logger.getLogger(NewAccountResource.class);
+
+ private GrowFrontend mGrowFrontend;
+ private F1OAuthHelper mHelper;
+
+ private String mErrorMessage;
+
+ private String mLoginPageUrl;
+ private String mVerificationPage;
+
+ @Override
+ public void doInit() {
+ super.doInit();
+
+ mGrowFrontend = (GrowFrontend) getApplication();
+ mHelper = mGrowFrontend.getHelper();
+
+ mErrorMessage = null;
+
+ mLoginPageUrl = "";
+ mVerificationPage = "";
+ }
+
+ /**
+ * Return the login page.
+ */
+ @Override
+ protected Representation get() {
+ Template t = mGrowFrontend.getTemplate("pages/newaccount.html.ftl");
+
+ try {
+ if (t == null) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return ErrorPage.TEMPLATE_NOT_FOUND;
+ }
+
+ Map<String, Object> root = getRootObject();
+ root.put("errorMessage", mErrorMessage);
+
+ return new TemplateRepresentation(t, root, MediaType.TEXT_HTML);
+
+ } catch (Exception e) {
+ LOG.fatal("Could not render page: " + e.getMessage(), e);
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return ErrorPage.RENDER_ERROR;
+ }
+ }
+
+ @Override
+ protected Representation post(Representation rep) {
+ Form form = new Form(rep);
+
+ String firstname = form.getFirstValue("firstname");
+ String lastname = form.getFirstValue("lastname");
+ String email = form.getFirstValue("email");
+
+ if (isEmpty(firstname)) {
+ mErrorMessage += "First Name is a required field. ";
+ }
+ if (isEmpty(lastname)) {
+ mErrorMessage += "Last Name is a required field. ";
+ }
+ if (isEmpty(email)) {
+ mErrorMessage += "Email is a required field. ";
+ }
+
+ if (mErrorMessage.length() > 0) {
+ return get();
+ }
+
+ try {
+ mHelper.createAccount(firstname, lastname, email, mLoginPageUrl);
+ getResponse().redirectSeeOther(mVerificationPage);
+ return new StringRepresentation("Redirecting to " + mVerificationPage);
+
+ } catch (OAuthException e) {
+ return new ErrorPage(e.getStatus().getDescription());
+ }
+ }
+
+ private boolean isEmpty(String s) {
+ return s != null && s.trim().length() > 0;
+ }
+}
diff --git a/src/com/p4square/grow/frontend/SurveyPageResource.java b/src/com/p4square/grow/frontend/SurveyPageResource.java
index 351eade..8a3b5a5 100644
--- a/src/com/p4square/grow/frontend/SurveyPageResource.java
+++ b/src/com/p4square/grow/frontend/SurveyPageResource.java
@@ -18,10 +18,10 @@ import org.restlet.resource.ServerResource;
import org.apache.log4j.Logger;
-import net.jesterpm.fmfacade.json.JsonRequestClient;
-import net.jesterpm.fmfacade.json.JsonResponse;
+import com.p4square.fmfacade.json.JsonRequestClient;
+import com.p4square.fmfacade.json.JsonResponse;
-import net.jesterpm.fmfacade.FreeMarkerPageResource;
+import com.p4square.fmfacade.FreeMarkerPageResource;
import com.p4square.grow.config.Config;
diff --git a/src/com/p4square/grow/frontend/TrainingPageResource.java b/src/com/p4square/grow/frontend/TrainingPageResource.java
index 6c89ac9..459eb9a 100644
--- a/src/com/p4square/grow/frontend/TrainingPageResource.java
+++ b/src/com/p4square/grow/frontend/TrainingPageResource.java
@@ -20,10 +20,10 @@ import org.restlet.resource.ServerResource;
import org.apache.log4j.Logger;
-import net.jesterpm.fmfacade.json.JsonRequestClient;
-import net.jesterpm.fmfacade.json.JsonResponse;
+import com.p4square.fmfacade.json.JsonRequestClient;
+import com.p4square.fmfacade.json.JsonResponse;
-import net.jesterpm.fmfacade.FreeMarkerPageResource;
+import com.p4square.fmfacade.FreeMarkerPageResource;
import com.p4square.grow.config.Config;
diff --git a/src/com/p4square/grow/frontend/VideosResource.java b/src/com/p4square/grow/frontend/VideosResource.java
index fed315b..cdb2fb4 100644
--- a/src/com/p4square/grow/frontend/VideosResource.java
+++ b/src/com/p4square/grow/frontend/VideosResource.java
@@ -17,8 +17,8 @@ import org.restlet.resource.ServerResource;
import org.apache.log4j.Logger;
-import net.jesterpm.fmfacade.json.JsonRequestClient;
-import net.jesterpm.fmfacade.json.JsonResponse;
+import com.p4square.fmfacade.json.JsonRequestClient;
+import com.p4square.fmfacade.json.JsonResponse;
import com.p4square.grow.config.Config;
diff --git a/src/com/p4square/grow/frontend/session/Session.java b/src/com/p4square/grow/frontend/session/Session.java
new file mode 100644
index 0000000..3a241ef
--- /dev/null
+++ b/src/com/p4square/grow/frontend/session/Session.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend.session;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.restlet.security.User;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class Session {
+ private static final long LIFETIME = 86400;
+
+ private final String mSessionId;
+ private final User mUser;
+ private final Map<String, String> mData;
+ private long mExpires;
+
+ Session(User user) {
+ mUser = user;
+ mSessionId = UUID.randomUUID().toString();
+ mExpires = System.currentTimeMillis() + LIFETIME;
+ mData = new HashMap<String, String>();
+ }
+
+ void touch() {
+ mExpires = System.currentTimeMillis() + LIFETIME;
+ }
+
+ boolean isExpired() {
+ return System.currentTimeMillis() > mExpires;
+ }
+
+ public String getId() {
+ return mSessionId;
+ }
+
+ public String get(String key) {
+ return mData.get(key);
+ }
+
+ public void put(String key, String value) {
+ mData.put(key, value);
+ }
+
+ public User getUser() {
+ return mUser;
+ }
+}
diff --git a/src/com/p4square/grow/frontend/LoginAuthenticator.java b/src/com/p4square/grow/frontend/session/SessionAuthenticator.java
index 64f5827..ac194af 100644
--- a/src/com/p4square/grow/frontend/LoginAuthenticator.java
+++ b/src/com/p4square/grow/frontend/session/SessionAuthenticator.java
@@ -2,9 +2,7 @@
* Copyright 2013 Jesse Morgan
*/
-package com.p4square.grow.frontend;
-
-import org.apache.log4j.Logger;
+package com.p4square.grow.frontend.session;
import org.restlet.Context;
import org.restlet.Request;
@@ -13,27 +11,12 @@ import org.restlet.security.Authenticator;
import org.restlet.security.User;
/**
- * LoginAuthenticator decrypts a cookie containing the user's session info
- * and makes that information available as the ClientInfo's User object.
- *
- * If this Authenticator is not optional, the user will be redirected to a
- * login page.
- *
+ *
* @author Jesse Morgan <jesse@jesterpm.net>
*/
-public class LoginAuthenticator extends Authenticator {
- private static Logger cLog = Logger.getLogger(LoginAuthenticator.class);
-
- public static final String COOKIE_NAME = "growsession";
-
- private final String mLoginPage;
-
- public LoginAuthenticator(Context context, boolean optional, String loginPage) {
- super(context, optional);
-
- mLoginPage = loginPage;
- }
-
+public class SessionAuthenticator /*extends Authenticator*/ {
+ /*
+ @Override
protected boolean authenticate(Request request, Response response) {
// Check for authentication cookie
final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
@@ -49,4 +32,5 @@ public class LoginAuthenticator extends Authenticator {
response.redirectSeeOther(mLoginPage);
return false;
}
+ */
}
diff --git a/src/com/p4square/grow/frontend/session/SessionCheckingAuthenticator.java b/src/com/p4square/grow/frontend/session/SessionCheckingAuthenticator.java
new file mode 100644
index 0000000..8382aff
--- /dev/null
+++ b/src/com/p4square/grow/frontend/session/SessionCheckingAuthenticator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+
+/**
+ * Authenticator which succeeds if a valid Session exists.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCheckingAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCheckingAuthenticator.class);
+
+ public SessionCheckingAuthenticator(Context context, boolean optional) {
+ super(context, optional);
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ Session s = Sessions.getInstance().get(request);
+
+ if (s != null) {
+ request.getClientInfo().setUser(s.getUser());
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/com/p4square/grow/frontend/session/SessionCookieAuthenticator.java b/src/com/p4square/grow/frontend/session/SessionCookieAuthenticator.java
new file mode 100644
index 0000000..789f58e
--- /dev/null
+++ b/src/com/p4square/grow/frontend/session/SessionCookieAuthenticator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCookieAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCookieAuthenticator.class);
+
+ private static final String COOKIE_NAME = "S";
+
+ private final Sessions mSessions;
+
+ public SessionCookieAuthenticator(Context context, boolean optional, Sessions sessions) {
+ super(context, optional);
+
+ mSessions = sessions;
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
+
+ if (request.getClientInfo().isAuthenticated()) {
+ // Request is already authenticated... create session if it doesn't exist.
+ if (cookie == null) {
+ Session s = mSessions.create(request.getClientInfo().getUser());
+ response.getCookieSettings().add(COOKIE_NAME, s.getId());
+ }
+
+ return true;
+
+ } else {
+ // Check for authentication cookie
+ if (cookie != null) {
+ LOG.debug("Got cookie: " + cookie);
+
+ Session s = mSessions.get(cookie);
+ if (s != null) {
+ request.getClientInfo().setUser(s.getUser());
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+}
diff --git a/src/com/p4square/grow/frontend/session/SessionCreatingAuthenticator.java b/src/com/p4square/grow/frontend/session/SessionCreatingAuthenticator.java
new file mode 100644
index 0000000..ce6024c
--- /dev/null
+++ b/src/com/p4square/grow/frontend/session/SessionCreatingAuthenticator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+import org.restlet.security.User;
+
+/**
+ * Authenticator which creates a Session for the request and adds a cookie
+ * to the response.
+ *
+ * The Request MUST be Authenticated and MUST have a User object associated.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCreatingAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCreatingAuthenticator.class);
+
+ public SessionCreatingAuthenticator(Context context) {
+ super(context, true);
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ if (Sessions.getInstance().get(request) != null) {
+ return true;
+ }
+
+ User user = request.getClientInfo().getUser();
+
+ if (request.getClientInfo().isAuthenticated() && user != null) {
+ Sessions.getInstance().create(request, response);
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/com/p4square/grow/frontend/session/Sessions.java b/src/com/p4square/grow/frontend/session/Sessions.java
new file mode 100644
index 0000000..094d2f0
--- /dev/null
+++ b/src/com/p4square/grow/frontend/session/Sessions.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.frontend.session;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+import org.restlet.Response;
+import org.restlet.Request;
+import org.restlet.security.User;
+
+/**
+ * Singleton Session Manager.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class Sessions {
+ private static final String COOKIE_NAME = "S";
+
+ private static final Sessions THE = new Sessions();
+ public static Sessions getInstance() {
+ return THE;
+ }
+
+ private final Map<String, Session> mSessions;
+
+ private Sessions() {
+ mSessions = new ConcurrentHashMap<String, Session>();
+ }
+
+ public Session get(String sessionid) {
+ Session s = mSessions.get(sessionid);
+
+ if (s != null && !s.isExpired()) {
+ s.touch();
+ return s;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the Session associated with the Request.
+ * @return A session or null if no session is found.
+ */
+ public Session get(Request request) {
+ final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
+
+ if (cookie != null) {
+ return get(cookie);
+ }
+
+ return null;
+ }
+
+ public Session create(User user) {
+ if (user == null) {
+ throw new IllegalArgumentException("Can not create session for null user.");
+ }
+
+ Session s = new Session(user);
+ mSessions.put(s.getId(), s);
+
+ return s;
+ }
+
+ /**
+ * Create a new Session and add the Session cookie to the response.
+ */
+ public Session create(Request request, Response response) {
+ Session s = create(request.getClientInfo().getUser());
+
+ request.getCookies().add(COOKIE_NAME, s.getId());
+ response.getCookieSettings().add(COOKIE_NAME, s.getId());
+
+ return s;
+ }
+}