/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jooby.internal; import org.jooby.Cookie; import org.jooby.Cookie.Definition; import org.jooby.Request; import org.jooby.Response; import org.jooby.Route; import org.jooby.Session; import org.jooby.internal.parser.ParserExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * Save session data in a cookie. * * @author edgar */ public class CookieSessionManager implements SessionManager { /** The logging system. */ private final Logger log = LoggerFactory.getLogger(SessionManager.class); private final ParserExecutor resolver; private Definition cookie; private long timeout; private String secret; @Inject public CookieSessionManager(final ParserExecutor resolver, final Session.Definition cookie, @Named("application.secret") final String secret) { this.resolver = resolver; this.cookie = cookie.cookie(); this.timeout = TimeUnit.SECONDS.toMillis(this.cookie.maxAge().get()); this.secret = secret; } @Override public Session create(final Request req, final Response rsp) { Session session = new SessionImpl.Builder(resolver, true, Session.COOKIE_SESSION, -1).build(); log.debug("session created: {}", session); rsp.after(saveCookie()); return session; } @Override public Session get(final Request req, final Response rsp) { return req.cookie(cookie.name().get()).toOptional().map(raw -> { SessionImpl.Builder session = new SessionImpl.Builder(resolver, false, Session.COOKIE_SESSION, -1); Map<String, String> attributes = attributes(raw); session.set(attributes); rsp.after(saveCookie()); return session.build(); }).orElse(null); } @Override public void destroy(final Session session) { // NOOP } @Override public void requestDone(final Session session) { // NOOP } @Override public Definition cookie() { return new Definition(cookie); } private Map<String, String> attributes(final String raw) { String unsigned = Cookie.Signature.unsign(raw, secret); return Cookie.URL_DECODER.apply(unsigned); } private Route.After saveCookie() { return (req, rsp, result) -> { req.ifSession().ifPresent(session -> { Optional<String> value = req.cookie(cookie.name().get()).toOptional(); Map<String, String> initial = value .map(this::attributes) .orElse(Collections.emptyMap()); Map<String, String> attributes = session.attributes(); // is dirty? boolean dirty = !initial.equals(attributes); log.debug("session dirty: {}", dirty); if (dirty) { log.debug("saving session cookie"); String encoded = Cookie.URL_ENCODER.apply(attributes); String signed = Cookie.Signature.sign(encoded, secret); rsp.cookie(new Cookie.Definition(cookie).value(signed)); } else if (timeout > 0) { // touch session value.ifPresent(raw -> rsp.cookie(new Cookie.Definition(cookie).value(raw))); } }); return result; }; } }