diff options
| author | Jesse Morgan <jesse@jesterpm.net> | 2014-03-31 22:35:43 -0700 | 
|---|---|---|
| committer | Jesse Morgan <jesse@jesterpm.net> | 2014-03-31 22:35:43 -0700 | 
| commit | dcb9d839c9a30adc833af51e5c0f5a0df8175ce2 (patch) | |
| tree | 523be788e99a196c0df64e11a06d831b64a38179 /src | |
| parent | adba5974bae4da0def1f13d47cfcf28a37e3cf7a (diff) | |
Locking down restlet library version.20140331a
While trying to fix this issue, I also moved FMFacade into this package
and fixed a couple bugs that snuck into the last commit.
Diffstat (limited to 'src')
| -rw-r--r-- | src/com/p4square/grow/GrowProcessComponent.java | 7 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/SurveyPageResource.java | 5 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/FMFacade.java | 98 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/FreeMarkerPageResource.java | 97 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/ftl/GetMethod.java | 94 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/json/ClientException.java | 20 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/json/JsonRequestClient.java | 109 | ||||
| -rw-r--r-- | src/net/jesterpm/fmfacade/json/JsonResponse.java | 87 | ||||
| -rw-r--r-- | src/net/jesterpm/session/Session.java | 59 | ||||
| -rw-r--r-- | src/net/jesterpm/session/SessionAuthenticator.java | 36 | ||||
| -rw-r--r-- | src/net/jesterpm/session/SessionCheckingAuthenticator.java | 39 | ||||
| -rw-r--r-- | src/net/jesterpm/session/SessionCookieAuthenticator.java | 59 | ||||
| -rw-r--r-- | src/net/jesterpm/session/SessionCreatingAuthenticator.java | 46 | ||||
| -rw-r--r-- | src/net/jesterpm/session/Sessions.java | 155 | ||||
| -rw-r--r-- | src/templates/utils/dump.ftl | 98 | 
15 files changed, 1002 insertions, 7 deletions
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<String>(new JsonRequestProvider<Question>(getContext().getClientDispatcher(), Question.class)) { +        mQuestionProvider = new DelegateProvider<String, String, Question>( +                new JsonRequestProvider<Question>(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 <jesse@jesterpm.net> + */ + +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 <jesse@jesterpm.net> + */ +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 <jesse@jesterpm.net> + */ +public class FreeMarkerPageResource extends ServerResource { +    private static Logger cLog = Logger.getLogger(FreeMarkerPageResource.class); + +    public static Map<String, Object> baseRootObject(Context context) { +        Map<String, Object> root = new HashMap<String, Object>(); + +        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<String, Object> getRootObject() { +        Map<String, Object> root = baseRootObject(getContext()); + +        root.put("attributes", getRequestAttributes()); +        root.put("query", getQuery().getValuesMap()); +         +        if (getClientInfo().isAuthenticated()) { +            final User user = getClientInfo().getUser(); +            final Map<String, String> userMap = new HashMap<String, String>(); +            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 <jesse@jesterpm.net> + */ +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<Map> mapRepresentation; +                if (representation instanceof JacksonRepresentation) { +                    mapRepresentation = (JacksonRepresentation<Map>) representation; +                } else { +                    mapRepresentation = new JacksonRepresentation<Map>( +                            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 <jesse@jesterpm.net> + */ +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 <jesse@jesterpm.net> + */ +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>(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>(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 <jesse@jesterpm.net> + */ +public class JsonResponse { +    private final Response mResponse; +    private final Representation mRepresentation; + +    private Map<String, Object> 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<String, Object> getMap() throws ClientException { +        if (mMap == null) { +            Representation representation = mRepresentation; + +            // Parse response +            if (representation == null) { +                return null; +            } + +            JacksonRepresentation<Map> mapRepresentation; +            if (representation instanceof JacksonRepresentation) { +                mapRepresentation = (JacksonRepresentation<Map>) representation; +            } else { +                mapRepresentation = new JacksonRepresentation<Map>( +                        representation, Map.class); +            } + +            try { +                mMap = (Map<String, Object>) 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 <jesse@jesterpm.net> + */ +public class Session { +    static final long LIFETIME = 86400000; + +    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 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<String, String> 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 <jesse@jesterpm.net> + */ +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 <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) { +            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 <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/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 <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); +            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 <jesse@jesterpm.net> + */ +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<String, Session> mSessions; +    private final Timer mCleanupTimer; + +    private Sessions() { +        mSessions = new ConcurrentHashMap<String, Session>(); + +        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 <pre>-tags to get the wanted
 +  -- result:
 +  -- <pre>
 +  -- <@dumper.dump foo />
 +  -- <pre>
 +  -->
 +
 +<#-- 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,[] />
 +</#if>
 +</#macro>
 +
 +<#-- 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>  | </#if></#list>
 +<#list has_next_array as has_next><#if !has_next>    <#else>  | </#if></#list><#t>
 +<#t><@printItem item?if_exists,has_next_array+[item_has_next], counter />
 +<#local counter = counter + 1/>
 +</#list>
 +</#macro>
 +
 +<#macro printHashEx hash has_next_array>
 +<#list hash?keys as key>
 +<#list has_next_array+[true] as has_next><#if !has_next>    <#else>  | </#if></#list>
 +<#list has_next_array as has_next><#if !has_next>    <#else>  | </#if></#list><#t>
 +<#t><@printItem hash[key]?if_exists,has_next_array+[key_has_next], key />
 +</#list>
 +</#macro>
 +
 +<#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)
 +</#if>
 +</#macro>
 +
 +<#function omit key>
 +    <#local what = key?lower_case>
 +    <#list black_list as item>
 +        <#if what?index_of(item) gte 0>
 +            <#return true>
 +        </#if>
 +    </#list>
 +    <#return false>
 +</#function>
\ No newline at end of file  | 
