aboutsummaryrefslogtreecommitdiff
path: root/ufund-api
diff options
context:
space:
mode:
authorsowgro <tpoke.ferrari@gmail.com>2025-03-26 18:14:47 -0400
committersowgro <tpoke.ferrari@gmail.com>2025-03-26 18:14:47 -0400
commitab35efb06b926e8a3aee5cfc8d1fa908aa4a4707 (patch)
tree6f456fe322a32510c611cd787d653ed186b0777d /ufund-api
parentea13cf6ab3b71ff5e83fca876ec71fec1f7b00ae (diff)
downloadJellySolutions-ab35efb06b926e8a3aee5cfc8d1fa908aa4a4707.tar.gz
JellySolutions-ab35efb06b926e8a3aee5cfc8d1fa908aa4a4707.tar.bz2
JellySolutions-ab35efb06b926e8a3aee5cfc8d1fa908aa4a4707.zip
Fix cupboard access checking and logging
Diffstat (limited to 'ufund-api')
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java33
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java12
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java30
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java3
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java29
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java6
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java12
7 files changed, 89 insertions, 36 deletions
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
index e62d5ab..55ee457 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
@@ -5,6 +5,7 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.ufund.api.ufundapi.service.AuthService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -23,21 +24,21 @@ import com.ufund.api.ufundapi.model.Need;
import com.ufund.api.ufundapi.model.Need.GoalType;
import com.ufund.api.ufundapi.service.CupboardService;
-import static java.util.List.of;
-
@RestController
@RequestMapping("cupboard")
public class CupboardController {
private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());
private final CupboardService cupboardService;
+ private final AuthService authService;
/**
* Create a cupboard controller to receive REST signals
*
* @param cupboardService The Data Access Object
*/
- public CupboardController(CupboardService cupboardService) {
+ public CupboardController(CupboardService cupboardService, AuthService authService) {
this.cupboardService = cupboardService;
+ this.authService = authService;
}
/**
@@ -50,14 +51,15 @@ public class CupboardController {
* INTERNAL_SERVER_ERROR otherwise
*/
@PostMapping("")
- public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params) {
+ public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params, @RequestHeader("jelly-api-key") String key) {
LOG.log(Level.INFO, "POST /cupboard body={0}", params);
String name = (String) params.get("name");
- double maxGoal = (double) params.get("maxGoal");
+ double maxGoal = ((Number) params.get("maxGoal")).doubleValue();
Need.GoalType goalType = GoalType.valueOf((String) params.get("type"));
try {
+ authService.keyHasAccessToCupboard(key);
Need need = cupboardService.createNeed(name, maxGoal, goalType);
return new ResponseEntity<>(need, HttpStatus.OK);
} catch (DuplicateKeyException ex) {
@@ -66,6 +68,9 @@ public class CupboardController {
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING, ex.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ } catch (IllegalAccessException ex) {
+ LOG.log(Level.WARNING, ex.getLocalizedMessage());
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (IOException ex) {
LOG.log(Level.SEVERE, ex.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -152,9 +157,10 @@ public class CupboardController {
* @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue
*/
@PutMapping("/{id}")
- public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id) {
+ public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id, @RequestHeader("jelly-api-key") String key) {
LOG.log(Level.INFO, "PUT /cupboard/{0} body={1}", of(id, need));
try {
+ authService.keyHasAccessToCupboard(key);
Need updatedNeed = cupboardService.updateNeed(need, id);
if (updatedNeed != null) {
return new ResponseEntity<>(need, HttpStatus.OK);
@@ -164,6 +170,9 @@ public class CupboardController {
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING, ex.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ } catch (IllegalAccessException ex) {
+ LOG.log(Level.WARNING, ex.getLocalizedMessage());
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (IOException ex) {
LOG.log(Level.SEVERE, ex.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -204,19 +213,27 @@ public class CupboardController {
* @return OK if the need was deleted, NOT_FOUND if the need was not found, or INTERNAL_SERVER_ERROR if an error occurred
*/
@DeleteMapping("/{id}")
- public ResponseEntity<Need> deleteNeed(@PathVariable int id) {
+ public ResponseEntity<Need> deleteNeed(@PathVariable int id, @RequestHeader("jelly-api-key") String key) {
LOG.log(Level.INFO, "DELETE /cupboard/{0}", id);
try {
+ authService.keyHasAccessToCupboard(key);
Need need = cupboardService.getNeed(id);
if (cupboardService.deleteNeed(id)) {
return new ResponseEntity<>(need, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
- }
+ }
+ } catch (IllegalAccessException ex) {
+ LOG.log(Level.WARNING, ex.getLocalizedMessage());
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (IOException ex) {
LOG.log(Level.SEVERE, ex.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
+ private Object[] of(Object ...params) {
+ return params;
+ }
+
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
index c2d9e06..33d2e4f 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
@@ -22,8 +22,6 @@ import com.ufund.api.ufundapi.model.User;
import com.ufund.api.ufundapi.service.AuthService;
import com.ufund.api.ufundapi.service.UserService;
-import static java.util.List.of;
-
@RestController
@RequestMapping("users")
public class UserController {
@@ -79,7 +77,7 @@ public class UserController {
LOG.log(Level.INFO, "GET /user/{0} key={1}", of(username, key));
try {
- authService.authenticate(username, key);
+ authService.keyHasAccessToUser(username, key);
User user = userService.getUser(username);
if (user != null) {
return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK);
@@ -109,7 +107,7 @@ public class UserController {
public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) {
LOG.log(Level.INFO,"PUT /users/{0} body={1} key={2}", of(username, user, key));
try {
- authService.authenticate(username, key);
+ authService.keyHasAccessToUser(username, key);
user = userService.updateUser(user, username);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
@@ -141,7 +139,7 @@ public class UserController {
LOG.log(Level.INFO, "DELETE /users/{0} id={1}", of(username, key));
try {
- authService.authenticate(username, key);
+ authService.keyHasAccessToUser(username, key);
if (userService.deleteUser(username)) {
return new ResponseEntity<>(HttpStatus.OK);
} else {
@@ -156,4 +154,8 @@ public class UserController {
}
}
+ private Object[] of(Object ...params) {
+ return params;
+ }
+
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
index 4e5ebce..cdce80d 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
@@ -25,8 +25,9 @@ public class AuthService {
* @param targetUsername The targetUsername of the user trying to be accessed.
* @param key The api key obtained by the client from logging in.
* @throws IllegalAccessException Thrown if access was denied to the user.
+ * @throws IOException Thrown on a file writing issue
*/
- public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException {
+ public void keyHasAccessToUser(String targetUsername, String key) throws IllegalAccessException, IOException {
var userAuth = userAuthDAO.getUserAuth(key);
if (userAuth == null) {
throw new IllegalAccessException("Invalid authentication key");
@@ -39,11 +40,36 @@ public class AuthService {
}
}
- public void authenticate(String key) throws IOException, IllegalAccessException {
+ /**
+ * Check if the provided key is valid
+ * @param key The api key obtained by the client from logging in.
+ * @throws IllegalAccessException Thrown if access was denied to the user.
+ * @throws IOException Thrown on a file writing issue
+ */
+ public void keyIsValid(String key) throws IOException, IllegalAccessException {
+ var userAuth = userAuthDAO.getUserAuth(key);
+ if (userAuth == null) {
+ throw new IllegalAccessException("Invalid authentication key");
+ }
+ }
+
+ /**
+ * Check if the provided key has access to edit the cupboard
+ * @param key The api key obtained by the client from logging in.
+ * @throws IllegalAccessException Thrown if access was denied to the user.
+ * @throws IOException Thrown on a file writing issue
+ */
+ public void keyHasAccessToCupboard(String key) throws IOException, IllegalAccessException {
var userAuth = userAuthDAO.getUserAuth(key);
if (userAuth == null) {
throw new IllegalAccessException("Invalid authentication key");
}
+
+ var username = userAuth.getUsername();
+ var userType = userService.getUser(username).getType();
+ if (userType != User.UserType.MANAGER) {
+ throw new IllegalAccessException("Provided key does not grant access to perform the requested operation");
+ }
}
/**
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
index 91e3ba5..aaa8cb8 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
@@ -111,7 +111,7 @@ public class CupboardService {
if (checkoutAmount <= 0) {
throw new IllegalArgumentException("Amount must be greater than 0");
}
- authService.authenticate(key);
+ authService.keyIsValid(key);
Need need = cupboardDAO.getNeed(id);
need.incrementCurrent(checkoutAmount);
}
@@ -124,6 +124,7 @@ public class CupboardService {
* @throws IOException Thrown on any problem removing the need
*/
public boolean deleteNeed(int id) throws IOException {
+
return cupboardDAO.deleteNeed(id);
}
}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
index 6ef6710..89697bf 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
@@ -7,10 +7,11 @@ import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.*;
+
+import com.ufund.api.ufundapi.service.AuthService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import org.springframework.http.HttpStatus;
import com.ufund.api.ufundapi.DuplicateKeyException;
@@ -21,11 +22,17 @@ import com.ufund.api.ufundapi.service.CupboardService;
public class CupboardControllerTest {
private CupboardController cupboardController;
private CupboardService mockCupboardService;
+ private final String key = "dummyKey";
@BeforeEach
public void setupCupboardDAO() {
+ AuthService mockAuthService = mock(AuthService.class);
mockCupboardService = mock(CupboardService.class);
- cupboardController = new CupboardController(mockCupboardService);
+ cupboardController = new CupboardController(mockCupboardService, mockAuthService);
+
+ try {
+ doThrow().when(mockAuthService).keyHasAccessToCupboard(key);
+ } catch (Exception ignored) {}
}
@Test
@@ -43,7 +50,7 @@ public class CupboardControllerTest {
entry("type", "MONETARY")
);
- var res = cupboardController.createNeed(needMap);
+ var res = cupboardController.createNeed(needMap, key);
assertEquals(HttpStatus.OK, res.getStatusCode());
assertEquals(need, res.getBody());
@@ -58,7 +65,7 @@ public class CupboardControllerTest {
entry("maxGoal", -100.0),
entry("type", "MONETARY"));
- var res = cupboardController.createNeed(needMap);
+ var res = cupboardController.createNeed(needMap, key);
assertEquals(HttpStatus.BAD_REQUEST, res.getStatusCode());
}
@@ -72,7 +79,7 @@ public class CupboardControllerTest {
entry("maxGoal", 100.0),
entry("type", "MONETARY"));
- var res = cupboardController.createNeed(needMap);
+ var res = cupboardController.createNeed(needMap, key);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
}
@@ -174,7 +181,7 @@ public class CupboardControllerTest {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
when(mockCupboardService.updateNeed(need, 1)).thenReturn(need);
- var res = cupboardController.updateNeed(need, 1);
+ var res = cupboardController.updateNeed(need, 1, key);
assertEquals(HttpStatus.OK, res.getStatusCode());
assertEquals(need, res.getBody());
@@ -185,7 +192,7 @@ public class CupboardControllerTest {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException());
- var res = cupboardController.updateNeed(need, 1);
+ var res = cupboardController.updateNeed(need, 1, key);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
}
@@ -196,7 +203,7 @@ public class CupboardControllerTest {
when(mockCupboardService.getNeed(1)).thenReturn(need);
when(mockCupboardService.deleteNeed(1)).thenReturn(true);
- var res = cupboardController.deleteNeed(1);
+ var res = cupboardController.deleteNeed(1, key);
assertEquals(HttpStatus.OK, res.getStatusCode());
}
@@ -206,7 +213,7 @@ public class CupboardControllerTest {
when(mockCupboardService.getNeed(1)).thenReturn(null);
when(mockCupboardService.deleteNeed(1)).thenReturn(false);
- var res = cupboardController.deleteNeed(1);
+ var res = cupboardController.deleteNeed(1, key);
assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode());
}
@@ -217,7 +224,7 @@ public class CupboardControllerTest {
when(mockCupboardService.getNeed(1)).thenReturn(need);
when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException());
- var res = cupboardController.deleteNeed(1);
+ var res = cupboardController.deleteNeed(1, key);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
index cc7df40..06fb6cd 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
@@ -82,7 +82,7 @@ public class UserControllerTest {
String key = UserAuth.generate(username).getKey();
// When getUser is called on the Mock User service, throw an IOException
// doThrow(new IllegalAccessException()).when(mockUserService).getUser(username);
- doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+ doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);
// Invoke
ResponseEntity<User> response = userController.getUser(username, key);
@@ -237,7 +237,7 @@ public class UserControllerTest {
String key = UserAuth.generate(username).getKey();
// When updateUser is called on the Mock User service, throw a Invalid Parameter exception
// exception
- doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+ doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);
// Invoke
@@ -298,7 +298,7 @@ public class UserControllerTest {
String username = "Test";
String key = UserAuth.generate(username).getKey();
// When deleteUser is called on the Mock User service, throw an IOException
- doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+ doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);
// Invoke
ResponseEntity<Boolean> response = userController.deleteUser(username, key);
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java
index d3085e5..4f58b12 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java
@@ -40,34 +40,34 @@ public class AuthServiceTest {
}
@Test
- public void testAuthenticate() throws IOException {
+ public void testKeyIsValid() throws IOException {
// Mock
when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, username, null));
when(mockUserService.getUser(username)).thenReturn(user);
// Analyze
- assertDoesNotThrow(() -> authService.authenticate(username, key));
+ assertDoesNotThrow(() -> authService.keyHasAccessToUser(username, key));
}
@Test
- public void testAuthenticateMismatchName() throws IOException {
+ public void testKeyIsValidMismatchName() throws IOException {
// Mock
when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null));
when(mockUserService.getUser("EvilFish")).thenReturn(user);
// Analyze
- assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key));
+ assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key));
}
@Test
- public void testAuthenticateMissingUserAuth() throws IOException {
+ public void testKeyIsValidMissingUserAuth() throws IOException {
// Mock
when(mockAuthDAO.getUserAuth(key)).thenReturn(null);
// Analyze
- assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key));
+ assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key));
}