@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; }