From dcb9d839c9a30adc833af51e5c0f5a0df8175ce2 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Mon, 31 Mar 2014 22:35:43 -0700 Subject: Locking down restlet library version. While trying to fix this issue, I also moved FMFacade into this package and fixed a couple bugs that snuck into the last commit. --- ivy.xml | 12 +- src/com/p4square/grow/GrowProcessComponent.java | 7 +- .../p4square/grow/frontend/SurveyPageResource.java | 5 +- src/net/jesterpm/fmfacade/FMFacade.java | 98 +++++++++++++ .../jesterpm/fmfacade/FreeMarkerPageResource.java | 97 +++++++++++++ src/net/jesterpm/fmfacade/ftl/GetMethod.java | 94 +++++++++++++ .../jesterpm/fmfacade/json/ClientException.java | 20 +++ .../jesterpm/fmfacade/json/JsonRequestClient.java | 109 +++++++++++++++ src/net/jesterpm/fmfacade/json/JsonResponse.java | 87 ++++++++++++ src/net/jesterpm/session/Session.java | 59 ++++++++ src/net/jesterpm/session/SessionAuthenticator.java | 36 +++++ .../session/SessionCheckingAuthenticator.java | 39 ++++++ .../session/SessionCookieAuthenticator.java | 59 ++++++++ .../session/SessionCreatingAuthenticator.java | 46 ++++++ src/net/jesterpm/session/Sessions.java | 155 +++++++++++++++++++++ src/templates/utils/dump.ftl | 98 +++++++++++++ web/WEB-INF/web.xml | 10 -- 17 files changed, 1010 insertions(+), 21 deletions(-) create mode 100644 src/net/jesterpm/fmfacade/FMFacade.java create mode 100644 src/net/jesterpm/fmfacade/FreeMarkerPageResource.java create mode 100644 src/net/jesterpm/fmfacade/ftl/GetMethod.java create mode 100644 src/net/jesterpm/fmfacade/json/ClientException.java create mode 100644 src/net/jesterpm/fmfacade/json/JsonRequestClient.java create mode 100644 src/net/jesterpm/fmfacade/json/JsonResponse.java create mode 100644 src/net/jesterpm/session/Session.java create mode 100644 src/net/jesterpm/session/SessionAuthenticator.java create mode 100644 src/net/jesterpm/session/SessionCheckingAuthenticator.java create mode 100644 src/net/jesterpm/session/SessionCookieAuthenticator.java create mode 100644 src/net/jesterpm/session/SessionCreatingAuthenticator.java create mode 100644 src/net/jesterpm/session/Sessions.java create mode 100644 src/templates/utils/dump.ftl diff --git a/ivy.xml b/ivy.xml index 62d73cf..00cdbc6 100644 --- a/ivy.xml +++ b/ivy.xml @@ -16,13 +16,17 @@ + + + + + + + + - - - - diff --git a/src/com/p4square/grow/GrowProcessComponent.java b/src/com/p4square/grow/GrowProcessComponent.java index eb92840..29da766 100644 --- a/src/com/p4square/grow/GrowProcessComponent.java +++ b/src/com/p4square/grow/GrowProcessComponent.java @@ -56,8 +56,8 @@ public class GrowProcessComponent extends Component { // Authenticated access to the backend BackendVerifier verifier = new BackendVerifier(backend.getUserRecordProvider()); - ChallengeAuthenticator auth = new ChallengeAuthenticator(getContext(), false, - ChallengeScheme.HTTP_BASIC, BACKEND_REALM, verifier); + ChallengeAuthenticator auth = new ChallengeAuthenticator(getContext().createChildContext(), + false, ChallengeScheme.HTTP_BASIC, BACKEND_REALM, verifier); auth.setNext(backend); getDefaultHost().attach("/backend", auth); } @@ -87,9 +87,6 @@ public class GrowProcessComponent extends Component { // Start the HTTP Server final GrowProcessComponent component = new GrowProcessComponent(); component.getServers().add(Protocol.HTTP, 8085); - component.getClients().add(Protocol.HTTP); - component.getClients().add(Protocol.HTTPS); - component.getClients().add(Protocol.FILE); //component.getClients().add(new Client(null, Arrays.asList(Protocol.HTTPS), "org.restlet.ext.httpclient.HttpClientHelper")); // Static content diff --git a/src/com/p4square/grow/frontend/SurveyPageResource.java b/src/com/p4square/grow/frontend/SurveyPageResource.java index 07bc73c..313fb7b 100644 --- a/src/com/p4square/grow/frontend/SurveyPageResource.java +++ b/src/com/p4square/grow/frontend/SurveyPageResource.java @@ -33,7 +33,6 @@ import com.p4square.grow.model.UserRecord; import com.p4square.grow.provider.DelegateProvider; import com.p4square.grow.provider.JsonEncodedProvider; import com.p4square.grow.provider.Provider; -import com.p4square.grow.provider.QuestionProvider; /** * SurveyPageResource handles rendering the survey and processing user's answers. @@ -71,7 +70,9 @@ public class SurveyPageResource extends FreeMarkerPageResource { } mJsonClient = new JsonRequestClient(getContext().getClientDispatcher()); - mQuestionProvider = new QuestionProvider(new JsonRequestProvider(getContext().getClientDispatcher(), Question.class)) { + mQuestionProvider = new DelegateProvider( + new JsonRequestProvider(getContext().getClientDispatcher(), + Question.class)) { @Override public String makeKey(String questionId) { return getBackendEndpoint() + "/assessment/question/" + questionId; diff --git a/src/net/jesterpm/fmfacade/FMFacade.java b/src/net/jesterpm/fmfacade/FMFacade.java new file mode 100644 index 0000000..0ea44f8 --- /dev/null +++ b/src/net/jesterpm/fmfacade/FMFacade.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade; + +import java.io.IOException; + +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.data.Protocol; +import org.restlet.Restlet; +import org.restlet.routing.Router; + +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.Template; + +import org.apache.log4j.Logger; + +/** + * + * @author Jesse Morgan + */ +public class FMFacade extends Application { + private static final Logger cLog = Logger.getLogger(FMFacade.class); + private final Configuration mFMConfig; + + public FMFacade() { + mFMConfig = new Configuration(); + mFMConfig.setClassForTemplateLoading(getClass(), "/templates"); + mFMConfig.setObjectWrapper(new DefaultObjectWrapper()); + } + + @Override + public synchronized Restlet createInboundRoot() { + return createRouter(); + } + + /** + * Retrieve a template. + * + * @param name The template name. + * @return A FreeMarker template or null on error. + */ + public Template getTemplate(String name) { + try { + return mFMConfig.getTemplate(name); + + } catch (IOException e) { + cLog.error("Could not load template \"" + name + "\"", e); + return null; + } + } + + /** + * Create the router to be used by this application. This can be overriden + * by sub-classes to add additional routes. + * + * @return The router. + */ + protected Router createRouter() { + Router router = new Router(getContext()); + router.attachDefault(FreeMarkerPageResource.class); + + return router; + } + + /** + * Stand-alone main for testing. + */ + public static void main(String[] args) { + // Start the HTTP Server + final Component component = new Component(); + component.getServers().add(Protocol.HTTP, 8085); + component.getClients().add(Protocol.HTTP); + component.getDefaultHost().attach(new FMFacade()); + + // Setup shutdown hook + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + try { + component.stop(); + } catch (Exception e) { + cLog.error("Exception during cleanup", e); + } + } + }); + + cLog.info("Starting server..."); + + try { + component.start(); + } catch (Exception e) { + cLog.fatal("Could not start: " + e.getMessage(), e); + } + } +} diff --git a/src/net/jesterpm/fmfacade/FreeMarkerPageResource.java b/src/net/jesterpm/fmfacade/FreeMarkerPageResource.java new file mode 100644 index 0000000..dfdba39 --- /dev/null +++ b/src/net/jesterpm/fmfacade/FreeMarkerPageResource.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade; + +import java.util.Map; +import java.util.HashMap; + +import freemarker.template.Template; + +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.ext.freemarker.TemplateRepresentation; +import org.restlet.representation.Representation; +import org.restlet.resource.ServerResource; +import org.restlet.security.User; + +import org.apache.log4j.Logger; + +import net.jesterpm.fmfacade.ftl.GetMethod; + +import net.jesterpm.session.Session; +import net.jesterpm.session.Sessions; + +/** + * + * @author Jesse Morgan + */ +public class FreeMarkerPageResource extends ServerResource { + private static Logger cLog = Logger.getLogger(FreeMarkerPageResource.class); + + public static Map baseRootObject(Context context) { + Map root = new HashMap(); + + root.put("get", new GetMethod(context.getClientDispatcher())); + + return root; + } + + private FMFacade mFMF; + private String mCurrentPage; + + @Override + public void doInit() { + mFMF = (FMFacade) getApplication(); + mCurrentPage = getReference().getRemainingPart(false, false); + } + + protected Representation get() { + try { + Template t = mFMF.getTemplate("pages" + mCurrentPage + ".ftl"); + + if (t == null) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + } + + return new TemplateRepresentation(t, getRootObject(), + MediaType.TEXT_HTML); + + } catch (Exception e) { + cLog.fatal("Could not render page: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + } + + /** + * Build and return the root object to pass to the FTL Template. + * @return A map of objects and methods for the template to access. + */ + protected Map getRootObject() { + Map root = baseRootObject(getContext()); + + root.put("attributes", getRequestAttributes()); + root.put("query", getQuery().getValuesMap()); + + if (getClientInfo().isAuthenticated()) { + final User user = getClientInfo().getUser(); + final Map userMap = new HashMap(); + userMap.put("id", user.getIdentifier()); + userMap.put("firstName", user.getFirstName()); + userMap.put("lastName", user.getLastName()); + userMap.put("email", user.getEmail()); + root.put("user", userMap); + } + + Session s = Sessions.getInstance().get(getRequest()); + if (s != null) { + root.put("session", s.getMap()); + } + + return root; + } +} diff --git a/src/net/jesterpm/fmfacade/ftl/GetMethod.java b/src/net/jesterpm/fmfacade/ftl/GetMethod.java new file mode 100644 index 0000000..8b92406 --- /dev/null +++ b/src/net/jesterpm/fmfacade/ftl/GetMethod.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade.ftl; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +import java.io.IOException; + +import freemarker.core.Environment; +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateMethodModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +import org.apache.log4j.Logger; + +import org.restlet.data.Status; +import org.restlet.data.Method; +import org.restlet.representation.Representation; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; + +import org.restlet.ext.jackson.JacksonRepresentation; + +/** + * This method allows templates to make GET requests. + * + * @author Jesse Morgan + */ +public class GetMethod implements TemplateMethodModel { + private static final Logger cLog = Logger.getLogger(GetMethod.class); + + private final Restlet mDispatcher; + + public GetMethod(Restlet dispatcher) { + mDispatcher = dispatcher; + } + + /** + * @param args List with exactly two arguments: + * * The variable in which to put the result. + * * The URI to GET. + */ + public TemplateModel exec(List args) throws TemplateModelException { + final Environment env = Environment.getCurrentEnvironment(); + + if (args.size() != 2) { + throw new TemplateModelException( + "Expecting exactly one argument containing the URI"); + } + + Request request = new Request(Method.GET, (String) args.get(1)); + Response response = mDispatcher.handle(request); + Status status = response.getStatus(); + Representation representation = response.getEntity(); + + try { + if (response.getStatus().isSuccess()) { + JacksonRepresentation mapRepresentation; + if (representation instanceof JacksonRepresentation) { + mapRepresentation = (JacksonRepresentation) representation; + } else { + mapRepresentation = new JacksonRepresentation( + representation, Map.class); + } + try { + TemplateModel mapModel = env.getObjectWrapper().wrap(mapRepresentation.getObject()); + + env.setVariable((String) args.get(0), mapModel); + + } catch (IOException e) { + cLog.warn("Exception occurred when calling getObject(): " + + e.getMessage(), e); + status = Status.SERVER_ERROR_INTERNAL; + } + } + + Map statusMap = new HashMap(); + statusMap.put("code", status.getCode()); + statusMap.put("reason", status.getReasonPhrase()); + statusMap.put("succeeded", status.isSuccess()); + return env.getObjectWrapper().wrap(statusMap); + } finally { + if (representation != null) { + representation.release(); + } + } + } +} diff --git a/src/net/jesterpm/fmfacade/json/ClientException.java b/src/net/jesterpm/fmfacade/json/ClientException.java new file mode 100644 index 0000000..fc8bf41 --- /dev/null +++ b/src/net/jesterpm/fmfacade/json/ClientException.java @@ -0,0 +1,20 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade.json; + +/** + * + * @author Jesse Morgan + */ +public class ClientException extends Exception { + + public ClientException(final String msg) { + super(msg); + } + + public ClientException(final String msg, final Exception cause) { + super(msg, cause); + } +} diff --git a/src/net/jesterpm/fmfacade/json/JsonRequestClient.java b/src/net/jesterpm/fmfacade/json/JsonRequestClient.java new file mode 100644 index 0000000..51cf317 --- /dev/null +++ b/src/net/jesterpm/fmfacade/json/JsonRequestClient.java @@ -0,0 +1,109 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade.json; + +import java.util.Map; + +import java.io.IOException; + +import org.apache.log4j.Logger; + +import org.restlet.data.Status; +import org.restlet.data.Method; +import org.restlet.representation.Representation; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; + +import org.restlet.ext.jackson.JacksonRepresentation; + +/** + * + * @author Jesse Morgan + */ +public class JsonRequestClient { + private final Restlet mDispatcher; + + public JsonRequestClient(Restlet dispatcher) { + mDispatcher = dispatcher; + } + + /** + * Perform a GET request for the given URI and parse the response as a + * JSON map. + * + * @return A JsonResponse object which can be used to retrieve the + * response as a JSON map. + */ + public JsonResponse get(final String uri) { + final Request request = new Request(Method.GET, uri); + final Response response = mDispatcher.handle(request); + + return new JsonResponse(response); + } + + /** + * Perform a PUT request for the given URI and parse the response as a + * JSON map. + * + * @return A JsonResponse object which can be used to retrieve the + * response as a JSON map. + */ + public JsonResponse put(final String uri, Representation entity) { + final Request request = new Request(Method.PUT, uri); + request.setEntity(entity); + + final Response response = mDispatcher.handle(request); + return new JsonResponse(response); + } + + /** + * Perform a PUT request for the given URI and parse the response as a + * JSON map. + * + * @return A JsonResponse object which can be used to retrieve the + * response as a JSON map. + */ + public JsonResponse put(final String uri, Map map) { + return put(uri, new JacksonRepresentation(map)); + } + + /** + * Perform a POST request for the given URI and parse the response as a + * JSON map. + * + * @return A JsonResponse object which can be used to retrieve the + * response as a JSON map. + */ + public JsonResponse post(final String uri, Representation entity) { + final Request request = new Request(Method.POST, uri); + request.setEntity(entity); + + final Response response = mDispatcher.handle(request); + return new JsonResponse(response); + } + + /** + * Perform a POST request for the given URI and parse the response as a + * JSON map. + * + * @return A JsonResponse object which can be used to retrieve the + * response as a JSON map. + */ + public JsonResponse post(final String uri, Map map) { + return post(uri, new JacksonRepresentation(map)); + } + + /** + * Perform a DELETE request for the given URI. + * + * @return A JsonResponse object with the status of the request. + */ + public JsonResponse delete(final String uri) { + final Request request = new Request(Method.DELETE, uri); + final Response response = mDispatcher.handle(request); + return new JsonResponse(response); + } +} diff --git a/src/net/jesterpm/fmfacade/json/JsonResponse.java b/src/net/jesterpm/fmfacade/json/JsonResponse.java new file mode 100644 index 0000000..f4a9050 --- /dev/null +++ b/src/net/jesterpm/fmfacade/json/JsonResponse.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.fmfacade.json; + +import java.util.Map; + +import java.io.IOException; + +import org.restlet.data.Status; +import org.restlet.data.Reference; +import org.restlet.representation.Representation; +import org.restlet.Response; + +import org.restlet.ext.jackson.JacksonRepresentation; + +/** + * JsonResponse wraps a Restlet Response object and parses the entity, if any, + * as a JSON map. + * + * @author Jesse Morgan + */ +public class JsonResponse { + private final Response mResponse; + private final Representation mRepresentation; + + private Map mMap; + + JsonResponse(Response response) { + mResponse = response; + mRepresentation = response.getEntity(); + mMap = null; + + if (!response.getStatus().isSuccess()) { + if (mRepresentation != null) { + mRepresentation.release(); + } + } + } + + /** + * @return the Status info from the response. + */ + public Status getStatus() { + return mResponse.getStatus(); + } + + /** + * @return the Reference for a redirect. + */ + public Reference getRedirectLocation() { + return mResponse.getLocationRef(); + } + + /** + * Return the parsed json map from the response. + */ + public Map getMap() throws ClientException { + if (mMap == null) { + Representation representation = mRepresentation; + + // Parse response + if (representation == null) { + return null; + } + + JacksonRepresentation mapRepresentation; + if (representation instanceof JacksonRepresentation) { + mapRepresentation = (JacksonRepresentation) representation; + } else { + mapRepresentation = new JacksonRepresentation( + representation, Map.class); + } + + try { + mMap = (Map) mapRepresentation.getObject(); + + } catch (IOException e) { + throw new ClientException("Failed to parse response: " + e.getMessage(), e); + } + } + + return mMap; + } + +} diff --git a/src/net/jesterpm/session/Session.java b/src/net/jesterpm/session/Session.java new file mode 100644 index 0000000..ffb4cd1 --- /dev/null +++ b/src/net/jesterpm/session/Session.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.session; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.restlet.security.User; + +/** + * + * @author Jesse Morgan + */ +public class Session { + static final long LIFETIME = 86400000; + + private final String mSessionId; + private final User mUser; + private final Map mData; + private long mExpires; + + Session(User user) { + mUser = user; + mSessionId = UUID.randomUUID().toString(); + mExpires = System.currentTimeMillis() + LIFETIME; + mData = new HashMap(); + } + + void touch() { + mExpires = System.currentTimeMillis() + LIFETIME; + } + + boolean isExpired() { + return System.currentTimeMillis() > mExpires; + } + + public String getId() { + return mSessionId; + } + + public Object get(String key) { + return mData.get(key); + } + + public void put(String key, String value) { + mData.put(key, value); + } + + public User getUser() { + return mUser; + } + + public Map getMap() { + return mData; + } +} diff --git a/src/net/jesterpm/session/SessionAuthenticator.java b/src/net/jesterpm/session/SessionAuthenticator.java new file mode 100644 index 0000000..09802e7 --- /dev/null +++ b/src/net/jesterpm/session/SessionAuthenticator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.session; + +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.security.Authenticator; +import org.restlet.security.User; + +/** + * + * @author Jesse Morgan + */ +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); + if (cookie != null) { + cLog.debug("Got cookie: " + cookie); + // TODO Decrypt user info + User user = new User(cookie); + request.getClientInfo().setUser(user); + return true; + } + + // Challenge the user if not authenticated + response.redirectSeeOther(mLoginPage); + return false; + } + */ +} diff --git a/src/net/jesterpm/session/SessionCheckingAuthenticator.java b/src/net/jesterpm/session/SessionCheckingAuthenticator.java new file mode 100644 index 0000000..f8db974 --- /dev/null +++ b/src/net/jesterpm/session/SessionCheckingAuthenticator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.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 + */ +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) { + LOG.debug("Found session for user " + s.getUser()); + request.getClientInfo().setUser(s.getUser()); + return true; + + } else { + return false; + } + } + +} diff --git a/src/net/jesterpm/session/SessionCookieAuthenticator.java b/src/net/jesterpm/session/SessionCookieAuthenticator.java new file mode 100644 index 0000000..744c054 --- /dev/null +++ b/src/net/jesterpm/session/SessionCookieAuthenticator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.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 + */ +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/net/jesterpm/session/SessionCreatingAuthenticator.java b/src/net/jesterpm/session/SessionCreatingAuthenticator.java new file mode 100644 index 0000000..4b0bc91 --- /dev/null +++ b/src/net/jesterpm/session/SessionCreatingAuthenticator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.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 + */ +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); + LOG.debug(response); + return true; + } + + return false; + } + +} diff --git a/src/net/jesterpm/session/Sessions.java b/src/net/jesterpm/session/Sessions.java new file mode 100644 index 0000000..e225853 --- /dev/null +++ b/src/net/jesterpm/session/Sessions.java @@ -0,0 +1,155 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package net.jesterpm.session; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import org.restlet.Response; +import org.restlet.Request; +import org.restlet.data.CookieSetting; +import org.restlet.security.User; + +/** + * Singleton Session Manager. + * + * @author Jesse Morgan + */ +public class Sessions { + private static final String COOKIE_NAME = "S"; + private static final int DELETE = 0; + + private static final Sessions THE = new Sessions(); + public static Sessions getInstance() { + return THE; + } + + private final Map mSessions; + private final Timer mCleanupTimer; + + private Sessions() { + mSessions = new ConcurrentHashMap(); + + mCleanupTimer = new Timer("sessionCleaner", true); + mCleanupTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + for (Session s : mSessions.values()) { + if (s.isExpired()) { + mSessions.remove(s.getId()); + } + } + } + }, Session.LIFETIME, Session.LIFETIME); + } + + /** + * Get a session by ID. + * + * @param sessionid + * The Session id + * @return The Session if found and not expired, null otherwise. + */ + 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. + * + * @param request + * The request to fetch a session for. + * @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; + } + + /** + * Create a new Session for the given User object. + * + * @param user + * The User to associate with the Session. + * @return The new Session object. + */ + 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; + } + + /** + * Delete a Session. + * + * @param sessionid + * The id of the Session to remove. + */ + public void delete(String sessionid) { + mSessions.remove(sessionid); + } + + /** + * Create a new Session and add the Session cookie to the response. + * + * @param request + * The request to create the Session for. + * @param response + * The response to add the session cookie to. + * @return The new Session. + */ + public Session create(Request request, Response response) { + Session s = create(request.getClientInfo().getUser()); + + CookieSetting cookie = new CookieSetting(COOKIE_NAME, s.getId()); + cookie.setPath("/"); + + request.getCookies().add(cookie); + response.getCookieSettings().add(cookie); + + return s; + } + + /** + * Remove a Session and delete the cookies. + * + * @param request + * The request with the session cookie to remove + * @param response + * The response to remove the session cookie from. + */ + public void delete(Request request, Response response) { + final String sessionid = request.getCookies().getFirstValue(COOKIE_NAME); + + delete(sessionid); + + CookieSetting cookie = new CookieSetting(COOKIE_NAME, ""); + cookie.setPath("/"); + cookie.setMaxAge(DELETE); + + request.getCookies().add(cookie); + response.getCookieSettings().add(cookie); + } + +} diff --git a/src/templates/utils/dump.ftl b/src/templates/utils/dump.ftl new file mode 100644 index 0000000..6491a25 --- /dev/null +++ b/src/templates/utils/dump.ftl @@ -0,0 +1,98 @@ +<#-- dump.ftl + -- + -- Generates tree representations of data model items. + -- + -- Usage: + -- <#import "dump.ftl" as dumper> + -- + -- <#assign foo = something.in["your"].data[0].model /> + -- + -- <@dumper.dump foo /> + -- + -- When used within html pages you've to use
-tags to get the wanted
+  -- result:
+  -- 
+  -- <@dumper.dump foo />
+  -- 
+  -->
+
+<#-- The black_list contains bad hash keys. Any hash key which matches a 
+  -- black_list entry is prevented from being displayed.
+  -->
+<#assign black_list = ["class"] />
+
+
+<#-- 
+  -- The main macro.
+  -->
+  
+<#macro dump data>
+(root)
+<#if data?is_enumerable>
+<@printList data,[] />
+<#elseif data?is_hash_ex>
+<@printHashEx data,[] />
+
+
+
+<#-- private helper macros. it's not recommended to use these macros from 
+  -- outside the macro library.
+  -->
+
+<#macro printList list has_next_array>
+<#local counter=0 />
+<#list list as item>
+<#list has_next_array+[true] as has_next><#if !has_next>    <#else>  | 
+<#list has_next_array as has_next><#if !has_next>    <#else>  | <#t>
+<#t><@printItem item?if_exists,has_next_array+[item_has_next], counter />
+<#local counter = counter + 1/>
+
+
+
+<#macro printHashEx hash has_next_array>
+<#list hash?keys as key>
+<#list has_next_array+[true] as has_next><#if !has_next>    <#else>  | 
+<#list has_next_array as has_next><#if !has_next>    <#else>  | <#t>
+<#t><@printItem hash[key]?if_exists,has_next_array+[key_has_next], key />
+
+
+
+<#macro printItem item has_next_array key>
+<#if item?is_method>
+  +- ${key} = ?? (method)
+<#elseif item?is_enumerable>
+  +- ${key}
+  <@printList item, has_next_array /><#t>
+<#elseif item?is_hash_ex && omit(key?string)><#-- omit bean-wrapped java.lang.Class objects -->
+  +- ${key} (omitted)
+<#elseif item?is_hash_ex>
+  +- ${key}
+  <@printHashEx item, has_next_array /><#t>
+<#elseif item?is_number>
+  +- ${key} = ${item}
+<#elseif item?is_string>
+  +- ${key} = "${item}"
+<#elseif item?is_boolean>
+  +- ${key} = ${item?string}
+<#elseif item?is_date>
+  +- ${key} = ${item?string("yyyy-MM-dd HH:mm:ss zzzz")}
+<#elseif item?is_transform>
+  +- ${key} = ?? (transform)
+<#elseif item?is_macro>
+  +- ${key} = ?? (macro)
+<#elseif item?is_hash>
+  +- ${key} = ?? (hash)
+<#elseif item?is_node>
+  +- ${key} = ?? (node)
+
+
+
+<#function omit key>
+    <#local what = key?lower_case>
+    <#list black_list as item>
+        <#if what?index_of(item) gte 0>
+            <#return true>
+        
+    
+    <#return false>
+
\ No newline at end of file
diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml
index a06031c..4b85bde 100644
--- a/web/WEB-INF/web.xml
+++ b/web/WEB-INF/web.xml
@@ -41,14 +41,4 @@
         RestletServlet
         /*
     
-
-    
-      404
-      /notfound.html
-    
-
-    
-      500
-      /error.html
-    
 
-- 
cgit v1.2.3