6 Commits 962bd0609b ... 5e3e12d5a6

Author SHA1 Message Date
  Steinar Bang 5e3e12d5a6 Handle ExcessiveAttemptsException in the ukelonn login 1 week ago
  Steinar Bang 892de05fe8 Mock UserManagementService instead of UserManagementServiceProvider in ukelonn backend test 1 week ago
  Steinar Bang a52f8f99ec Remove use of UserManagementServiceProvider using the ukelonn database in tests 1 week ago
  Steinar Bang 6a70082fd0 Fixed indentation 1 week ago
  Steinar Bang d4d73e3856 Put assertThrows lambdas on a single line 1 week ago
  Steinar Bang 7663226731 Update parent to get new osgi UserManagementnService supporting user account locking and authservice supporting that version 1 week ago

+ 1 - 1
pom.xml

@@ -17,7 +17,7 @@
     <parent>
         <groupId>no.priv.bang.pom</groupId>
         <artifactId>bang-authservice-client-pom</artifactId>
-        <version>2.0.8</version>
+        <version>2.0.9</version>
     </parent>
     <groupId>no.priv.bang.ukelonn</groupId>
     <artifactId>ukelonn</artifactId>

+ 20 - 129
ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceProviderTest.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2024 Steinar Bang
+ * Copyright 2018-2025 Steinar Bang
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,10 +39,8 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import no.priv.bang.authservice.definitions.AuthserviceException;
-import no.priv.bang.authservice.users.UserManagementServiceProvider;
 import no.priv.bang.osgi.service.mocks.logservice.MockLogService;
 import no.priv.bang.osgiservice.users.Role;
-import no.priv.bang.osgiservice.users.UserAndPasswords;
 import no.priv.bang.osgiservice.users.UserManagementService;
 import no.priv.bang.ukelonn.UkelonnException;
 import no.priv.bang.ukelonn.UkelonnService;
