All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.security.CmsPersistentLoginTokenHandler Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 17.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.security;

import org.opencms.file.CmsObject;
import org.opencms.file.CmsUser;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.util.CmsStringUtil;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.logging.Log;

import com.google.common.collect.Lists;

/**
 * Creates and validates persisten login tokens for users.

* * When a token is created for a user, a special additional info item is stored on the user, such that * the token uniquely identifies that info item. The value of the info item is the expiration date of the token. * A token is validated by looking up the additional info item for the user and checking whether the token is still valid * according to the stored expiration date.

*/ public class CmsPersistentLoginTokenHandler { /** * Bean representing the data encoded in a login token (user name and key).

*/ public static class Token { /** Separator to use for the encoded token string. */ public static final String SEPARATOR = "|"; /** The key. */ private String m_key; /** The name. */ private String m_name; /** * Creates a new token object from the encoded representation.

* * @param token a string containing the token data */ public Token(String token) { if (token != null) { List parts = CmsStringUtil.splitAsList(token, SEPARATOR); if (parts.size() == 2) { m_name = decodeName(parts.get(0)); m_key = parts.get(1); } } } /** * Creates a token object from the given name and key.

* * @param name the name * @param key the key */ public Token(String name, String key) { m_name = name; m_key = key; } /** * Gets the encoded token string representation.

* * @return the token string */ public String encode() { return encodeName(m_name) + SEPARATOR + m_key; } /** * Gets the additional info key to use for this token.

* * @return the additional info key */ public String getAdditionalInfoKey() { return KEY_PREFIX + m_key; } /** * Gets the key for this token.

* * @return the key */ public String getKey() { return m_key; } /** * Gets the user name for this token.

* * @return the user name */ public String getName() { return m_name; } /** * Checks if this token is valid.

* * @return true if this token is valid */ public boolean isValid() { return (m_name != null) && (m_key != null); } /** * Decodes the user name from a hexadecimal string, and returns null if this is not possible.

* * @param nameHex the encoded name * @return the decoded name */ @SuppressWarnings("synthetic-access") private String decodeName(String nameHex) { try { return new String(Hex.decodeHex(nameHex.toCharArray()), "UTF-8"); } catch (Exception e) { LOG.warn(e.getLocalizedMessage(), e); return null; } } /** * Encodes a user name as a hex string for storing in the cookie.

* * @param name the user name * * @return the encoded name */ private String encodeName(String name) { try { return Hex.encodeHexString(name.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { // shouldn't happen throw new IllegalStateException("UTF8 not supported"); } } } /** Default token lifetime. */ public static final long DEFAULT_LIFETIME = 1000 * 60 * 60 * 8; /** Prefix used for the keys for the additional infos this class creates. */ public static final String KEY_PREFIX = "logintoken_"; /** The logger for this class. */ private static final Log LOG = CmsLog.getLog(CmsPersistentLoginTokenHandler.class); /** Admin CMS context. */ private static CmsObject m_adminCms; /** The lifetime for created tokens. */ private long m_lifetime = DEFAULT_LIFETIME; /** * Creates a new instance.

*/ public CmsPersistentLoginTokenHandler() { // Default constructor, do nothing } /** * Static method used to give this class access to an admin cms context.

* * @param adminCms the admin cms context to set */ public static void setAdminCms(CmsObject adminCms) { if (m_adminCms == null) { m_adminCms = adminCms; } } /** * Generates a new login token for a given user and registers the token in the user's additional info.

* * @param cms the CMS context for which to create a new token * @return the generated token * * @throws CmsException if something goes wrong */ public String createToken(CmsObject cms) throws CmsException { CmsUser user = cms.getRequestContext().getCurrentUser(); String key = RandomStringUtils.randomAlphanumeric(16); Token tokenObj = new Token(user.getName(), key); String token = tokenObj.encode(); String addInfoKey = tokenObj.getAdditionalInfoKey(); String value = "" + (System.currentTimeMillis() + m_lifetime); user.getAdditionalInfo().put(addInfoKey, value); removeExpiredTokens(user, System.currentTimeMillis()); LOG.info("Generated token for user " + user.getName() + " using key " + key); m_adminCms.writeUser(user); return token; } /** * Invalidates all tokens for the given user.

* * @param user the user * @param token the token string * * @throws CmsException if something goes wrong */ public void invalidateToken(CmsUser user, String token) throws CmsException { Token tokenObj = new Token(token); if (tokenObj.isValid()) { String addInfoKey = tokenObj.getAdditionalInfoKey(); if (null != user.getAdditionalInfo().remove(addInfoKey)) { m_adminCms.writeUser(user); } } } /** * Removes expired tokens from the user's additional infos.

* * This method does not write the user back to the database. * * @param user the user for which to remove the additional infos * @param now the current time */ public void removeExpiredTokens(CmsUser user, long now) { List toRemove = Lists.newArrayList(); for (Map.Entry entry : user.getAdditionalInfo().entrySet()) { String key = entry.getKey(); if (key.startsWith(KEY_PREFIX)) { try { long expiry = Long.parseLong((String)entry.getValue()); if (expiry < now) { toRemove.add(key); } } catch (NumberFormatException e) { toRemove.add(key); } } } LOG.info("Removing " + toRemove.size() + " expired tokens for user " + user.getName()); for (String removeKey : toRemove) { user.getAdditionalInfo().remove(removeKey); } } /** * Sets the token lifetime.

* * @param duration the number of milliseconds for which the token should be valid */ public void setTokenLifetime(long duration) { m_lifetime = duration; } /** * Validates a token and returns the matching user for which the token is valid.

* * Returns null if no user matching the token is found, or if the token for the user is expired * * @param tokenString the token for which to find the matching user * * @return the matching user for the token, or null if no matching user was found or the token is expired */ public CmsUser validateToken(String tokenString) { if (CmsStringUtil.isEmpty(tokenString)) { return null; } Token token = new Token(tokenString); if (!token.isValid()) { LOG.warn("Invalid token: " + tokenString); return null; } String name = token.getName(); String key = token.getKey(); String logContext = "[user=" + name + ",key=" + key + "] "; try { CmsUser user = m_adminCms.readUser(name); String infoKey = token.getAdditionalInfoKey(); String addInfoValue = (String)user.getAdditionalInfo().get(infoKey); logContext = logContext + "[value=" + addInfoValue + "]"; if (addInfoValue == null) { LOG.warn(logContext + " no matching additional info value found"); return null; } try { long expirationDate = Long.parseLong(addInfoValue); if (System.currentTimeMillis() > expirationDate) { LOG.warn(logContext + "Login token expired"); user.getAdditionalInfo().remove(infoKey); try { m_adminCms.writeUser(user); } catch (Exception e) { LOG.error(e.getLocalizedMessage(), e); } return null; } } catch (NumberFormatException e) { LOG.warn(logContext + "Invalid format for login token additional info"); return null; } return user; } catch (Exception e) { LOG.warn(logContext + "error validating token", e); return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy