001: /*
002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/db/CmsLoginManager.java,v $
003: * Date : $Date: 2008-02-27 12:05:42 $
004: * Version: $Revision: 1.9 $
005: *
006: * This library is part of OpenCms -
007: * the Open Source Content Management System
008: *
009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
010: *
011: * This library is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU Lesser General Public
013: * License as published by the Free Software Foundation; either
014: * version 2.1 of the License, or (at your option) any later version.
015: *
016: * This library is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: * Lesser General Public License for more details.
020: *
021: * For further information about Alkacon Software GmbH, please see the
022: * company website: http://www.alkacon.com
023: *
024: * For further information about OpenCms, please see the
025: * project website: http://www.opencms.org
026: *
027: * You should have received a copy of the GNU Lesser General Public
028: * License along with this library; if not, write to the Free Software
029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
030: */
031:
032: package org.opencms.db;
033:
034: import org.opencms.file.CmsObject;
035: import org.opencms.main.OpenCms;
036: import org.opencms.security.CmsAuthentificationException;
037: import org.opencms.security.CmsRole;
038: import org.opencms.security.CmsRoleViolationException;
039: import org.opencms.security.Messages;
040:
041: import java.util.Date;
042: import java.util.Hashtable;
043:
044: /**
045: * Provides functions used to check the validity of a user login.<p>
046: *
047: * Stores invalid login attempts and disables a user account temporarily in case
048: * the configured threshold of invalid logins is reached.<p>
049: *
050: * The invalid login attempt storage operates on a combination of user name, login remote IP address and
051: * user type. This means that a user can be disabled for one remote IP, but still be enabled for
052: * another rempte IP.<p>
053: *
054: * Also allows to temporarily disallow logins (for example in case of maintainance work on the system).<p>
055: *
056: * @author Alexander Kandzior
057: *
058: * @version $Revision: 1.9 $
059: *
060: * @since 6.0.0
061: */
062: public class CmsLoginManager {
063:
064: /**
065: * Contains the data stored for each user in the storage for invalid login attempts.<p>
066: */
067: private class CmsUserData {
068:
069: /** The start time this account was disabled. */
070: private long m_disableTimeStart;
071:
072: /** The count of the failed attempts. */
073: private int m_invalidLoginCount;
074:
075: /**
076: * Creates a new user data instance.<p>
077: */
078: protected CmsUserData() {
079:
080: // a new instance is creted only if there already was one failed attempt
081: m_invalidLoginCount = 1;
082: }
083:
084: /**
085: * Returns the bad attempt count for this user.<p>
086: *
087: * @return the bad attempt count for this user
088: */
089: protected Integer getInvalidLoginCount() {
090:
091: return new Integer(m_invalidLoginCount);
092: }
093:
094: /**
095: * Returns the date this disabled user is released again.<p>
096: *
097: * @return the date this disabled user is released again
098: */
099: protected Date getReleaseDate() {
100:
101: return new Date(m_disableTimeStart + m_disableMillis + 1);
102: }
103:
104: /**
105: * Increases the bad attempt count, disables the data in case the
106: * configured threshold is reached.<p>
107: */
108: protected void increaseInvalidLoginCount() {
109:
110: m_invalidLoginCount++;
111: if (m_invalidLoginCount >= m_maxBadAttempts) {
112: // threshold for bad login attempts has been reached for this user
113: if (m_disableTimeStart == 0) {
114: // only disable in case this user has not already been disabled
115: m_disableTimeStart = System.currentTimeMillis();
116: }
117: }
118: }
119:
120: /**
121: * Returns <code>true</code> in case this user has been temporarily disabled.<p>
122: *
123: * @return <code>true</code> in case this user has been temporarily disabled
124: */
125: protected boolean isDisabled() {
126:
127: if (m_disableTimeStart > 0) {
128: // check if the disable time is already over
129: long currentTime = System.currentTimeMillis();
130: if ((currentTime - m_disableTimeStart) > m_disableMillis) {
131: // disable time is over
132: m_disableTimeStart = 0;
133: }
134: }
135: return m_disableTimeStart > 0;
136: }
137: }
138:
139: /** Default lock time if treshold for bad login attempts is reached. */
140: public static final int DISABLE_MINUTES_DEFAULT = 15;
141:
142: /** Default for bad login attempts. */
143: public static final int MAX_BAD_ATTEMPTS_DEFAULT = 3;
144:
145: /** The milliseconds to disable an account if the threshold is reached. */
146: protected int m_disableMillis;
147:
148: /** The minutes to disable an account if the threshold is reached. */
149: protected int m_disableMinutes;
150:
151: /** The number of bad login attempts allowed before an account is temporarily disabled. */
152: protected int m_maxBadAttempts;
153:
154: /** The storage for the bad login attempts. */
155: protected Hashtable m_storage;
156:
157: /** The login message, setting this may also disable logins for non-Admin users. */
158: private CmsLoginMessage m_loginMessage;
159:
160: /**
161: * Creates a new storage for invalid logins.<p>
162: *
163: * @param disableMinutes the minutes to disable an account if the threshold is reached
164: * @param maxBadAttempts the number of bad login attempts allowed before an account is temporarily disabled
165: */
166: public CmsLoginManager(int disableMinutes, int maxBadAttempts) {
167:
168: m_maxBadAttempts = maxBadAttempts;
169: if (m_maxBadAttempts >= 0) {
170: // otherwise the invalid login storage is sisabled
171: m_disableMinutes = disableMinutes;
172: m_disableMillis = disableMinutes * 60 * 1000;
173: m_storage = new Hashtable();
174: }
175: }
176:
177: /**
178: * Returns the key to use for looking up the user in the invalid login storage.<p>
179: *
180: * @param userName the name of the user
181: * @param remoteAddress the remore address (IP) from which the login attempt was made
182: *
183: * @return the key to use for looking up the user in the invalid login storage
184: */
185: private static String createStorageKey(String userName,
186: String remoteAddress) {
187:
188: StringBuffer result = new StringBuffer();
189: result.append(userName);
190: result.append('_');
191: result.append(remoteAddress);
192: return result.toString();
193: }
194:
195: /**
196: * Checks if the threshold for the invalid logins has been reached for the given user.<p>
197: *
198: * In case the configured threshold is reached, an Exception is thrown.<p>
199: *
200: * @param userName the name of the user
201: * @param remoteAddress the remore address (IP) from which the login attempt was made
202: *
203: * @throws CmsAuthentificationException in case the threshold of invalid login attempts has been reached
204: */
205: public void checkInvalidLogins(String userName, String remoteAddress)
206: throws CmsAuthentificationException {
207:
208: if (m_maxBadAttempts < 0) {
209: // invalid login storage is disabled
210: return;
211: }
212:
213: String key = createStorageKey(userName, remoteAddress);
214: // look up the user in the storage
215: CmsUserData userData = (CmsUserData) m_storage.get(key);
216: if ((userData != null) && (userData.isDisabled())) {
217: // threshold of invalid logins is reached
218: throw new CmsAuthentificationException(Messages.get()
219: .container(
220: Messages.ERR_LOGIN_FAILED_TEMP_DISABLED_4,
221: new Object[] { userName, remoteAddress,
222: userData.getReleaseDate(),
223: userData.getInvalidLoginCount() }));
224: }
225: }
226:
227: /**
228: * Checks if a login is currently allowed.<p>
229: *
230: * In case no logins are allowed, an Exception is thrown.<p>
231: *
232: * @throws CmsAuthentificationException in case no logins are allowed
233: */
234: public void checkLoginAllowed() throws CmsAuthentificationException {
235:
236: if ((m_loginMessage != null)
237: && (m_loginMessage.isLoginCurrentlyForbidden())) {
238: // login message has been set and is active
239: throw new CmsAuthentificationException(Messages.get()
240: .container(
241: Messages.ERR_LOGIN_FAILED_WITH_MESSAGE_1,
242: m_loginMessage.getMessage()));
243: }
244: }
245:
246: /**
247: * Returns the minutes an account gets disabled after too many failed login attempts.<p>
248: *
249: * @return the minutes an account gets disabled after too many failed login attempts
250: */
251: public int getDisableMinutes() {
252:
253: return m_disableMinutes;
254: }
255:
256: /**
257: * Returns the current login message that is displayed if a user logs in.<p>
258: *
259: * if <code>null</code> is returned, no login message has been currently set.<p>
260: *
261: * @return the current login message that is displayed if a user logs in
262: */
263: public CmsLoginMessage getLoginMessage() {
264:
265: return m_loginMessage;
266: }
267:
268: /**
269: * Returns the number of bad login attempts allowed before an account is temporarily disabled.<p>
270: *
271: * @return the number of bad login attempts allowed before an account is temporarily disabled
272: */
273: public int getMaxBadAttempts() {
274:
275: return m_maxBadAttempts;
276: }
277:
278: /**
279: * Removes the current login message.<p>
280: *
281: * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p>
282: *
283: * @param cms the current OpenCms user context
284: *
285: * @throws CmsRoleViolationException in case the current user does not have the required role permissions
286: */
287: public void removeLoginMessage(CmsObject cms)
288: throws CmsRoleViolationException {
289:
290: OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN);
291: m_loginMessage = null;
292: }
293:
294: /**
295: * Sets the login message to display if a user logs in.<p>
296: *
297: * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p>
298: *
299: * @param cms the current OpenCms user context
300: * @param message the message to set
301: *
302: * @throws CmsRoleViolationException in case the current user does not have the required role permissions
303: */
304: public void setLoginMessage(CmsObject cms, CmsLoginMessage message)
305: throws CmsRoleViolationException {
306:
307: if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
308: // during configuration phase no permission check id required
309: OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN);
310: }
311: m_loginMessage = message;
312: if (m_loginMessage != null) {
313: m_loginMessage.setFrozen();
314: }
315: }
316:
317: /**
318: * Adds an invalid attempt to login for the given user / IP to the storage.<p>
319: *
320: * In case the configured threshold is reached, the user is disabled for the configured time.<p>
321: *
322: * @param userName the name of the user
323: * @param remoteAddress the remore address (IP) from which the login attempt was made
324: */
325: protected void addInvalidLogin(String userName, String remoteAddress) {
326:
327: if (m_maxBadAttempts < 0) {
328: // invalid login storage is disabled
329: return;
330: }
331:
332: String key = createStorageKey(userName, remoteAddress);
333: // look up the user in the storage
334: CmsUserData userData = (CmsUserData) m_storage.get(key);
335: if (userData != null) {
336: // user data already contained in storage
337: userData.increaseInvalidLoginCount();
338: } else {
339: // create an new data object for this user
340: userData = new CmsUserData();
341: m_storage.put(key, userData);
342: }
343: }
344:
345: /**
346: * Removes all invalid attempts to login for the given user / IP.<p>
347: *
348: * @param userName the name of the user
349: * @param remoteAddress the remore address (IP) from which the login attempt was made
350: */
351: protected void removeInvalidLogins(String userName,
352: String remoteAddress) {
353:
354: if (m_maxBadAttempts < 0) {
355: // invalid login storage is disabled
356: return;
357: }
358:
359: String key = createStorageKey(userName, remoteAddress);
360: // just remove the user from the storage
361: m_storage.remove(key);
362: }
363: }
|