Example #1
0
 private void handleReset() {
   // This posts the reset event to any classes that need to take action,
   // avoiding coupling this class to those classes.
   Events.eventBus().post(new ResetEvent());
   if (LanternClientConstants.DEFAULT_MODEL_FILE.isFile()) {
     try {
       FileUtils.forceDelete(LanternClientConstants.DEFAULT_MODEL_FILE);
     } catch (final IOException e) {
       log.warn("Could not delete model file?");
     }
   }
   final Model base = new Model(model.getCountryService());
   model.setEverGetMode(false);
   model.setLaunchd(base.isLaunchd());
   model.setModal(base.getModal());
   model.setNodeId(base.getNodeId());
   model.setProfile(base.getProfile());
   model.setNproxiedSitesMax(base.getNproxiedSitesMax());
   // we need to keep clientID and clientSecret, because they are application-level settings
   String clientID = model.getSettings().getClientID();
   String clientSecret = model.getSettings().getClientSecret();
   model.setSettings(base.getSettings());
   model.getSettings().setClientID(clientID);
   model.getSettings().setClientSecret(clientSecret);
   model.setSetupComplete(base.isSetupComplete());
   model.setShowVis(base.isShowVis());
   // model.setFriends(base.getFriends());
   model.clearNotifications();
   modelIo.write();
 }
Example #2
0
 /**
  * Convenience method for syncing the current modal with the frontend.
  *
  * @param model The state model.
  */
 public static void syncModal(final Model model) {
   Events.asyncEventBus().post(new SyncEvent(SyncPath.MODAL, model.getModal()));
 }
