4545import org .labkey .api .data .Container ;
4646import org .labkey .api .data .ContainerManager ;
4747import org .labkey .api .data .CoreSchema ;
48+ import org .labkey .api .data .DbScope ;
49+ import org .labkey .api .data .DbScope .Transaction ;
4850import org .labkey .api .data .Project ;
4951import org .labkey .api .data .PropertyManager ;
5052import org .labkey .api .data .PropertyManager .PropertyMap ;
@@ -263,6 +265,41 @@ public static boolean isAutoCreateAccountsEnabled()
263265
264266 public static boolean isSelfServiceEmailChangesEnabled () { return getAuthSetting (SELF_SERVICE_EMAIL_CHANGES_KEY , false );}
265267
268+ public static boolean isLoginAttemptControlEnabled ()
269+ {
270+ return getAuthSetting (LOGIN_ATTEMPT_ENABLED_KEY , false );
271+ }
272+
273+ public static int getLoginAttemptLimit ()
274+ {
275+ return getAuthenticationProperty (LOGIN_ATTEMPT_LIMIT_KEY , 3 );
276+ }
277+
278+ public static int getLoginAttemptPeriod ()
279+ {
280+ return getAuthenticationProperty (LOGIN_ATTEMPT_PERIOD_KEY , 30 );
281+ }
282+
283+ public static int getLoginAttemptResetTime ()
284+ {
285+ return getAuthenticationProperty (LOGIN_ATTEMPT_RESET_TIME_KEY , 5 );
286+ }
287+
288+ // Convenience method that returns the default value on missing or bad value
289+ private static int getAuthenticationProperty (@ NotNull String key , int defaultValue )
290+ {
291+ Map <String , String > props = PropertyManager .getProperties (AUTHENTICATION_CATEGORY );
292+ String value = props .get (key );
293+ try
294+ {
295+ return value == null ? defaultValue : Integer .parseInt (value );
296+ }
297+ catch (NumberFormatException e )
298+ {
299+ return defaultValue ;
300+ }
301+ }
302+
266303 public static @ NotNull String getDefaultDomain ()
267304 {
268305 Map <String , String > props = PropertyManager .getProperties (AUTHENTICATION_CATEGORY );
@@ -291,7 +328,7 @@ public static void saveAuthSetting(User user, String key, boolean value)
291328 saveAuthSetting (user , key , Boolean .toString (value ), value ? "enabled" : "disabled" );
292329 }
293330
294- private static void saveAuthSetting (User user , String key , String value , String action )
331+ public static void saveAuthSetting (User user , String key , String value , String action )
295332 {
296333 WritablePropertyMap props = PropertyManager .getWritableProperties (AUTHENTICATION_CATEGORY , true );
297334 props .put (key , value );
@@ -308,6 +345,41 @@ public static void saveAuthSettings(User user, Map<String, Boolean> map)
308345 .forEach (e ->saveAuthSetting (user , e .getKey (), e .getValue ()));
309346 }
310347
348+ // Returns true if any setting changed
349+ public static boolean saveLoginAttemptSettings (User user , boolean enabled , int limit , int period , int resetTime )
350+ {
351+ if (limit < 1 || period < 1 || resetTime < 1 )
352+ throw new IllegalArgumentException ("limit, period, and resetTime values must be positive!" );
353+
354+ // Use standard saveAuthSetting() methods to ensure audit logging
355+ boolean changed = false ;
356+ try (Transaction t = DbScope .getLabKeyScope ().beginTransaction ())
357+ {
358+ if (enabled != isLoginAttemptControlEnabled ())
359+ {
360+ saveAuthSetting (user , LOGIN_ATTEMPT_ENABLED_KEY , enabled );
361+ changed = true ;
362+ }
363+ if (limit != getLoginAttemptLimit ())
364+ {
365+ saveAuthSetting (user , LOGIN_ATTEMPT_LIMIT_KEY , String .valueOf (limit ), "set to " + limit );
366+ changed = true ;
367+ }
368+ if (period != getLoginAttemptPeriod ())
369+ {
370+ saveAuthSetting (user , LOGIN_ATTEMPT_PERIOD_KEY , String .valueOf (period ), "set to " + period );
371+ changed = true ;
372+ }
373+ if (resetTime != getLoginAttemptResetTime ())
374+ {
375+ saveAuthSetting (user , LOGIN_ATTEMPT_RESET_TIME_KEY , String .valueOf (resetTime ), "set to " + resetTime );
376+ changed = true ;
377+ }
378+ t .commit ();
379+ }
380+ return changed ;
381+ }
382+
311383 public static void reorderConfigurations (User user , String name , int [] rowIds )
312384 {
313385 if (null != rowIds && rowIds .length != 0 )
@@ -588,7 +660,8 @@ private static void addAuthSettingAuditEvent(User user, String name, String acti
588660 return AuthenticationProviderCache .getProvider (ResetPasswordProvider .class , name );
589661 }
590662
591- public static @ Nullable DisableLoginProvider getEnabledDisableLoginProviderForUser (String id )
663+ // Return a DisableLoginProvider if it's enabled and applicable to this user
664+ public static @ Nullable DisableLoginProvider getDisableLoginProviderForUser (String id )
592665 {
593666 for (DisableLoginProvider provider : AuthenticationProviderCache .getProviders (DisableLoginProvider .class ))
594667 if (provider .isEnabledForUser (id ))
@@ -631,19 +704,27 @@ public static boolean isAcceptOnlyFicamProviders()
631704
632705 public static void setAcceptOnlyFicamProviders (User user , boolean enable )
633706 {
634- saveAuthSetting (user , ACCEPT_ONLY_FICAM_PROVIDERS_KEY , enable );
635- AuthenticationConfigurationCache .clear ();
707+ if (isAcceptOnlyFicamProviders () != enable )
708+ {
709+ saveAuthSetting (user , ACCEPT_ONLY_FICAM_PROVIDERS_KEY , enable );
710+ AuthenticationConfigurationCache .clear ();
711+ }
636712 }
637713
638- // Used by start-up properties
639- private static final String AUTHENTICATION_CATEGORY = "Authentication" ;
714+ // Used by start-up properties and upgrade code
715+ public static final String AUTHENTICATION_CATEGORY = "Authentication" ;
640716
641717 public static final String SELF_REGISTRATION_KEY = "SelfRegistration" ;
642718 public static final String AUTO_CREATE_ACCOUNTS_KEY = "AutoCreateAccounts" ;
643719 public static final String DEFAULT_DOMAIN = "DefaultDomain" ;
644720 public static final String SELF_SERVICE_EMAIL_CHANGES_KEY = "SelfServiceEmailChanges" ;
645721 public static final String ACCEPT_ONLY_FICAM_PROVIDERS_KEY = "AcceptOnlyFicamProviders" ;
646722
723+ public static final String LOGIN_ATTEMPT_ENABLED_KEY = "LoginAttemptEnabled" ;
724+ public static final String LOGIN_ATTEMPT_LIMIT_KEY = "LoginAttemptLimit" ;
725+ public static final String LOGIN_ATTEMPT_PERIOD_KEY = "LoginAttemptPeriod" ;
726+ public static final String LOGIN_ATTEMPT_RESET_TIME_KEY = "LoginAttemptResetTime" ;
727+
647728 public enum AuthenticationSettings implements StartupProperty
648729 {
649730 SelfRegistration ("Allow self sign up" ),
@@ -1165,17 +1246,23 @@ public static PrimaryAuthenticationResult finalizePrimaryAuthentication(HttpServ
11651246
11661247 // limit one bad login per second averaged out over 60sec
11671248 private static final Cache <Integer , RateLimiter > addrLimiter = CacheManager .getCache (1001 , TimeUnit .MINUTES .toMillis (5 ), "Login limiter" );
1168- private static final Cache <Integer , RateLimiter > userLimiter = CacheManager .getCache (1001 , TimeUnit .MINUTES .toMillis (5 ), "User limiter" );
11691249 private static final Cache <Integer , RateLimiter > pwdLimiter = CacheManager .getCache (1001 , TimeUnit .MINUTES .toMillis (5 ), "Password limiter" );
1170- private static final CacheLoader <Integer , RateLimiter > addrLoader = (key , request ) -> new RateLimiter ("Addr limiter: " + key , new Rate (60 , TimeUnit .MINUTES ));
1171- private static final CacheLoader <Integer , RateLimiter > pwdLoader = (key , request ) -> new RateLimiter ("Pwd limiter: " + key , new Rate (20 , TimeUnit .MINUTES ));
1172- private static final CacheLoader <Integer , RateLimiter > userLoader = (key , request ) -> new RateLimiter ("User limiter: " + key , new Rate (20 , TimeUnit .MINUTES ));
1250+ private static final CacheLoader <Integer , RateLimiter > addrLoader = (key , _ ) -> new RateLimiter ("Addr limiter: " + key , new Rate (60 , TimeUnit .MINUTES ));
1251+ private static final CacheLoader <Integer , RateLimiter > pwdLoader = (key , _ ) -> new RateLimiter ("Pwd limiter: " + key , new Rate (20 , TimeUnit .MINUTES ));
1252+
1253+ private static final Cache <String , RateLimiter > userLimiter = CacheManager .getCache (10000 , TimeUnit .MINUTES .toMillis (5 ), "User limiter" );
1254+ private static final CacheLoader <String , RateLimiter > userLoader = (key , _ ) -> new RateLimiter ("User limiter: " + key , new Rate (20 , TimeUnit .MINUTES ));
11731255
1174- private static Integer _toKey (String s )
1256+ private static Integer getIntCacheKey (String s )
11751257 {
11761258 return null ==s ? 0 : s .toLowerCase ().hashCode () % 1000 ;
11771259 }
11781260
1261+ public static String getEmailCacheKey (String s )
1262+ {
1263+ return StringUtils .trimToEmpty (s ).toLowerCase ();
1264+ }
1265+
11791266 private static PrimaryAuthenticationResult _beforeAuthenticate (HttpServletRequest request , String id , String pwd )
11801267 {
11811268 if (null == id || null == pwd )
@@ -1184,10 +1271,10 @@ private static PrimaryAuthenticationResult _beforeAuthenticate(HttpServletReques
11841271 long delay = 0 ;
11851272
11861273 // slow down login attempts when we detect more than 20/minute bad attempts per user, password, or ip address
1187- rl = addrLimiter .get (_toKey (request == null ? null : request .getRemoteAddr ()));
1274+ rl = addrLimiter .get (getIntCacheKey (request == null ? null : request .getRemoteAddr ()));
11881275 if (null != rl )
11891276 delay = Math .max (delay ,rl .add (0 , false ));
1190- rl = pwdLimiter .get (_toKey (pwd ));
1277+ rl = pwdLimiter .get (getIntCacheKey (pwd ));
11911278 if (null != rl )
11921279 delay = Math .max (delay , rl .add (0 , false ));
11931280
@@ -1209,15 +1296,15 @@ private static PrimaryAuthenticationResult _beforeAuthenticate(HttpServletReques
12091296
12101297 private static long getUserLoginDelay (String id ) throws LoginDisabledException
12111298 {
1212- DisableLoginProvider provider = AuthenticationManager .getEnabledDisableLoginProviderForUser (id );
1299+ DisableLoginProvider provider = AuthenticationManager .getDisableLoginProviderForUser (id );
12131300 if (provider != null )
12141301 return provider .getUserDelay (id );
12151302 return getDefaultUserLoginDelay (id );
12161303 }
12171304
12181305 private static long getDefaultUserLoginDelay (String id )
12191306 {
1220- RateLimiter rl = userLimiter .get (_toKey (id ));
1307+ RateLimiter rl = userLimiter .get (getEmailCacheKey (id ));
12211308 if (null != rl )
12221309 return rl .add (0 , false );
12231310 return 0 ;
@@ -1230,9 +1317,9 @@ private static void _afterAuthenticate(HttpServletRequest request, String id, St
12301317 if (result .getStatus () == AuthenticationStatus .BadCredentials || result .getStatus () == AuthenticationStatus .InactiveUser )
12311318 {
12321319 RateLimiter rl ;
1233- rl = addrLimiter .get (_toKey (request .getRemoteAddr ()),request , addrLoader );
1320+ rl = addrLimiter .get (getIntCacheKey (request .getRemoteAddr ()),request , addrLoader );
12341321 rl .add (1 , false );
1235- rl = pwdLimiter .get (_toKey (pwd ),request , pwdLoader );
1322+ rl = pwdLimiter .get (getIntCacheKey (pwd ),request , pwdLoader );
12361323 rl .add (1 , false );
12371324
12381325 addUserLoginDelay (request , id );
@@ -1245,14 +1332,14 @@ else if (result.getStatus() == AuthenticationStatus.Success)
12451332
12461333 private static void resetModuleUserLoginDelay (String id )
12471334 {
1248- DisableLoginProvider provider = AuthenticationManager .getEnabledDisableLoginProviderForUser (id );
1335+ DisableLoginProvider provider = AuthenticationManager .getDisableLoginProviderForUser (id );
12491336 if (provider != null )
12501337 provider .resetUserDelay (id );
12511338 }
12521339
12531340 private static void addUserLoginDelay (HttpServletRequest request , String id )
12541341 {
1255- DisableLoginProvider provider = AuthenticationManager .getEnabledDisableLoginProviderForUser (id );
1342+ DisableLoginProvider provider = AuthenticationManager .getDisableLoginProviderForUser (id );
12561343 if (provider != null )
12571344 provider .addUserDelay (request , id , 1 );
12581345 else
@@ -1261,7 +1348,7 @@ private static void addUserLoginDelay(HttpServletRequest request, String id)
12611348
12621349 private static void addDefaultUserLoginDelay (HttpServletRequest request , String id )
12631350 {
1264- RateLimiter rl = userLimiter .get (_toKey (id ),request , userLoader );
1351+ RateLimiter rl = userLimiter .get (getEmailCacheKey (id ),request , userLoader );
12651352 rl .add (1 , false );
12661353 }
12671354
0 commit comments