2727import org .junit .Test ;
2828
2929import org .apache .cassandra .SchemaLoader ;
30+ import org .apache .cassandra .config .CassandraRelevantProperties ;
3031import org .apache .cassandra .config .DatabaseDescriptor ;
3132import org .apache .cassandra .db .ColumnFamilyStore ;
33+ import org .apache .cassandra .exceptions .OverloadedException ;
3234import org .apache .cassandra .schema .KeyspaceParams ;
3335import org .apache .cassandra .schema .SchemaConstants ;
3436import org .apache .cassandra .schema .TableMetadata ;
3739import static org .apache .cassandra .auth .AuthTestUtils .*;
3840import static org .junit .Assert .assertEquals ;
3941import static org .junit .Assert .assertTrue ;
42+ import static org .junit .Assert .fail ;
4043
4144public class CassandraRoleManagerTest
4245{
@@ -151,6 +154,53 @@ public void warmCacheLoadsAllEntries()
151154 }
152155 }
153156
157+ public void testPasswordUpdateRateLimiting () throws Exception
158+ {
159+ try
160+ {
161+ CassandraRoleManager .updatePasswordUpdateMinInterval (100 );
162+
163+ IRoleManager roleManager = new LocalCassandraRoleManager ();
164+ roleManager .setup ();
165+
166+ RoleResource testRole = RoleResource .role ("test_password_role" );
167+ RoleOptions options = getLoginRoleOptions ("initial_password" );
168+ roleManager .createRole (AuthenticatedUser .ANONYMOUS_USER , testRole , options );
169+
170+ // Wait for the rate limit interval to pass
171+ Thread .sleep (150 );
172+
173+ // First password change should succeed
174+ RoleOptions newOptions1 = getLoginRoleOptions ("new_password_1" );
175+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , testRole , newOptions1 );
176+
177+ // Immediate second password change should fail with OverloadedException
178+ try
179+ {
180+ RoleOptions newOptions2 = getLoginRoleOptions ("new_password_2" );
181+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , testRole , newOptions2 );
182+ fail ("Expected OverloadedException due to password update rate limiting" );
183+ }
184+ catch (OverloadedException e )
185+ {
186+ assertEquals ("Password for role test_password_role can only be changed every 100ms. " , e .getMessage ());
187+ }
188+
189+ // Wait for the rate limit interval to pass
190+ Thread .sleep (150 );
191+
192+ // After waiting, password change should succeed again
193+ RoleOptions newOptions3 = getLoginRoleOptions ("new_password_3" );
194+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , testRole , newOptions3 );
195+
196+ roleManager .dropRole (AuthenticatedUser .ANONYMOUS_USER , testRole );
197+ }
198+ finally
199+ {
200+ CassandraRoleManager .updatePasswordUpdateMinInterval (CassandraRelevantProperties .ROLE_PASSWORD_UPDATE_MIN_INTERVAL_MS .getInt ());
201+ }
202+ }
203+
154204 @ Test
155205 public void warmCacheWithEmptyTable ()
156206 {
@@ -167,4 +217,86 @@ private void assertRoleSet(Set<Role> actual, RoleResource...expected)
167217 for (RoleResource expectedRole : expected )
168218 assertTrue (actual .stream ().anyMatch (role -> role .resource .equals (expectedRole )));
169219 }
220+
221+ public void testPasswordUpdateRateLimitingDisabled () throws Exception
222+ {
223+ try
224+ {
225+ CassandraRoleManager .updatePasswordUpdateMinInterval (0 );
226+
227+ IRoleManager roleManager = new LocalCassandraRoleManager ();
228+ roleManager .setup ();
229+
230+ RoleResource testRole = RoleResource .role ("test_no_limit_role" );
231+ RoleOptions options = getLoginRoleOptions ("initial_password" );
232+ roleManager .createRole (AuthenticatedUser .ANONYMOUS_USER , testRole , options );
233+
234+ // Multiple rapid password changes should all succeed when rate limiting is disabled
235+ for (int i = 0 ; i < 5 ; i ++)
236+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , testRole , getLoginRoleOptions ("password_" + i ));
237+
238+ roleManager .dropRole (AuthenticatedUser .ANONYMOUS_USER , testRole );
239+ }
240+ finally
241+ {
242+ CassandraRoleManager .updatePasswordUpdateMinInterval (CassandraRelevantProperties .ROLE_PASSWORD_UPDATE_MIN_INTERVAL_MS .getInt ());
243+ }
244+ }
245+
246+ @ Test
247+ public void testPasswordUpdateRateLimitingPerRole () throws Exception
248+ {
249+ try
250+ {
251+ CassandraRoleManager .updatePasswordUpdateMinInterval (100 );
252+
253+ IRoleManager roleManager = new LocalCassandraRoleManager ();
254+ roleManager .setup ();
255+
256+ RoleResource role1 = RoleResource .role ("test_role_1" );
257+ RoleResource role2 = RoleResource .role ("test_role_2" );
258+
259+ RoleOptions options1 = getLoginRoleOptions ("password1" );
260+ roleManager .createRole (AuthenticatedUser .ANONYMOUS_USER , role1 , options1 );
261+
262+ RoleOptions options2 = getLoginRoleOptions ("password2" );
263+ roleManager .createRole (AuthenticatedUser .ANONYMOUS_USER , role2 , options2 );
264+
265+ // Wait for the rate limit interval to pass
266+ Thread .sleep (150 );
267+
268+ RoleOptions newOptions1 = getLoginRoleOptions ("new_password1" );
269+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , role1 , newOptions1 );
270+
271+ RoleOptions newOptions2 = getLoginRoleOptions ("new_password2" );
272+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , role2 , newOptions2 );
273+
274+ try
275+ {
276+ RoleOptions newOptions1Again = getLoginRoleOptions ("another_password1" );
277+ roleManager .alterRole (AuthenticatedUser .ANONYMOUS_USER , role1 , newOptions1Again );
278+ fail ("Expected OverloadedException for test_role_1" );
279+ }
280+ catch (OverloadedException e )
281+ {
282+ assertEquals ("Password for role test_role_1 can only be changed every 100ms." , e .getMessage ());
283+ }
284+
285+ roleManager .dropRole (AuthenticatedUser .ANONYMOUS_USER , role1 );
286+ roleManager .dropRole (AuthenticatedUser .ANONYMOUS_USER , role2 );
287+ }
288+ finally
289+ {
290+ CassandraRoleManager .updatePasswordUpdateMinInterval (CassandraRelevantProperties .ROLE_PASSWORD_UPDATE_MIN_INTERVAL_MS .getInt ());
291+ }
292+ }
293+
294+ public static RoleOptions getLoginRoleOptions (String password )
295+ {
296+ RoleOptions roleOptions = new RoleOptions ();
297+ roleOptions .setOption (IRoleManager .Option .SUPERUSER , false );
298+ roleOptions .setOption (IRoleManager .Option .LOGIN , true );
299+ roleOptions .setOption (IRoleManager .Option .PASSWORD , password );
300+ return roleOptions ;
301+ }
170302}
0 commit comments