/* * Copyright 2000-2001,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jetspeed.services.security; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.servlet.ServletConfig; import org.apache.jetspeed.om.security.JetspeedUser; import org.apache.jetspeed.om.security.JetspeedUserFactory; import org.apache.jetspeed.om.security.UserNamePrincipal; import org.apache.jetspeed.portal.Portlet; import org.apache.jetspeed.services.JetspeedPortalAccessController; import org.apache.jetspeed.services.JetspeedSecurity; import org.apache.jetspeed.services.JetspeedUserManagement; import org.apache.jetspeed.services.logging.JetspeedLogFactoryService; import org.apache.jetspeed.services.logging.JetspeedLogger; import org.apache.jetspeed.services.rundata.JetspeedRunData; import org.apache.turbine.om.security.User; import org.apache.turbine.services.InitializationException; import org.apache.turbine.services.TurbineBaseService; import org.apache.turbine.services.TurbineServices; import org.apache.turbine.services.resources.ResourceService; /** * <p>This is an implementation of the <code>JetspeedSecurityService</code> interface. * * * @author <a href="mailto:david@bluesunrise.com">David Sean Taylor</a> * @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a> * @version $Id: JetspeedDBSecurityService.java,v 1.25 2004/03/31 04:49:10 morciuch Exp $ */ public class JetspeedDBSecurityService extends TurbineBaseService implements JetspeedSecurityService { /** * Static initialization of the logger for this class */ private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedDBSecurityService.class.getName()); private final static String CONFIG_CASEINSENSITIVE_USERNAME = "caseinsensitive.username"; private final static String CONFIG_CASEINSENSITIVE_PASSWORD = "caseinsensitive.password"; private final static String CONFIG_CASEINSENSITIVE_UPPER = "caseinsensitive.upper"; private final static String CONFIG_LOGON_STRIKE_COUNT = "logon.strike.count"; private final static String CONFIG_LOGON_STRIKE_MAX = "logon.strike.max"; private final static String CONFIG_LOGON_STRIKE_INTERVAL = "logon.strike.interval"; private final static String CONFIG_LOGON_AUTO_DISABLE = "logon.auto.disable"; private final static String CONFIG_ACTIONS_ANON_DISABLE = "actions.anon.disable"; private final static String CONFIG_ACTIONS_ALLUSERS_DISABLE = "actions.allusers.disable"; private final static String CONFIG_ACTIONS_ADMIN_ROLES = "admin.roles"; private final static String CONFIG_NEWUSER_ROLES = "newuser.roles"; private final static String CONFIG_DEFAULT_PERMISSION_LOGGEDIN = "permission.default.loggedin"; private final static String CONFIG_DEFAULT_PERMISSION_ANONYMOUS = "permission.default.anonymous"; private final static String CONFIG_ANONYMOUS_USER = "user.anonymous"; private final static String [] DEFAULT_PERMISSIONS = {""}; private final static String [] DEFAULT_CONFIG_NEWUSER_ROLES = { "user" }; private final static String [] DEFAULT_ADMIN_ROLES = { "admin" }; String roles[] = null; boolean caseInsensitiveUsername = false; boolean caseInsensitivePassword = false; boolean caseInsensitiveUpper = true; boolean actionsAnonDisable = true; boolean actionsAllUsersDisable = false; String anonymousUser = "anon"; String[] adminRoles = null; int strikeCount = 3; // 3 within the interval int strikeMax = 20; // 20 total failures long strikeInterval = 300; // five minutes boolean autoLogonDisable = false; private static HashMap users = new HashMap(); private static Object sem = new Object(); /** * This is the early initialization method called by the * Turbine <code>Service</code> framework * @param conf The <code>ServletConfig</code> * @exception throws a <code>InitializationException</code> if the service * fails to initialize */ public synchronized void init(ServletConfig conf) throws InitializationException { // already initialized if (getInit()) return; super.init(conf); // get configuration parameters from Jetspeed Resources ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance()) .getResources(JetspeedSecurityService.SERVICE_NAME); try { roles = serviceConf.getStringArray(CONFIG_NEWUSER_ROLES); adminRoles = serviceConf.getStringArray(CONFIG_ACTIONS_ADMIN_ROLES); } catch (Exception e) {} if (null == roles || roles.length == 0) { roles = DEFAULT_CONFIG_NEWUSER_ROLES; } if (null == adminRoles || adminRoles.length == 0) { adminRoles = DEFAULT_ADMIN_ROLES; } caseInsensitiveUsername = serviceConf.getBoolean(CONFIG_CASEINSENSITIVE_USERNAME, caseInsensitiveUsername); caseInsensitivePassword = serviceConf.getBoolean(CONFIG_CASEINSENSITIVE_PASSWORD, caseInsensitivePassword); caseInsensitiveUpper = serviceConf.getBoolean(CONFIG_CASEINSENSITIVE_UPPER, caseInsensitiveUpper); strikeCount = serviceConf.getInt(CONFIG_LOGON_STRIKE_COUNT, strikeCount); strikeInterval = serviceConf.getLong(CONFIG_LOGON_STRIKE_INTERVAL, strikeInterval); strikeMax = serviceConf.getInt(CONFIG_LOGON_STRIKE_MAX, strikeMax); autoLogonDisable = serviceConf.getBoolean(CONFIG_LOGON_AUTO_DISABLE, autoLogonDisable); actionsAnonDisable = serviceConf.getBoolean(CONFIG_ACTIONS_ANON_DISABLE, actionsAnonDisable); actionsAllUsersDisable = serviceConf.getBoolean(CONFIG_ACTIONS_ALLUSERS_DISABLE, actionsAllUsersDisable); anonymousUser = serviceConf.getString(CONFIG_ANONYMOUS_USER, anonymousUser); // initialization done setInit(true); } ////////////////////////////////////////////////////////////////////////// // // Required JetspeedSecurity Functions // // Required Features provided by default JetspeedSecurity // ////////////////////////////////////////////////////////////////////////// /* * Factory to create a new JetspeedUser, using JetspeedUserFactory. * The class that is created by the default JetspeedUserFactory is configured * in the JetspeedSecurity properties: * * services.JetspeedSecurity.user.class= * org.apache.jetspeed.om.security.BaseJetspeedUser * * @return JetspeedUser a newly created user that implements JetspeedUser. */ public JetspeedUser getUserInstance() { try { return JetspeedUserFactory.getInstance(); } catch (UserException e) { return null; } } ////////////////////////////////////////////////////////////////////////// // // Optional JetspeedSecurity Features // // Features are not required to be implemented by Security Provider // ////////////////////////////////////////////////////////////////////////// /* * During logon, the username can be case sensitive or case insensitive. * * Given a username, converts the username to either lower or upper case. * This optional feature is configurable from the JetspeedSecurity.properties: * * <code>services.JetspeedSecurity.caseinsensitive.username = true/false</code> * <code>services.JetspeedSecurity.caseinsensitive.upper = true/false</code> * * If <code>caseinsensitive.username</code> is true, * then conversion is enabled and the username will be converted before * being sent to the Authentication provider. * * @param username The username to be converted depending on configuration. * @return The converted username. * */ public String convertUserName(String username) { if (caseInsensitiveUsername) { username = (caseInsensitiveUpper) ? username.toUpperCase() : username.toLowerCase(); } return username; } /* * During logon, the password can be case sensitive or case insensitive. * * Given a password, converts the password to either lower or upper case. * This optional feature is configurable from the JetspeedSecurity.properties: * * <code>services.JetspeedSecurity.caseinsensitive.password = true/false</code> * <code>services.JetspeedSecurity.caseinsensitive.upper = true/false</code> * * If <code>caseinsensitive.password</code> is true, * then conversion is enabled and the password will be converted before * being sent to the Authentication provider. * * @param password The password to be converted depending on configuration. * @return The converted password. * */ public String convertPassword(String password) { if (caseInsensitivePassword) { password = (caseInsensitiveUpper) ? password.toUpperCase() : password.toLowerCase(); } return password; } /* * Logon Failure / Account Disabling Feature * * Checks and tracks failed user-logon attempts. * If the user fails to logon after a configurable number of logon attempts, * then the user's account will be disabled. * * This optional feature is configurable from the JetspeedSecurity.properties: * * <code>services.JetspeedSecurity.logon.auto.disable=false</code> * * The example setting below allows for 3 logon strikes per 300 seconds. * When the strike.count is exceeded over the strike.interval, the account * is disabled. The strike.max is the cumulative maximum. * * <code>services.JetspeedSecurity.logon.strike.count=3</code> * <code>services.JetspeedSecurity.logon.strike.interval=300</code> * <code>services.JetspeedSecurity.logon.strike.max=10</code> * * These settings are not persisted, and in a distributed environment are * only tracked per node. * * @param username The username to be checked. * @return True if the strike count reached the maximum threshold and the * user's account was disabled, otherwise False. * */ public boolean checkDisableAccount(String username) { username = convertUserName(username); // TODO: make this work across a cluster of servers UserLogonStats stat = (UserLogonStats)users.get(username); if (stat == null) { stat = new UserLogonStats(username); synchronized (sem) { users.put(username, stat); } } boolean disabled = stat.failCheck(strikeCount, strikeInterval, strikeMax); if (disabled) { try { // disable the account JetspeedUser user = (JetspeedUser)JetspeedSecurity.getUser(username); if (user != null) { user.setDisabled("T"); JetspeedSecurity.saveUser(user); } } catch (Exception e) { logger.error("Could not disable user: " + username, e); } } return disabled; } /* * Logon Failure / Account Disabling Feature * * Returns state of the the logon failure / account disabling feature. * * If the user fails to logon after a configurable number of logon attempts, * then the user's account will be disabled. * * @see JetspeedSecurityService#checkLogonFailures * * @return True if the feature is enabled, false if the feature is disabled. * */ public boolean isDisableAccountCheckEnabled() { return autoLogonDisable; } /* * Logon Failure / Account Disabling Feature * * Resets counters for the logon failure / account disabling feature. * * If the user fails to logon after a configurable number of logon attempts, * then the user's account will be disabled. * * @see JetspeedSecurityService#checkLogonFailures * * @param username The username to reset the logon failure counters. * */ public void resetDisableAccountCheck(String username) { // TODO: make this work across a cluster of servers username = convertUserName(username); UserLogonStats stat = (UserLogonStats)users.get(username); if (stat == null) { stat = new UserLogonStats(username); synchronized (sem) { users.put(username, stat); } } stat.reset(); } ////////////////////////////////////////////////////////////////////////// // // Optional JetspeedSecurity Helpers // ////////////////////////////////////////////////////////////////////////// /** * Helper to UserManagement. * Retrieves a <code>JetspeedUser</code> given the primary principle username. * The principal can be any valid Jetspeed Security Principal: * <code>org.apache.jetspeed.om.security.UserNamePrincipal</code> * <code>org.apache.jetspeed.om.security.UserIdPrincipal</code> * * The security service may optionally check the current user context * to determine if the requestor has permission to perform this action. * * @param username The username principal. * @return a <code>JetspeedUser</code> associated to the principal identity. * @exception UserException when the security provider has a general failure retrieving a user. * @exception UnknownUserException when the security provider cannot match * the principal identity to a user. * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege */ public JetspeedUser getUser(String username) throws JetspeedSecurityException { return JetspeedUserManagement.getUser(new UserNamePrincipal(username)); } /** * Helper to PortalAuthorization. * Gets a <code>JetspeedUser</code> from rundata, authorize user to perform the secured action on * the given <code>Portlet</code> resource. If the user does not have * sufficient privilege to perform the action on the resource, the check returns false, * otherwise when sufficient privilege is present, checkPermission returns true. * * @param rundata request that the user is taken from rundatas * @param action the secured action to be performed on the resource by the user. * @param portlet the portlet resource. * @return boolean true if the user has sufficient privilege. */ public boolean checkPermission(JetspeedRunData runData, String action, Portlet portlet) { return JetspeedPortalAccessController.checkPermission(runData.getJetspeedUser(), portlet, action); } /** * Helper to PortalAuthorization. * Gets a <code>JetspeedUser</code> from rundata, authorize user to perform the secured action on * the given <code>Entry</code> resource. If the user does not have * sufficient privilege to perform the action on the resource, the check returns false, * otherwise when sufficient privilege is present, checkPermission returns true. * * @param rundata request that the user is taken from rundatas * @param action the secured action to be performed on the resource by the user. * @param entry the portal entry resource. * @return boolean true if the user has sufficient privilege. public boolean checkPermission(JetspeedRunData runData, String action, RegistryEntry entry) { return JetspeedPortalAccessController.checkPermission(runData.getJetspeedUser(), entry, action); } */ /* * Security configuration setting to disable all action buttons for the Anon user * This setting is readonly and is edited in the JetspeedSecurity deployment * * * @return True if the feature actions are disabled for the anon user * */ public boolean areActionsDisabledForAnon() { return actionsAnonDisable; } /* * Security configuration setting to disable all action buttons for all users * This setting is readonly and is edited in the JetspeedSecurity deployment * * * @return True if the feature actions are disabled for the all users * */ public boolean areActionsDisabledForAllUsers() { return actionsAllUsersDisable; } /* * Gets the name of the anonymous user account if applicable * * * @return String the name of the anonymous user account * */ public String getAnonymousUserName() { return anonymousUser; } /* * Gets the list of administrative roles * * @return list of admin roles */ public List getAdminRoles() { List result = new ArrayList(); for (int i = 0; i < adminRoles.length; i++) { result.add(adminRoles[i]); } return result; } /** * Returns true if user has administrative role * * @param user * @return true if user has administrative role */ public boolean hasAdminRole(User user) { String username = user.getUserName(); try { List adminRoles = getAdminRoles(); for (Iterator it = adminRoles.iterator(); it.hasNext();) { if (JetspeedSecurity.hasRole(username, (String)it.next())) { return true; } } } catch (Exception e) { logger.error(e); } return false; } }