@Override
  @SuppressWarnings("unchecked")
  public User getUserObject(JsonSessionState session) {
    logger.trace("getUserObject");
    List<String> un = (List<String>) session.get(SHIB_USER_NAME);
    List<String> rn = (List<String>) session.get(SHIB_COMMON_NAME);
    if (un != null) {
      if (rn == null) {
        rn = un;
      }
      if (un.size() > 1) {
        logger.warn(
            String.format(
                "More than one username was retrieved: %s using the first: %s", un, un.get(0)));
      }
      if (rn.size() > 1 && rn != un) {
        logger.warn(
            String.format(
                "More than one real name was retrieved: %s using the first: %s", un, un.get(0)));
      }

      ShibbolethUser user = new ShibbolethUser(un.get(0), rn.get(0));
      Object tmp;
      for (String s : SHIB_ATTRIBUTES) {
        tmp = session.get(s);
        if (!s.equals(SHIB_USER_NAME) && !s.equals(SHIB_COMMON_NAME) && tmp instanceof List) {
          user.set(s, join(SHIB_ATTRIBUTE_DELIMITER, (List<String>) tmp));
        }
      }
      user.setSource(SHIBBOLETH_PLUGIN_ID);
      return user;
    }
    return null;
  }
  /**
   * Validate the provided trust token.
   *
   * @param token : The token to validate
   * @return boolean : True if the token is valid, False otherwise
   */
  @Override
  public boolean testTrustToken(JsonSessionState session, String token) {
    String[] parts = StringUtils.split(token, ":");

    // Check the length
    if (parts.length != 4) {
      log.error("TOKEN: Should have 4 parts, not {} : '{}'", parts.length, token);
      return false;
    }

    // Check the parts
    String username = parts[0];
    String timestamp = parts[1];
    String publicKey = parts[2];
    String userToken = parts[3];
    if (username.isEmpty() || timestamp.isEmpty() || publicKey.isEmpty() || userToken.isEmpty()) {
      log.error("TOKEN: One or more parts are empty : '{}'", token);
      return false;
    }

    // Make sure the publicKey is valid
    if (!tokens.containsKey(publicKey)) {
      log.error("TOKEN: Invalid public key : '{}'", publicKey);
      return false;
    }
    String privateKey = tokens.get(publicKey);
    Long expiry = tokenExpiry.get(publicKey);

    // Check for valid timestamp & expiry
    timestamp = getFormattedTime(timestamp);
    if (timestamp == null) {
      log.error("TOKEN: Invalid timestamp : '{}'", timestamp);
      return false;
    }
    Long tokenTime = Long.valueOf(timestamp);
    Long currentTime = Long.valueOf(getFormattedTime(null));
    Long age = currentTime - tokenTime;
    if (age > expiry) {
      log.error("Token is passed its expiry : {}s old", age);
      return false;
    }

    // Now validate the token itself
    String tokenSeed = username + ":" + timestamp + ":" + privateKey;
    String expectedToken = DigestUtils.md5Hex(tokenSeed);
    if (userToken.equals(expectedToken)) {
      // The token is valid
      session.set("username", username);
      session.set("source", TRUST_TOKEN_PREFIX + publicKey);
      // Store it in case we redirect later
      session.set("validToken", token);
      return true;
    }

    // Token was not valid
    log.error("TOKEN: Invalid token, hash does not match: '{}'", userToken);
    return false;
  }
 @Override
 public void ssoPrepareLogin(JsonSessionState session, String returnAddress, String server)
     throws Exception {
   logger.trace(
       String.format("ssoPrepareLogin, Return Address: %s Server: %s", returnAddress, server));
   session.set(RETURN_ADDRESS, returnAddress);
 }
 @Override
 public void logout(JsonSessionState session) {
   logger.trace("Logout Requested");
   for (String shibKey : SHIB_ATTRIBUTES) {
     session.remove(shibKey);
     logger.trace(String.format("Removing Shibboleth Attribute: %s from user.", shibKey));
   }
 }
 @SuppressWarnings("unchecked")
 private void addAttr(String key, String value, JsonSessionState session) {
   Object o = session.get(key);
   List<String> l;
   if (o == null) {
     session.set(key, l = new ArrayList<String>());
   } else {
     l = (List<String>) o;
   }
   List<String> vals;
   if (SHIB_ATTRIBUTE_DELIMITER != null) {
     vals = Arrays.asList(value.split(SHIB_ATTRIBUTE_DELIMITER));
     logger.trace(String.format("Adding: %s : %s", key, vals));
   } else {
     vals = new ArrayList<String>();
     vals.add(value);
   }
   l.addAll(vals);
 }
  /**
   * Retrieve the details of a user by username
   *
   * @param username The username of a user to retrieve
   * @param source The authentication source if known
   * @return User The user requested
   * @throws AuthenticationException if any errors occur
   */
  @Override
  public User getUser(JsonSessionState session, String username, String source)
      throws AuthenticationException {
    // Sanity check
    if (username == null || username.equals("") || source == null || source.equals("")) {
      throw new AuthenticationException("Invalid user data requested");
    }

    // SSO Users
    if (sso.containsKey(source)) {
      GenericUser user = (GenericUser) sso.get(source).getUserObject(session);
      // Sanity check our data
      if (user == null || !user.getUsername().equals(username)) {
        throw new AuthenticationException("Unknown user '" + username + "'");
      }
      return user;
    }

    // Trust token users
    if (source.startsWith(TRUST_TOKEN_PREFIX)) {
      String sUsername = (String) session.get("username");
      String sSource = (String) session.get("source");

      // We can't lookup token users so it must match
      if (sUsername == null
          || !username.equals(sUsername)
          || sSource == null
          || !source.equals(sSource)) {
        throw new AuthenticationException("Unknown user '" + username + "'");
      }

      // Seems valid, create a basic user object and return
      GenericUser user = new GenericUser();
      user.setUsername(username);
      user.setSource(source);
      return user;
    }

    // Standard users
    authManager.setActivePlugin(source);
    return authManager.getUser(username);
  }
  /**
   * Logout the provided user
   *
   * @return user The user to logout
   */
  @Override
  public void logout(JsonSessionState session, User user) throws AuthenticationException {
    String source = user.getSource();

    // Clear session
    session.remove("username");
    session.remove("source");

    // SSO Users
    if (sso.containsKey(source)) {
      sso.get(source).logout(session);
      return;
    }

    // Trust token users
    if (source.startsWith(TRUST_TOKEN_PREFIX)) {
      session.remove("validToken");
      return;
    }

    // Standard users
    authManager.logOut(user);
  }
  /**
   * Get user details from SSO connection and set them in the user session.
   *
   * @return boolean: Flag whether a user was actually logged in or not.
   */
  @Override
  public boolean ssoCheckUserDetails(JsonSessionState session) {
    // After the SSO roun-trip, restore any old query parameters we lost
    List<String> currentParams = request.getParameterNames();
    // Cast a copy of keySet() to array to avoid errors as we modify
    String[] oldParams = session.keySet().toArray(new String[0]);
    // Loop through session data...
    for (String key : oldParams) {
      // ... looking for SSO stored params
      if (key.startsWith(SSO_STORAGE_PREFIX)) {
        // Remove our prefix...
        String oldParam = key.replace(SSO_STORAGE_PREFIX, "");
        // ... and check if it survived the trip
        if (!currentParams.contains(oldParam)) {
          // No it didn't, add it to form data... the parameters are
          // already accessible from there in Jython
          String data = (String) session.get(key);
          formData.set(oldParam, data);
          // Don't forget to clear it from the session
          session.remove(key);
        }
      }
    }

    // Check our SSO providers for valid logins
    for (String ssoId : sso.keySet()) {
      sso.get(ssoId).ssoCheckUserDetails(session);
      GenericUser user = (GenericUser) sso.get(ssoId).getUserObject(session);
      if (user != null) {
        session.set("username", user.getUsername());
        session.set("source", ssoId);
        return true;
      }
    }
    return false;
  }
  @Override
  public void ssoInit(JsonSessionState session, HttpServletRequest request) throws Exception {
    logger.trace(String.format("ssoInit, URL: %s", session.get("ssoPortalUrl")));

    if (logger.isTraceEnabled()) {
      logger.trace("Available Attributes:");
      Enumeration<String> attrs = request.getAttributeNames();
      String name;
      logger.trace("\n");
      while (attrs.hasMoreElements()) {
        name = attrs.nextElement();
        logger.trace(String.format("\t\t%s: %s", name, request.getAttribute(name)));
      }
      logger.trace("\n");
      logger.trace("Available Headers:");
      attrs = request.getHeaderNames();
      while (attrs.hasMoreElements()) {
        name = attrs.nextElement();
        logger.trace(String.format("\t\t%s: %s", name, request.getHeader(name)));
      }
      logger.trace("\n");
    }

    Object tmp;
    for (String key : SHIB_ATTRIBUTES) {
      tmp = request.getAttribute(key);
      if (tmp != null) {
        addAttr(key, tmp.toString(), session);
      }
      if (SHIB_USE_HEADERS) {
        tmp = request.getHeader(key);
        if (tmp != null) {
          addAttr(key, tmp.toString(), session);
        }
      }
    }
  }
 @Override
 public String ssoGetRemoteLogonURL(JsonSessionState session) {
   logger.trace("ssoGetRemoteLogonURL");
   return (String) session.get(RETURN_ADDRESS);
 }
  /**
   * Given the provided resource, test whether SSO should be 'aware' of this resource. 'Aware'
   * resources are valid return return points after SSO redirects, so the test should return false
   * on (for examples) static resources and utilities such as atom feeds.
   *
   * @param session : The session for this request
   * @param resource : The name of the resource being accessed
   * @param uri : The full URI of the resource if simple matches fail
   * @return boolean : True if SSO should be evaluated, False otherwise
   */
  @Override
  public boolean testForSso(JsonSessionState session, String resource, String uri) {
    // The URL parameters can request forced SSO to this URL
    String ssoId = request.getParameter("ssoId");
    if (ssoId != null) {
      return true;
    }

    // The URL parameters can contain a trust token
    String utoken = request.getParameter("token");
    String stoken = (String) session.get("validToken");
    if (utoken != null || stoken != null) {
      return true;
    }

    // Test for resources that start with unwanted values
    for (String test : excStarts) {
      if (resource.startsWith(test)) {
        return false;
      }
    }

    // Test for resources that end with unwanted values
    for (String test : excEnds) {
      if (resource.endsWith(test)) {
        return false;
      }
    }

    // Test for resources that equal unwanted values
    for (String test : excEquals) {
      if (resource.equals(test)) {
        return false;
      }
    }

    // Detail screen - specific payload target
    // This is an edge case, where the payload was a deep link,
    // it's not a subpage we can ignore
    String returnAddress = (String) session.get("returnAddress");
    if (returnAddress != null && returnAddress.endsWith(uri)) {
      return true;
    }

    // The detail screen generates a lot of background calls to the server
    if (resource.equals("detail") || resource.equals("download") || resource.equals("preview")) {
      // Now check for the core page
      if (resource.equals("detail")) {
        if (detailPattern == null) {
          detailPattern = Pattern.compile("detail/\\w+/*$");
        }
        Matcher matcher = detailPattern.matcher(uri);
        if (matcher.find()) {
          // This is actually the 'core' detail page
          return true;
        }
      }

      // This is just a subpage
      return false;
    }

    // Every other page
    return true;
  }
  /**
   * Initialize the SSO Service, prepare a login if required
   *
   * @param session The server session data
   * @throws Exception if any errors occur
   */
  @Override
  public String ssoInit(JsonSessionState session) throws Exception {
    // Keep track of the user switching portals for
    // link building in other methods
    String portalId = (String) session.get("portalId", defaultPortal);
    ssoLoginUrl = serverUrlBase + portalId + SSO_LOGIN_PAGE;

    // Find out what page we are on
    String path = request.getAttribute("RequestURI").toString();
    String currentAddress = serverUrlBase + path;

    // Store the portal URL, might be required by implementers to build
    // an interface (images etc).
    session.set("ssoPortalUrl", serverUrlBase + portalId);

    // Makes sure all SSO plugins get initialised
    for (String ssoId : sso.keySet()) {
      sso.get(ssoId).ssoInit(session, rg.getHTTPServletRequest());
    }

    // Are we logging in right now?
    String ssoId = request.getParameter("ssoId");

    // If this isn't the login page...
    if (!currentAddress.contains(SSO_LOGIN_PAGE)) {
      // Store the current address for use later
      session.set("returnAddress", currentAddress);
      // We might still be logging in from a deep link
      if (ssoId == null) {
        // No we're not, finished now
        return null;
      } else {
        // Yes it's a deep link, store any extra query params
        // since they probably won't survive the round-trip
        // through SSO.
        for (String param : request.getParameterNames()) {
          if (!param.equals("ssoId")) {
            // Store all the other parameters
            session.set(SSO_STORAGE_PREFIX + param, request.getParameter(param));
          }
        }
      }
    }

    // Get the last address to return the user to
    String returnAddress = (String) session.get("returnAddress");
    if (returnAddress == null) {
      // Or use the home page
      returnAddress = serverUrlBase + portalId + "/home";
    }

    // Which SSO provider did the user request?
    if (ssoId == null) {
      log.error("==== SSO: SSO ID not found!");
      return null;
    }
    if (!sso.containsKey(ssoId)) {
      log.error("==== SSO: SSO ID invalid: '{}'!", ssoId);
      return null;
    }

    // The main event... finally
    sso.get(ssoId).ssoPrepareLogin(session, returnAddress, serverUrlBase);
    return ssoId;
  }
  /**
   * Wrapper method for other SSO methods provided by the security manager. If desired, the security
   * manager can take care of the integration using the default usage pattern, rather then calling
   * them individually.
   *
   * @param session : The session of the current request
   * @param formData : FormData object for the current request
   * @return boolean : True if SSO has redirected, in which case no response should be sent by
   *     Dispatch, otherwise False.
   */
  @Override
  public boolean runSsoIntegration(JsonSessionState session, FormData formData) {
    this.formData = formData;

    // Used in integrating with thrid party systems. They can send us a
    // user, we will log them in via a SSO round-trip, then send them back
    // to the external system
    String returnUrl = request.getParameter("returnUrl");
    if (returnUrl != null) {
      log.info("External redirect requested: '{}'", returnUrl);
      session.set("ssoReturnUrl", returnUrl);
    }

    // The URL parameters can contain a trust token
    String utoken = request.getParameter("token");
    String stoken = (String) session.get("validToken");
    String token = null;
    // Or an 'old' token still in the session
    if (stoken != null) {
      token = stoken;
    }
    // But give the URL priority
    if (utoken != null) {
      token = utoken;
    }
    if (token != null) {
      // Valid token
      if (testTrustToken(session, token)) {
        // Dispatch can continue
        return false;
      }

      // Invalid token
      // Given that trust tokens are designed for system integration
      // we are going to fail with a non-branded error message
      try {
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid or expired security token!");
      } catch (IOException ex) {
        log.error("Error sending 403 response to client!");
      }
      // We don't want Dispatch to send a response
      return true;
    }

    // Single Sign-On integration
    try {
      // Instantiate with access to the session
      String ssoId = ssoInit(session);
      if (ssoId != null) {
        // We are logging in, so send them to the SSO portal
        String ssoUrl = ssoGetRemoteLogonURL(session, ssoId);
        if (ssoUrl != null) {
          log.info("Redirect to external URL: '{}'", ssoUrl);
          response.sendRedirect(ssoUrl);
          return true;
        }
      } else {
        // Otherwise, check if we have user's details
        boolean valid = ssoCheckUserDetails(session);
        // If we validly logged in an SSO user, check for an
        // external redirect to third party systems
        if (valid) {
          returnUrl = (String) session.get("ssoReturnUrl");
          if (returnUrl != null) {
            log.info("Redirect to external URL: '{}'", returnUrl);
            session.remove("ssoReturnUrl");
            response.sendRedirect(returnUrl);
            return true;
          }
        }
      }
    } catch (Exception ex) {
      log.error("SSO Error!", ex);
    }

    return false;
  }