@@ -207,9 +205,7 @@ class UkelonnServiceProviderTest {
     @Test
     void testGetAccountInfoFromDatabaseAccountHasNoTransactions() {
         var provider = getUkelonnServiceSingleton();
-        assertThrows(UkelonnException.class, () -> {
-                provider.getAccount("on");
-            });
+        assertThrows(UkelonnException.class, () -> provider.getAccount("on"));
     }
 
     /**
@@ -219,9 +215,7 @@ class UkelonnServiceProviderTest {
     @Test
     void testGetAccountInfoFromDatabaseWhenAccountDoesNotExist() {
         var ukelonn = getUkelonnServiceSingleton();
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.getAccount("unknownuser");
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.getAccount("unknownuser"));
     }
 
     /**
@@ -248,9 +242,7 @@ class UkelonnServiceProviderTest {
             when(resultset.next()).thenThrow(SQLException.class);
             when(statement.executeQuery()).thenReturn(resultset);
             ukelonn.setDataSource(datasource);
-            assertThrows(UkelonnException.class, () -> {
-                    ukelonn.getAccount("jad");
-                });
+            assertThrows(UkelonnException.class, () -> ukelonn.getAccount("jad"));
         } finally {
             // Restore the real derby database
             ukelonn.setDataSource(originalDatasource);
@@ -258,93 +250,6 @@ class UkelonnServiceProviderTest {
     }
 
     @Test
-    void testAddAccount() {
-        var ukelonn = getUkelonnServiceSingleton();
-        var usermanagement = new UserManagementServiceProvider();
-        usermanagement.setLogservice(ukelonn.getLogservice());
-        usermanagement.setDataSource(ukelonn.getDataSource());
-        ukelonn.setUserAdmin(usermanagement);
-
-        // Create a user object
-        var newUsername = "aragorn";
-        var newEmailaddress = "strider@hotmail.com";
-        var newFirstname = "Aragorn";
-        var newLastname = "McArathorn";
-        var user = no.priv.bang.osgiservice.users.User.with()
-            .userid(0)
-            .username(newUsername)
-            .email(newEmailaddress)
-            .firstname(newFirstname)
-            .lastname(newLastname)
-            .build();
-
-        // Create a passwords object containing the user
-        var passwords = UserAndPasswords.with().user(user).password1("zecret").password2("zecret").build();
-
-        // Create a user in the database, and retrieve it (to get the user id)
-        var updatedUsers = usermanagement.addUser(passwords);
-        var createdUser = updatedUsers.stream().filter(u -> newUsername.equals(u.username())).findFirst().get();
-
-        // Add a new account to the database
-        var userWithUserId = User.with()
-            .userId(createdUser.userid())
-            .username(newUsername)
-            .email(newEmailaddress)
-            .firstname(newFirstname)
-            .lastname(newLastname)
-            .build();
-        var newAccount = ukelonn.addAccount(userWithUserId);
-        assertThat(newAccount.accountId()).isPositive();
-        assertEquals(0.0, newAccount.balance(), 0);
-    }
-
-    @Test
-    void testAddAccountWhenSqlExceptionIsThrown() throws Exception {
-        var ukelonn = new UkelonnServiceProvider();
-        var usermanagement = new UserManagementServiceProvider();
-        usermanagement.setDataSource(getUkelonnServiceSingleton().getDataSource());
-        usermanagement.setLogservice(getUkelonnServiceSingleton().getLogservice());
-        // Create a mock database that throws exceptions and inject it
-        var datasource = mock(DataSource.class);
-        var connection = mock(Connection.class);
-        when(datasource.getConnection()).thenReturn(connection);
-        var statement = mock(PreparedStatement.class);
-        when(connection.prepareStatement(anyString())).thenReturn(statement);
-        when(statement.executeUpdate()).thenThrow(SQLException.class);
-        ukelonn.setDataSource(datasource);
-        ukelonn.setLogservice(getUkelonnServiceSingleton().getLogservice());
-
-        // Create a user object
-        var newUsername = "aragorn";
-        var newEmailaddress = "strider@hotmail.com";
-        var newFirstname = "Aragorn";
-        var newLastname = "McArathorn";
-        var user = no.priv.bang.osgiservice.users.User.with()
-            .userid(0)
-            .username(newUsername)
-            .email(newEmailaddress)
-            .firstname(newFirstname)
-            .lastname(newLastname)
-            .build();
-
-        // Create a passwords object containing the user
-        var passwords = UserAndPasswords.with().user(user).password1("zecret").password2("zecret").build();
-        // Create a user in the database, and retrieve it (to get the user id)
-        var updatedUsers = usermanagement.addUser(passwords);
-        var createdUser = updatedUsers.stream().filter(u -> newUsername.equals(u.username())).findFirst().get();
-
-        // Add a new account to the database and expect to fail
-        var userWithUserId = User.with()
-            .userId(createdUser.userid())
-            .username(newUsername)
-            .email(newEmailaddress)
-            .firstname(newFirstname)
-            .lastname(newLastname)
-            .build();
-        assertThrows(UkelonnException.class, () -> ukelonn.addAccount(userWithUserId));
-    }
-
-    @Test
     void testGetJobs() {
         var ukelonn = getUkelonnServiceSingleton();
         var username = "jad";
@@ -434,9 +339,7 @@ class UkelonnServiceProviderTest {
                 .transactionAmount(45.0)
                 .transactionDate(new Date())
                 .build();
-            assertThrows(UkelonnException.class, () -> {
-                    ukelonn.registerPerformedJob(performedJob);
-                });
+            assertThrows(UkelonnException.class, () -> ukelonn.registerPerformedJob(performedJob));
         } finally {
             // Restore the real derby database
             ukelonn.setDataSource(originalDatasource);
@@ -650,9 +553,7 @@ class UkelonnServiceProviderTest {
         ukelonn.setDataSource(datasource);
 
         // trying to set the parameter here will throw an UkelonnException
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.addParametersToDeleteJobsStatement(1, statement);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.addParametersToDeleteJobsStatement(1, statement));
     }
 
     @Test
@@ -717,9 +618,7 @@ class UkelonnServiceProviderTest {
         ukelonn.setDataSource(datasource);
 
         var updatedTransaction = UpdatedTransaction.with().build();
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.updateJob(updatedTransaction);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.updateJob(updatedTransaction));
     }
 
     private TransactionType findJobTypeWithDifferentIdAndAmount(UkelonnService ukelonn, Integer transactionTypeId, double amount) {
@@ -966,9 +865,7 @@ class UkelonnServiceProviderTest {
             .build();
 
         // Try update the jobtype in the database, which should cause an exception
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.modifyJobtype(jobtype);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.modifyJobtype(jobtype));
     }
 
     @Test
@@ -1017,9 +914,7 @@ class UkelonnServiceProviderTest {
             .build();
 
         // Try update the jobtype in the database, which should cause an exception
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.createJobtype(jobtype);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.createJobtype(jobtype));
     }
 
     @Test
@@ -1066,9 +961,7 @@ class UkelonnServiceProviderTest {
             .build();
 
         // Try update the payment type in the database, which should cause an exception
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.modifyPaymenttype(paymenttype);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.modifyPaymenttype(paymenttype));
     }
 
     @Test
@@ -1117,9 +1010,7 @@ class UkelonnServiceProviderTest {
             .build();
 
         // Try creating the payment type in the database, which should cause an exception
-        assertThrows(UkelonnException.class, () -> {
-                ukelonn.createPaymenttype(paymenttype);
-            });
+        assertThrows(UkelonnException.class, () -> ukelonn.createPaymenttype(paymenttype));
     }
 
     @Test
@@ -1182,7 +1073,7 @@ class UkelonnServiceProviderTest {
         assertEquals("1", UkelonnServiceProvider.joinIds(Arrays.asList(1)).toString());
         assertEquals("1, 2", UkelonnServiceProvider.joinIds(Arrays.asList(1, 2)).toString());
         assertEquals("1, 2, 3, 4", UkelonnServiceProvider.joinIds(Arrays.asList(1, 2, 3, 4)).toString());
-        var useradmin = mock(UserManagementServiceProvider.class);
+        var useradmin = mock(UserManagementService.class);
         var user = no.priv.bang.osgiservice.users.User.with()
             .userid(1)
             .username("jad")
@@ -1341,14 +1232,14 @@ class UkelonnServiceProviderTest {
         // Add an extra active bonus to verify that two
         // concurrent bonuses will give the expected result
         var julebonus2 = ukelonn.createBonus(Bonus.with()
-                                             .bonusId(0)
-                                             .enabled(true)
-                                             .title("Julebonuz")
-                                             .description("Dobbelt betaling for utførte jobber")
-                                             .bonusFactor(1.25)
-                                             .startDate(julestart)
-                                             .endDate(juleslutt)
-                                             .build()).stream().filter(b -> "Julebonuz".equals(b.title())).findFirst().get();
+            .bonusId(0)
+            .enabled(true)
+            .title("Julebonuz")
+            .description("Dobbelt betaling for utførte jobber")
+            .bonusFactor(1.25)
+            .startDate(julestart)
+            .endDate(juleslutt)
+            .build()).stream().filter(b -> "Julebonuz".equals(b.title())).findFirst().get();
         var expectAmount2 = julebonus.bonusFactor() * amount + julebonus2.bonusFactor() * amount - amount;
         assertEquals(expectAmount2, ukelonn.addBonus(amount), 0.0);
         ukelonn.deleteBonus(julebonus2);

+ 6 - 0
ukelonn.web.services/src/main/java/no/priv/bang/ukelonn/api/resources/Login.java

@@ -28,6 +28,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.ExcessiveAttemptsException;
 import org.apache.shiro.authc.IncorrectCredentialsException;
 import org.apache.shiro.authc.LockedAccountException;
 import org.apache.shiro.authc.UnknownAccountException;
@@ -77,6 +78,11 @@ public class Login {
             return LoginResult.with()
                 .errorMessage("Wrong password")
                 .build();
+        } catch (ExcessiveAttemptsException  e) {
+            logger.warn("Login error: Failed login attempts limit reached, account locked", e);
+            return LoginResult.with()
+                .errorMessage("Login error: Failed login attempts limit reached. Account will be locked. Please contact system administrator")
+                .build();
         } catch (LockedAccountException  e) {
             logger.warn("Login error: locked account", e);
             return LoginResult.with()

+ 20 - 1
ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/resources/LoginTest.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2024 Steinar Bang
+ * Copyright 2018-2025 Steinar Bang
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import javax.servlet.http.HttpSession;
 import javax.ws.rs.InternalServerErrorException;
 
 import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.ExcessiveAttemptsException;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.web.subject.WebSubject;
@@ -174,6 +175,24 @@ class LoginTest extends ServletTestBase {
     }
 
     @Test
+    void testLoginWithExcessiveAttemptsException() {
+        createSubjectThrowingExceptionAndBindItToThread(ExcessiveAttemptsException.class);
+        LoginCredentials credentials = LoginCredentials.with()
+            .username("jad")
+            .password(Base64.getEncoder().encodeToString("wrong".getBytes()))
+            .build();
+        // Create mock OSGi services to inject
+        MockLogService logservice = new MockLogService();
+        // Create the servlet and do the login
+        Login resource = new Login();
+        resource.setLogservice(logservice);
+        LoginResult result = resource.doLogin(credentials);
+        // Check the response
+        assertThat(result.roles()).isEmpty();
+        assertEquals("Login error: Failed login attempts limit reached. Account will be locked. Please contact system administrator", result.errorMessage());
+    }
+
+    @Test
     void testLoginWithAuthenticationException() {
         createSubjectThrowingExceptionAndBindItToThread(AuthenticationException.class);
         LoginCredentials credentials = LoginCredentials.with()