/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package foam.box;

import foam.core.X;
import foam.dao.DAO;
import foam.nanos.app.AppConfig;
import foam.nanos.auth.AuthenticationException;
import foam.nanos.auth.AuthorizationException;
import foam.nanos.auth.Group;
import foam.nanos.auth.User;
import foam.nanos.boot.Boot;
import foam.nanos.boot.NSpec;
import foam.nanos.logger.Logger;
import foam.nanos.logger.PrefixLogger;
import foam.nanos.session.Session;
import foam.util.SafetyUtil;
import org.eclipse.jetty.server.Request;

import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;

/**
 * This Box decorator adds session support to boxes.
 *
 * Its core purpose is to create a new context using parts of the context it was
 * created with and parts of a user's session context to pass on to its delegate
 * box. This class also enforces authorization and authentication controls for
 * the NSpec in the context.
 */
public class SessionServerBox
  extends ProxyBox
{
  protected boolean authenticate_;

  public SessionServerBox(X x, Box delegate, boolean authenticate) {
    super(x, delegate);
    authenticate_ = authenticate;
  }

  public void send(Message msg) {
    Logger logger = (Logger) getX().get("logger");
    NSpec spec = getX().get(NSpec.class);
    String sessionID = null;

    try {
      HttpServletRequest req = getX().get(HttpServletRequest.class);
      if ( req != null ) {
        String authorization = req.getHeader("Authorization");
        if ( ! SafetyUtil.isEmpty(authorization) ) {
          StringTokenizer st = new StringTokenizer(authorization);
          if ( st.hasMoreTokens() ) {
            String authType = st.nextToken();
            if ( HTTPAuthorizationType.BEARER.getName().equalsIgnoreCase(authType) ) {
              sessionID = st.nextToken();
            } else {
              logger.warning(this.getClass().getSimpleName(), "send", "Authorization: "+authType+" not supported.");
              msg.replyWithException(new IllegalArgumentException("Authorization: "+authType+ " not supported."));
              return;
            }
            if ( SafetyUtil.isEmpty(sessionID) ) {
              logger.warning(this.getClass().getSimpleName(), "send", "Authorization: Bearer token not found.");
              msg.replyWithException(new IllegalArgumentException("Authorization: Bearer token not found"));
              return;
            }
          }
        }
      }

      if ( sessionID == null ) {
        sessionID = (String) msg.getAttributes().get("sessionId");

      }

      if ( sessionID == null && authenticate_ ) {
        msg.replyWithException(new IllegalArgumentException("sessionid required for authenticated services"));
        return;
      }

      DAO sessionDAO = (DAO) getX().get("localSessionDAO");
      Session session = sessionID == null ? null : (Session) sessionDAO.find(sessionID);

      if ( session == null ) {
        session = new Session((X) getX().get(Boot.ROOT));
        session.setId(sessionID == null ? "anonymous" : sessionID);
        if ( req != null ) {
          session.setRemoteHost(req.getRemoteHost());
        }

        //        session = (Session) sessionDAO.put(session);
        if ( System.getProperty("CLUSTER") != null ) {
          // how to avoid based on user?
          DAO userDAO = (DAO) getX().get("localUserDAO");
          User user = (User) userDAO.find(session.getUserId());
          if ( user != null &&
               "system".equals(user.getGroup()) ) {
            logger.warning("SessionServerBox", "Session not updated for user", session.getUserId());
            session.setX(getX());
          } else {
            session = (Session) sessionDAO.put(session);
          }
        } else {
          session = (Session) sessionDAO.put(session);
        }
      } else if ( req != null ) {
        // if req == null it means that we're being accessed via webSockets
        if ( ! session.validRemoteHost(req.getRemoteHost()) ) {
          // If an existing session is reused with a different remote host then
          // delete the session and force a re-login.
          // This is done as a security measure to reduce the likelihood of
          // session hijacking. If an attacker were to get ahold of another
          // user's session id, they could start using that session id in the
          // requests they send to the server and gain access to the real user's
          // session and therefore their privileges and data. By forcing users
          // to sign back in when the remote host changes, we reduce the attack
          // surface for session hijacking. Session hijacking is still possible,
          // but only if the user is on the same remote host.
          logger.warning("Remote host for session ", sessionID, " changed from ", session.getRemoteHost(), " to ", req.getRemoteHost(), ". Deleting session and forcing the user to sign in again.");
          sessionDAO.remove(session);
          msg.replyWithException(new AuthenticationException("IP address changed. Your session was deleted to keep your account secure. Please sign in again to verify your identity."));
        }
      }

      // If this service has been configured to require authentication, then
      // throw an error if there's no user in the context.
      if ( authenticate_ && session.getUserId() == 0 ) {
        msg.replyWithException(new AuthenticationException());
        return;
      }

      X effectiveContext = session.applyTo(getX());

      session.touch();

      try {
        spec.checkAuthorization(effectiveContext);
      } catch (AuthorizationException e) {
        Group group = (Group) effectiveContext.get("group");
        logger.warning("Missing permission", group != null ? group.getId() : "NO GROUP" , "service." + spec.getName());
        msg.replyWithException(e);
        return;
      }

      msg.getLocalAttributes().put("x", effectiveContext);
      getDelegate().send(msg);
    } catch (Throwable t) {
      logger.error("Error throw in SessionServerBox: " + t, " ,service: " + spec.getName(), t);
      t.printStackTrace();
      msg.replyWithException(t);
      return;
    }
  }
}

@@ -3286,24 +3286,27 @@
 on);%0A       
+ //
  if ( System
@@ -3349,18 +3349,21 @@
         
+//
   
+ 
 // how t
@@ -3392,16 +3392,19 @@
 %0A       
+ //
    DAO u
@@ -3453,16 +3453,19 @@
 %0A       
+ //
    User 
@@ -3512,32 +3512,35 @@
 erId());%0A       
+ //
    if ( user != 
@@ -3550,24 +3550,27 @@
 l &&%0A       
+ //
         %22sys
@@ -3602,36 +3602,39 @@
 ()) ) %7B%0A        
+//
     
+ 
 logger.warning(%22
@@ -3705,32 +3705,35 @@
 erId());%0A       
+ //
      session.set
@@ -3746,24 +3746,27 @@
 ());%0A       
+ //
    %7D else %7B%0A
@@ -3768,24 +3768,27 @@
 se %7B%0A       
+ //
      session
@@ -3824,32 +3824,35 @@
 ession);%0A       
+ //
    %7D%0A        %7D e
@@ -3843,24 +3843,27 @@
    %7D%0A       
+ //
  %7D else %7B%0A  
@@ -3852,32 +3852,33 @@
     // %7D else %7B%0A
+ 
           sessio
@@ -3915,32 +3915,35 @@
 ession);%0A       
+ //
  %7D%0A      %7D else 

Remove special CLUSTER considerations, no longer necessary