Author: jalkanen
Date: Wed May 21 13:17:36 2008
New Revision: 658843
URL: http://svn.apache.org/viewvc?rev=658843&view=rev
Log:
JSPWIKI-44: Added a delay between login attempts.
Added:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/TimedCounterList.java
incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/TimedCounterListTest.java
Modified:
incubator/jspwiki/trunk/ChangeLog
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/Release.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/PriorityList.java
incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/AllTests.java
Modified: incubator/jspwiki/trunk/ChangeLog
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/ChangeLog?rev=658843&r1=658842&r2=658843&view=diff
==============================================================================
--- incubator/jspwiki/trunk/ChangeLog (original)
+++ incubator/jspwiki/trunk/ChangeLog Wed May 21 13:17:36 2008
@@ -1,5 +1,12 @@
2008-05-11 Janne Jalkanen <[EMAIL PROTECTED]>
+ * 2.7.0-svn-27
+
+ * [JSPWIKI-44]. AuthenticationManager now remembers the last logins
+ and slows down repeated access attempts, to a maximum of 20 seconds.
+
+2008-05-11 Janne Jalkanen <[EMAIL PROTECTED]>
+
* 2.7.0-svn-26
* [JSPWIKI-236]: Fixed localization for the different login/edit
Modified: incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/Release.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/Release.java?rev=658843&r1=658842&r2=658843&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/Release.java (original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/Release.java Wed May 21
13:17:36 2008
@@ -77,7 +77,7 @@
* <p>
* If the build identifier is empty, it is not added.
*/
- public static final String BUILD = "26";
+ public static final String BUILD = "27";
/**
* This is the generic version string you should use
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java?rev=658843&r1=658842&r2=658843&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
(original)
+++
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
Wed May 21 13:17:36 2008
@@ -46,17 +46,30 @@
import com.ecyrd.jspwiki.event.WikiEventListener;
import com.ecyrd.jspwiki.event.WikiEventManager;
import com.ecyrd.jspwiki.event.WikiSecurityEvent;
+import com.ecyrd.jspwiki.util.TimedCounterList;
/**
* Manages authentication activities for a WikiEngine: user login, logout, and
* credential refreshes. This class uses JAAS to determine how users log in.
+ * <p>
+ * The login procedure is protected in addition by a mechanism which prevents
+ * a hacker to try and force-guess passwords by slowing down attempts to log in
+ * into the same account. Every login attempt is recorded, and stored for a
while
+ * (currently ten minutes), and each login attempt during that time incurs a
penalty
+ * of 2^login attempts milliseconds - that is, 10 login attempts incur a login
penalty of 1.024 seconds.
+ * The delay is currently capped to 20 seconds.
+ *
* @author Andrew Jaquith
- * @author Janne Jalkanen
* @author Erik Bunn
* @since 2.3
*/
public final class AuthenticationManager
{
+ /** How many milliseconds the logins are stored before they're cleaned
away. */
+ private static final long LASTLOGINS_CLEANUP_TIME = 10*60*1000L; // Ten
minutes
+
+ private static final long MAX_LOGIN_DELAY = 20*1000L; // 20 seconds
+
/** The name of the built-in cookie assertion module */
public static final String COOKIE_MODULE =
CookieAssertionLoginModule.class.getName();
@@ -132,6 +145,10 @@
private boolean m_useJAAS = true;
+ /** Keeps a list of the usernames who have attempted a login recently. */
+
+ private TimedCounterList<String> m_lastLoginAttempts = new
TimedCounterList<String>();
+
/**
* Creates an AuthenticationManager instance for the given WikiEngine and
* the specified set of properties. All initialization for the modules is
@@ -337,6 +354,8 @@
return false;
}
+ delayLogin(username);
+
UserManager userMgr = m_engine.getUserManager();
CallbackHandler handler = new WikiCallbackHandler(
userMgr.getUserDatabase(),
@@ -356,6 +375,34 @@
}
return false;
}
+
+ /**
+ * This method builds a database of login names that are being attempted,
and will try to
+ * delay if there are too many requests coming in for the same username.
+ * <p>
+ * The current algorithm uses 2^loginattempts as the delay in
milliseconds, i.e.
+ * at 10 login attempts it'll add 1.024 seconds to the login.
+ *
+ * @param username The username that is being logged in
+ */
+ private void delayLogin( String username )
+ {
+ try
+ {
+ m_lastLoginAttempts.cleanup( LASTLOGINS_CLEANUP_TIME );
+ int count = m_lastLoginAttempts.count( username );
+
+ long delay = Math.min( 1<<count, MAX_LOGIN_DELAY );
+ log.debug( "Sleeping for "+delay+" ms to allow login." );
+ Thread.sleep( delay );
+
+ m_lastLoginAttempts.add( username );
+ }
+ catch( InterruptedException e )
+ {
+ // FALLTHROUGH is fine
+ }
+ }
/**
* Logs the user out by retrieving the WikiSession associated with the
Modified: incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/PriorityList.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/PriorityList.java?rev=658843&r1=658842&r2=658843&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/PriorityList.java
(original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/PriorityList.java Wed
May 21 13:17:36 2008
@@ -31,8 +31,6 @@
* <p>
* Priority is an integer, and the list is sorted in descending order
* (that is, 100 is before 10 is before 0 is before -40).
- *
- * @author Janne Jalkanen
*/
public class PriorityList
extends AbstractList
Added: incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/TimedCounterList.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/TimedCounterList.java?rev=658843&view=auto
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/TimedCounterList.java
(added)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/util/TimedCounterList.java
Wed May 21 13:17:36 2008
@@ -0,0 +1,230 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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 com.ecyrd.jspwiki.util;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Provides a List in which all items store their addition time. This
+ * can then be used to clean the list from old items.
+ * <p>
+ * This class is thread-safe - all modifications are blocking, but
+ * reading is non-blocking (unless a write is ongoing).
+ *
+ * @param <T> The class you wish to store here
+ */
+public class TimedCounterList<T> extends AbstractList<T>
+{
+ private ArrayList<CounterItem<T>> m_list = new ArrayList<CounterItem<T>>();
+ private ReadWriteLock m_lock = new ReentrantReadWriteLock();
+
+ @Override
+ public T set( int index, T element )
+ {
+ m_lock.writeLock().lock();
+
+ T t;
+
+ try
+ {
+ t = m_list.set(index,new CounterItem<T>(element)).m_obj;
+ }
+ finally
+ {
+ m_lock.writeLock().unlock();
+ }
+
+ return t;
+ }
+
+ @Override
+ public T get( int index )
+ {
+ m_lock.readLock().lock();
+
+ T t;
+
+ try
+ {
+ t = m_list.get(index).m_obj;
+ }
+ finally
+ {
+ m_lock.readLock().unlock();
+ }
+
+ return t;
+ }
+
+ @Override
+ public int size()
+ {
+ m_lock.readLock().lock();
+ int size = 0;
+
+ try
+ {
+ size = m_list.size();
+ }
+ finally
+ {
+ m_lock.readLock().unlock();
+ }
+
+ return size;
+ }
+
+
+ @Override
+ public void add( int index, T element )
+ {
+ m_lock.writeLock().lock();
+
+ try
+ {
+ m_list.add(index, new CounterItem<T>(element));
+ }
+ finally
+ {
+ m_lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public T remove( int index )
+ {
+ m_lock.writeLock().lock();
+ T t;
+
+ try
+ {
+ t = m_list.remove( index ).m_obj;
+ }
+ finally
+ {
+ m_lock.writeLock().unlock();
+ }
+
+ return t;
+ }
+
+ /**
+ * Returns the count how many times this object is available in
+ * this list, using equals().
+ *
+ * @param obj
+ * @return
+ */
+ public int count( T obj )
+ {
+ int c = 0;
+ m_lock.readLock().lock();
+
+ try
+ {
+ for( CounterItem i : m_list )
+ {
+ if( i.m_obj.equals( obj ) )
+ {
+ c++;
+ }
+ }
+ }
+ finally
+ {
+ m_lock.readLock().unlock();
+ }
+
+ return c;
+ }
+
+ /**
+ * Performs a cleanup of all items older than maxage.
+ *
+ * @param maxage The maximum age in milliseconds after an item is removed.
+ */
+ public void cleanup( long maxage )
+ {
+ m_lock.writeLock().lock();
+
+ try
+ {
+ long now = System.currentTimeMillis();
+
+ for( Iterator<CounterItem<T>> i = m_list.iterator(); i.hasNext(); )
+ {
+ CounterItem<T> ci = i.next();
+
+ long age = now - ci.m_addTime;
+
+ if( age > maxage )
+ {
+ i.remove();
+ }
+ }
+ }
+ finally
+ {
+ m_lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Returns the time when this particular item was added on the list.
+ *
+ * @param index The index of the object.
+ * @return The addition time in milliseconds (@see
System.currentTimeMillis()).
+ */
+ public long getAddTime( int index )
+ {
+ m_lock.readLock().lock();
+ long res = 0;
+
+ try
+ {
+ res = m_list.get( index ).m_addTime;
+ }
+ finally
+ {
+ m_lock.readLock().unlock();
+ }
+
+ return res;
+ }
+
+ private static class CounterItem<E>
+ {
+ private E m_obj;
+ private long m_addTime;
+
+ public CounterItem(E o)
+ {
+ m_addTime = System.currentTimeMillis();
+ m_obj = o;
+ }
+ }
+
+
+}
Modified: incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/AllTests.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/AllTests.java?rev=658843&r1=658842&r2=658843&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/AllTests.java
(original)
+++ incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/AllTests.java Wed May
21 13:17:36 2008
@@ -21,6 +21,7 @@
suite.addTest( MailUtilTest.suite() );
suite.addTest( PriorityListTest.suite() );
suite.addTest( TextUtilTest.suite() );
+ suite.addTest( TimedCounterListTest.suite() );
return suite;
}
Added:
incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/TimedCounterListTest.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/TimedCounterListTest.java?rev=658843&view=auto
==============================================================================
---
incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/TimedCounterListTest.java
(added)
+++
incubator/jspwiki/trunk/tests/com/ecyrd/jspwiki/util/TimedCounterListTest.java
Wed May 21 13:17:36 2008
@@ -0,0 +1,53 @@
+package com.ecyrd.jspwiki.util;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class TimedCounterListTest extends TestCase
+{
+ TimedCounterList<String> m_list = new TimedCounterList<String>();
+
+ public void setUp()
+ {
+ m_list.add( "Foo" );
+ m_list.add( "Foo" );
+ m_list.add( "Foo" );
+ m_list.add( "Bar" );
+ }
+
+ public void testCount()
+ {
+ assertEquals( "Foo", 3, m_list.count( "Foo" ) );
+ assertEquals( "Bar", 1, m_list.count( "Bar" ) );
+ assertEquals( "Baz", 0, m_list.count( "Baz" ) );
+ }
+
+ public void testCleanup()
+ {
+ try
+ {
+ Thread.sleep(110);
+
+ m_list.cleanup(100);
+
+ assertEquals( "Foo", 0, m_list.count( "Foo" ) );
+ assertEquals( "Bar", 0, m_list.count( "Foo" ) );
+ assertEquals( "Baz", 0, m_list.count( "Foo" ) );
+
+ assertEquals( "size", 0, m_list.size() );
+ }
+ catch( InterruptedException e )
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite( TimedCounterListTest.class );
+ }
+
+}