Example #3
0
  protected void processRequest(final HttpServletRequest req, final HttpServletResponse resp) {
    LanternUtils.addCSPHeader(resp);
    final String uri = req.getRequestURI();
    log.debug("Received URI: {}", uri);
    final String interactionStr = StringUtils.substringAfterLast(uri, "/");
    if (StringUtils.isBlank(interactionStr)) {
      log.debug("blank interaction");
      HttpUtils.sendClientError(resp, "blank interaction");
      return;
    }

    log.debug("Headers: " + HttpUtils.getRequestHeaders(req));

    if (!"XMLHttpRequest".equals(req.getHeader("X-Requested-With"))) {
      log.debug("invalid X-Requested-With");
      HttpUtils.sendClientError(resp, "invalid X-Requested-With");
      return;
    }

    if (!SecurityUtils.constantTimeEquals(model.getXsrfToken(), req.getHeader("X-XSRF-TOKEN"))) {
      log.debug(
          "X-XSRF-TOKEN wrong: got {} expected {}",
          req.getHeader("X-XSRF-TOKEN"),
          model.getXsrfToken());
      HttpUtils.sendClientError(resp, "invalid X-XSRF-TOKEN");
      return;
    }

    final int cl = req.getContentLength();
    String json = "";
    if (cl > 0) {
      try {
        json = IOUtils.toString(req.getInputStream());
      } catch (final IOException e) {
        log.error("Could not parse json?");
      }
    }

    log.debug("Body: '" + json + "'");

    final Interaction inter = Interaction.valueOf(interactionStr.toUpperCase());

    if (inter == Interaction.CLOSE) {
      if (handleClose(json)) {
        return;
      }
    }

    if (inter == Interaction.URL) {
      final String url = JsonUtils.getValueFromJson("url", json);
      if (!StringUtils.startsWith(url, "http://") && !StringUtils.startsWith(url, "https://")) {
        log.error("http(s) url expected, got {}", url);
        HttpUtils.sendClientError(resp, "http(s) urls only");
        return;
      }
      try {
        new URL(url);
      } catch (MalformedURLException e) {
        log.error("invalid url: {}", url);
        HttpUtils.sendClientError(resp, "invalid url");
        return;
      }

      final String cmd;
      if (SystemUtils.IS_OS_MAC_OSX) {
        cmd = "open";
      } else if (SystemUtils.IS_OS_LINUX) {
        cmd = "gnome-open";
      } else if (SystemUtils.IS_OS_WINDOWS) {
        cmd = "start";
      } else {
        log.error("unsupported OS");
        HttpUtils.sendClientError(resp, "unsupported OS");
        return;
      }
      try {
        if (SystemUtils.IS_OS_WINDOWS) {
          // On Windows, we have to quote the url to allow for
          // e.g. ? and & characters in query string params.
          // To quote the url, we supply a dummy first argument,
          // since otherwise start treats the first argument as a
          // title for the new console window when it's quoted.
          LanternUtils.runCommand(cmd, "\"\"", "\"" + url + "\"");
        } else {
          // on OS X and Linux, special characters in the url make
          // it through this call without our having to quote them.
          LanternUtils.runCommand(cmd, url);
        }
      } catch (IOException e) {
        log.error("open url failed");
        HttpUtils.sendClientError(resp, "open url failed");
        return;
      }
      return;
    }

    final Modal modal = this.model.getModal();

    log.debug(
        "processRequest: modal = {}, inter = {}, mode = {}",
        modal,
        inter,
        this.model.getSettings().getMode());

    if (handleExceptionalInteractions(modal, inter, json)) {
      return;
    }

    Modal switchTo = null;
    try {
      // XXX a map would make this more robust
      switchTo = Modal.valueOf(interactionStr);
    } catch (IllegalArgumentException e) {
    }
    if (switchTo != null && switchModals.contains(switchTo)) {
      if (!switchTo.equals(modal)) {
        if (!switchModals.contains(modal)) {
          this.internalState.setLastModal(modal);
        }
        Events.syncModal(model, switchTo);
      }
      return;
    }

    switch (modal) {
      case welcome:
        this.model.getSettings().setMode(Mode.unknown);
        switch (inter) {
          case GET:
            log.debug("Setting get mode");
            handleSetModeWelcome(Mode.get);
            break;
          case GIVE:
            log.debug("Setting give mode");
            handleSetModeWelcome(Mode.give);
            break;
        }
        break;
      case authorize:
        log.debug("Processing authorize modal...");
        this.internalState.setModalCompleted(Modal.authorize);
        this.internalState.advanceModal(null);
        break;
      case finished:
        this.internalState.setCompletedTo(Modal.finished);
        switch (inter) {
          case CONTINUE:
            log.debug("Processing continue");
            this.model.setShowVis(true);
            Events.sync(SyncPath.SHOWVIS, true);
            this.internalState.setModalCompleted(Modal.finished);
            this.internalState.advanceModal(null);
            break;
          case SET:
            log.debug("Processing set in finished modal...applying JSON\n{}", json);
            applyJson(json);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(
                resp, "Interaction not handled for modal: " + modal + " and interaction: " + inter);
            break;
        }
        break;
      case firstInviteReceived:
        log.error("Processing invite received...");
        break;
      case lanternFriends:
        this.internalState.setCompletedTo(Modal.lanternFriends);
        switch (inter) {
          case FRIEND:
            this.friender.addFriend(email(json));
            break;
          case REJECT:
            this.friender.removeFriend(email(json));
            break;
          case CONTINUE:
            // This dialog always passes continue as of this writing and
            // not close.
          case CLOSE:
            log.debug("Processing continue/close for friends dialog");
            if (this.model.isSetupComplete()) {
              Events.syncModal(model, Modal.none);
            } else {
              this.internalState.setModalCompleted(Modal.lanternFriends);
              this.internalState.advanceModal(null);
            }
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(
                resp, "Interaction not handled for modal: " + modal + " and interaction: " + inter);
            break;
        }
        break;
      case none:
        break;
      case notInvited:
        switch (inter) {
          case RETRY:
            Events.syncModal(model, Modal.authorize);
            break;
            // not currently implemented:
            // case REQUESTINVITE:
            //    Events.syncModal(model, Modal.requestInvite);
            //    break;
          default:
            log.error("Unexpected interaction: " + inter);
            break;
        }
        break;
      case proxiedSites:
        this.internalState.setCompletedTo(Modal.proxiedSites);
        switch (inter) {
          case CONTINUE:
            if (this.model.isSetupComplete()) {
              Events.syncModal(model, Modal.none);
            } else {
              this.internalState.setModalCompleted(Modal.proxiedSites);
              this.internalState.advanceModal(null);
            }
            break;
          case LANTERNFRIENDS:
            log.debug("Processing lanternFriends from proxiedSites");
            Events.syncModal(model, Modal.lanternFriends);
            break;
          case SET:
            if (!model.getSettings().isSystemProxy()) {
              String msg =
                  "Because you are using manual proxy "
                      + "configuration, you may have to restart your "
                      + "browser for your updated proxied sites list "
                      + "to take effect.";
              model.addNotification(msg, MessageType.info, 30);
              Events.sync(SyncPath.NOTIFICATIONS, model.getNotifications());
            }
            applyJson(json);
            break;
          case SETTINGS:
            log.debug("Processing settings from proxiedSites");
            Events.syncModal(model, Modal.settings);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(resp, "unexpected interaction for proxied sites");
            break;
        }
        break;
      case requestInvite:
        log.info("Processing request invite");
        switch (inter) {
          case CANCEL:
            this.internalState.setModalCompleted(Modal.requestInvite);
            this.internalState.advanceModal(Modal.notInvited);
            break;
          case CONTINUE:
            applyJson(json);
            this.internalState.setModalCompleted(Modal.proxiedSites);
            // TODO: need to do something here
            this.internalState.advanceModal(null);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(resp, "unexpected interaction for request invite");
            break;
        }
        break;
      case requestSent:
        log.debug("Process request sent");
        break;
      case settings:
        switch (inter) {
          case GET:
            log.debug("Setting get mode");
            // Only deal with a mode change if the mode has changed!
            if (modelService.getMode() == Mode.give) {
              // Break this out because it's set in the subsequent
              // setMode call
              final boolean everGet = model.isEverGetMode();
              this.modelService.setMode(Mode.get);
              if (!everGet) {
                // need to do more setup to switch to get mode from
                // give mode
                model.setSetupComplete(false);
                model.setModal(Modal.proxiedSites);
                Events.syncModel(model);
              } else {
                // This primarily just triggers a setup complete event,
                // which triggers connecting to proxies, setting up
                // the local system proxy, etc.
                model.setSetupComplete(true);
              }
            }
            break;
          case GIVE:
            log.debug("Setting give mode");
            this.modelService.setMode(Mode.give);
            break;
          case CLOSE:
            log.debug("Processing settings close");
            Events.syncModal(model, Modal.none);
            break;
          case SET:
            log.debug("Processing set in setting...applying JSON\n{}", json);
            applyJson(json);
            break;
          case RESET:
            log.debug("Processing reset");
            Events.syncModal(model, Modal.confirmReset);
            break;
          case PROXIEDSITES:
            log.debug("Processing proxied sites in settings");
            Events.syncModal(model, Modal.proxiedSites);
            break;
          case LANTERNFRIENDS:
            log.debug("Processing friends in settings");
            Events.syncModal(model, Modal.lanternFriends);
            break;

          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(
                resp, "Interaction not handled for modal: " + modal + " and interaction: " + inter);
            break;
        }
        break;
      case settingsLoadFailure:
        switch (inter) {
          case RETRY:
            modelIo.reload();
            Events.sync(SyncPath.NOTIFICATIONS, model.getNotifications());
            Events.syncModal(model, model.getModal());
            break;
          case RESET:
            backupSettings();
            Events.syncModal(model, Modal.welcome);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            break;
        }
        break;
      case systemProxy:
        this.internalState.setCompletedTo(Modal.systemProxy);
        switch (inter) {
          case CONTINUE:
            log.debug("Processing continue in systemProxy", json);
            applyJson(json);
            Events.sync(SyncPath.SYSTEMPROXY, model.getSettings().isSystemProxy());
            this.internalState.setModalCompleted(Modal.systemProxy);
            this.internalState.advanceModal(null);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(resp, "error setting system proxy pref");
            break;
        }
        break;
      case updateAvailable:
        switch (inter) {
          case CLOSE:
            this.internalState.setModalCompleted(Modal.updateAvailable);
            this.internalState.advanceModal(null);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            break;
        }
        break;
      case authorizeLater:
        log.error("Did not handle interaction {} for modal {}", inter, modal);
        break;
      case confirmReset:
        log.debug("Handling confirm reset interaction");
        switch (inter) {
          case CANCEL:
            log.debug("Processing cancel");
            Events.syncModal(model, Modal.settings);
            break;
          case RESET:
            handleReset();
            Events.syncModel(this.model);
            break;
          default:
            log.error("Did not handle interaction {} for modal {}", inter, modal);
            HttpUtils.sendClientError(
                resp, "Interaction not handled for modal: " + modal + " and interaction: " + inter);
        }
        break;
      case about: // fall through on purpose
      case sponsor:
        switch (inter) {
          case CLOSE:
            Events.syncModal(model, this.internalState.getLastModal());
            break;
          default:
            HttpUtils.sendClientError(resp, "invalid interaction " + inter);
        }
        break;
      case contact:
        switch (inter) {
          case CONTINUE:
            String msg;
            MessageType messageType;
            try {
              lanternFeedback.submit(json, this.model.getProfile().getEmail());
              msg = "Thank you for contacting Lantern.";
              messageType = MessageType.info;
            } catch (Exception e) {
              log.error("Error submitting contact form: {}", e);
              msg = "Error sending message. Please check your " + "connection and try again.";
              messageType = MessageType.error;
            }
            model.addNotification(msg, messageType, 30);
            Events.sync(SyncPath.NOTIFICATIONS, model.getNotifications());
            // fall through because this should be done in both cases:
          case CANCEL:
            Events.syncModal(model, this.internalState.getLastModal());
            break;
          default:
            HttpUtils.sendClientError(resp, "invalid interaction " + inter);
        }
        break;
      case giveModeForbidden:
        if (inter == Interaction.CONTINUE) {
          //  need to do more setup to switch to get mode from give mode
          model.getSettings().setMode(Mode.get);
          model.setSetupComplete(false);
          this.internalState.advanceModal(null);
          Events.syncModal(model, Modal.proxiedSites);
          Events.sync(SyncPath.SETUPCOMPLETE, false);
        }
        break;
      default:
        log.error("No matching modal for {}", modal);
    }
    this.modelIo.write();
  }