aboutsummaryrefslogtreecommitdiff
path: root/ufund-api/src/main/java
diff options
context:
space:
mode:
authorHayden Hartman <haydenhartman10@gmail.com>2025-03-15 23:59:47 -0400
committerGitHub <noreply@github.com>2025-03-15 23:59:47 -0400
commit9baaa0590fbc38c06d530786a1de804ee9edd7db (patch)
tree7c94dc98f9b1978f8ccf3c38bb3777237bf0788a /ufund-api/src/main/java
parente4e6ae9a3d142fc78f31ee19464ec5e54bfb516f (diff)
parenta3150b8a8e17c8a71f617745bb8588b397a75f47 (diff)
downloadJellySolutions-9baaa0590fbc38c06d530786a1de804ee9edd7db.tar.gz
JellySolutions-9baaa0590fbc38c06d530786a1de804ee9edd7db.tar.bz2
JellySolutions-9baaa0590fbc38c06d530786a1de804ee9edd7db.zip
Merge pull request #8 from RIT-SWEN-261-02/api-auth
First attempt at an authentication system.
Diffstat (limited to 'ufund-api/src/main/java')
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java7
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java62
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java70
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java81
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java13
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java39
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java43
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java21
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java (renamed from ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java)25
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java32
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java73
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java21
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java88
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java63
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java106
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java72
16 files changed, 622 insertions, 194 deletions
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java
new file mode 100644
index 0000000..69ce6c0
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java
@@ -0,0 +1,7 @@
+package com.ufund.api.ufundapi;
+
+public class DuplicateKeyException extends Exception {
+ public DuplicateKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java
new file mode 100644
index 0000000..b46d4ee
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java
@@ -0,0 +1,62 @@
+package com.ufund.api.ufundapi.controller;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ufund.api.ufundapi.service.AuthService;
+
+@RestController
+@RequestMapping("auth")
+public class AuthController {
+ private final AuthService authService;
+
+ public AuthController(AuthService authService) {
+ this.authService = authService;
+ }
+
+ /**
+ * Attempts to log in as a user
+ *
+ * @param params A json object in the format {username: string, password: string}
+ * @return An api key and status OK if the authentication was successful,
+ * Status UNAUTHORIZED if the authentication failed and INTERNAL SERVER ERROR otherwise.
+ */
+ @PostMapping("")
+ public ResponseEntity<String> login(@RequestBody Map<String, String> params) {
+ String username = params.get("username");
+ String password = params.get("password");
+ try {
+ String key = authService.login(username, password);
+ return new ResponseEntity<>(key, HttpStatus.OK);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ } catch (IOException ex) {
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Logs out the current user
+ *
+ * @param key The API sent by the client in the header
+ * @return OK if the user was successfully logged out, INTERNAL_SERVER_ERROR otherwise.
+ */
+ @DeleteMapping("")
+ public ResponseEntity<Object> logout(@RequestHeader("jelly-api-key") String key) {
+ try {
+ authService.logout(key);
+ return new ResponseEntity<>(HttpStatus.OK);
+ } catch (IOException e) {
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+}
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 faaa98a..7773028 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
@@ -1,6 +1,8 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.security.InvalidParameterException;
+import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -17,37 +19,47 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ufund.api.ufundapi.model.Need;
-import com.ufund.api.ufundapi.persistence.CupboardDAO;
+import com.ufund.api.ufundapi.model.Need.GoalType;
+import com.ufund.api.ufundapi.service.CupboardService;
+import com.ufund.api.ufundapi.DuplicateKeyException;
@RestController
@RequestMapping("cupboard")
public class CupboardController {
private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());
- private final CupboardDAO cupboardDAO;
+ private final CupboardService cupboardService;
/**
* Create a cupboard controller to receive REST signals
*
- * @param cupboardDAO The Data Access Object
+ * @param cupboardService The Data Access Object
*/
- public CupboardController(CupboardDAO cupboardDAO) {
- this.cupboardDAO = cupboardDAO;
+ public CupboardController(CupboardService cupboardService) {
+ this.cupboardService = cupboardService;
}
/**
* Creates a Need with the provided object
*
- * @param need The need to create
- * @return OK response and the need if it was successful, INTERNAL_SERVER_ERROR otherwise
+ * @param params The need to create
+ * @return OK response and the need if it was successful,
+ * CONFLICT if another need with the same name exists
+ * UNPROCESSABLE_ENTITY if the need contains bad data
+ * INTERNAL_SERVER_ERROR otherwise
*/
@PostMapping("")
- public ResponseEntity<Need> createNeed(@RequestBody Need need) {
+ public ResponseEntity<Need> createNeed(@RequestBody Map<String, String> params) {
+ String name = params.get("name");
+ int maxGoal = Integer.parseInt(params.get("maxGoal"));
+ Need.GoalType goalType = GoalType.valueOf(params.get("goalType"));
+
try {
- if (need.getMaxGoal() <= 0) {
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
- }
- cupboardDAO.createNeed(need);
+ Need need = cupboardService.createNeed(name, maxGoal, goalType);
return new ResponseEntity<>(need, HttpStatus.OK);
+ } catch (DuplicateKeyException ex) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
+ } catch (IllegalArgumentException ex) {
+ return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
} catch (IOException ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -66,7 +78,7 @@ public class CupboardController {
LOG.info("GET /needs");
try {
- Need[] needs = cupboardDAO.getNeeds();
+ Need[] needs = cupboardService.getNeeds();
return new ResponseEntity<>(needs, HttpStatus.OK);
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
@@ -90,8 +102,8 @@ public class CupboardController {
LOG.info("GET /need/?name="+name);
try {
- Need[] needArray = cupboardDAO.findNeeds(name);
- return new ResponseEntity<>(needArray, HttpStatus.OK);
+ Need[] needs = cupboardService.searchNeeds(name);
+ return new ResponseEntity<>(needs, HttpStatus.OK);
} catch (IOException e) {
LOG.log(Level.SEVERE,e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -103,23 +115,20 @@ public class CupboardController {
*
* @param id The id used to locate the {@link Need need}
*
- * @return ResponseEntity with {@link Need need} object and HTTP status of OK if
- * found<br>
+ * @return ResponseEntity with {@link Need need} object and HTTP status of OK if found<br>
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
- * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
*/
@GetMapping("/{id}")
public ResponseEntity<Need> getNeed(@PathVariable int id) {
LOG.log(Level.INFO, "GET /need/{0}", id);
try {
- Need need = cupboardDAO.getNeed(id);
+ Need need = cupboardService.getNeed(id);
if (need != null) {
return new ResponseEntity<>(need, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -133,12 +142,17 @@ public class CupboardController {
* @param need The need to update
* @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue
*/
-
- @PutMapping("")
- public ResponseEntity<Need> updateNeed(@RequestBody Need need) {
+ @PutMapping("/{id}")
+ public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id) {
try {
- need = cupboardDAO.updateNeed(need);
- return new ResponseEntity<>(need, HttpStatus.OK);
+ Need updatedNeed = cupboardService.updateNeed(need, id);
+ if (updatedNeed != null) {
+ return new ResponseEntity<>(need, HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+ } catch (InvalidParameterException ex) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -153,9 +167,9 @@ public class CupboardController {
@DeleteMapping("/{id}")
public ResponseEntity<Need> deleteNeed(@PathVariable int id) {
try {
- if (cupboardDAO.getNeed(id) != null) {
- cupboardDAO.deleteNeed(id);
- return new ResponseEntity<>(HttpStatus.OK);
+ Need need = cupboardService.getNeed(id);
+ if (cupboardService.deleteNeed(id)) {
+ return new ResponseEntity<>(need, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
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 4e5f156..adf17a1 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
@@ -1,6 +1,8 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.security.InvalidParameterException;
+import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -12,43 +14,47 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.User;
-import com.ufund.api.ufundapi.persistence.UserDAO;
+import com.ufund.api.ufundapi.service.AuthService;
+import com.ufund.api.ufundapi.service.UserService;
@RestController
@RequestMapping("users")
public class UserController {
- private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());
- private final UserDAO UserDAO;
+ private static final Logger LOG = Logger.getLogger(UserController.class.getName());
+ private final UserService userService;
+ private final AuthService authService;
- /**
- * Create a user controller to receive REST signals
- *
- * @param userDAO The Data Access Object
- */
- public UserController(UserDAO userDAO) {
- this.UserDAO = userDAO;
+ public UserController(UserService userService, AuthService authService) {
+ this.userService = userService;
+ this.authService = authService;
}
/**
* Creates a User with the provided object
- *
- * @param user The user to create
+ * @param params A map consisting of the parameters for a user
* @return OK response and the user if it was successful, INTERNAL_SERVER_ERROR
* otherwise
*/
@PostMapping("")
- public ResponseEntity<User> createUser(@RequestBody User user) {
+ public ResponseEntity<User> createUser(@RequestBody Map<String, String> params) {
+ String username = params.get("username");
+ String password = params.get("password");
+
try {
- if (UserDAO.createUser(user) != null) {
+ User user = userService.createUser(username, password);
+ if (user != null) {
return new ResponseEntity<>(user, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
-
+ } catch (DuplicateKeyException ex) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
} catch (IOException ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -57,23 +63,27 @@ public class UserController {
/**
* Responds to the GET request for a {@linkplain User user} for the given id
*
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return ResponseEntity with {@link User user} object and HTTP status of OK if
* found<br>
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
*/
- @GetMapping("/{name}")
- public ResponseEntity<User> getUser(@PathVariable String name) {
- LOG.log(Level.INFO, "GET /user/{0}", name);
+ @GetMapping("/{username}")
+ public ResponseEntity<User> getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) {
+ LOG.log(Level.INFO, "GET /user/{0}", username);
try {
- User user = UserDAO.getUser(name);
+ authService.authenticate(username, key);
+ User user = userService.getUser(username);
if (user != null) {
- return new ResponseEntity<>(user, HttpStatus.OK);
+ return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
+ } catch (IllegalAccessException ex) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -84,42 +94,53 @@ public class UserController {
/**
* Updates a User with the provided one
*
- * @param user The user to update
+ * @param user The user to update
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return OK response and the user if it was successful, or
* INTERNAL_SERVER_ERROR if there was an issue
*/
- @PutMapping("/{name}")
- public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String name) {
+ @PutMapping("/{username}")
+ public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) {
try {
- user = UserDAO.updateUser(user, name);
+ authService.authenticate(username, key);
+ user = userService.updateUser(user, username);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
+ } catch (InvalidParameterException ex) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
/**
* Deletes a user with the desired name
*
- * @param name The name of the user
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return OK if the user was deleted, NOT_FOUND if the user was not found, or
* INTERNAL_SERVER_ERROR if an error occurred
*/
- @DeleteMapping("/{name}")
- public ResponseEntity<User> deleteUser(@PathVariable String name) {
+ @DeleteMapping("/{username}")
+ public ResponseEntity<Boolean> deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) {
+
try {
- if (UserDAO.deleteUser(name)) {
+ authService.authenticate(username, key);
+ if (userService.deleteUser(username)) {
return new ResponseEntity<>(HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
index 2611357..9ca097a 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
@@ -32,6 +32,19 @@ public class Need {
}
/**
+ * Create a new need
+ *
+ * @param name The name of the need
+ * @param maxGoal The maximum goal for this need
+ * @param type The type of need (monetary, physical)
+ */
+ public Need(String name, GoalType type, double maxGoal) {
+ this.name = name;
+ this.type = type;
+ this.maxGoal = maxGoal;
+ }
+
+ /**
* Create a deep copy of another need
*
* @param other The need to copy from
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
index 59f4c46..1e182a6 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
@@ -7,8 +7,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
- @JsonProperty("name")
- private final String name;
+ @JsonProperty("username")
+ private final String username;
@JsonProperty("passwordHash")
private int passwordHash;
@JsonProperty("basket")
@@ -17,36 +17,35 @@ public class User {
/**
* Create a new user
*
- * @param name The name of the user
+ * @param username The name of the user
*/
- public User(String name) {
- this.name = name;
+ public User(String username) {
+ this.username = username;
basket = new ArrayList<>();
}
/**
* Create a new user
*
- * @param name The name of the user
+ * @param username The name of the user
* @param basket A basket to copy from
*/
- public User(@JsonProperty("name") String name, @JsonProperty("basket") List<Need> basket) {
- this.name = name;
+ public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, @JsonProperty("basket") List<Need> basket) {
+ this.username = username;
this.basket = basket;
+ this.passwordHash = passwordHash;
}
- /**
- * Create a deep copy of another user
- *
- * @param other The user to copy from
- */
- public User(User other) {
- this.name = other.name;
- this.basket = other.basket;
+ public static User create(String username, String password) {
+ return new User(
+ username,
+ password.hashCode(),
+ new ArrayList<>()
+ );
}
- public String getName() {
- return name;
+ public String getUsername() {
+ return username;
}
public boolean verifyPassword(String password) {
@@ -65,4 +64,8 @@ public class User {
basket.remove(need);
}
+ public User withoutPasswordHash() {
+ return new User(this.username, 0, this.basket);
+ }
+
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java
new file mode 100644
index 0000000..1c11a28
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java
@@ -0,0 +1,43 @@
+package com.ufund.api.ufundapi.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+public class UserAuth {
+ @JsonProperty("key") String key;
+ @JsonProperty("username") String username;
+ @JsonProperty("expiration") LocalDateTime expiration;
+
+ public UserAuth(@JsonProperty("key") String key, @JsonProperty("username") String username, @JsonProperty("expiration") LocalDateTime expiration) {
+ this.key = key;
+ this.expiration = expiration;
+ this.username = username;
+ }
+
+ /**
+ * Generate a new user authentication profile
+ * @param username the username the key will belong to
+ * @return The new user authentication profile
+ */
+ public static UserAuth generate(String username) {
+ return new UserAuth(
+ UUID.randomUUID().toString(),
+ username,
+ LocalDateTime.now().plusDays(30)
+ );
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public LocalDateTime getExpiration() {
+ return expiration;
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
index 1435410..c8285a0 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
@@ -1,9 +1,9 @@
package com.ufund.api.ufundapi.persistence;
-import com.ufund.api.ufundapi.model.Need;
-
import java.io.IOException;
+import com.ufund.api.ufundapi.model.Need;
+
/**
* Defines the interface for Need object persistence
*
@@ -14,23 +14,10 @@ public interface CupboardDAO {
* Retrieves all {@linkplain Need needs}
*
* @return An array of {@link Need need} objects, may be empty
- *
- * @throws IOException if an issue with underlying storage
*/
Need[] getNeeds() throws IOException;
/**
- * Finds all {@linkplain Need needs} whose name contains the given text
- *
- * @param targetName The text to match against
- *
- * @return An array of {@link Need needs} whose names contains the given text, may be empty
- *
- * @throws IOException if an issue with underlying storage
- */
- Need[] findNeeds(String targetName) throws IOException;
-
- /**
* Retrieves a {@linkplain Need need} with the given name
*
* @param id The ID of the {@link Need need} to get
@@ -38,8 +25,6 @@ public interface CupboardDAO {
* @return a {@link Need need} object with the matching name
* <br>
* null if no {@link Need need} with a matching name is found
- *
- * @throws IOException if an issue with underlying storage
*/
Need getNeed(int id) throws IOException;
@@ -54,7 +39,7 @@ public interface CupboardDAO {
*
* @throws IOException if an issue with underlying storage
*/
- Need createNeed(Need need) throws IOException;
+ Need addNeed(Need need) throws IOException;
/**
* Updates and saves a {@linkplain Need need}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java
index 81ee7c0..c4aaca3 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java
@@ -11,14 +11,14 @@ import java.util.Map;
import java.util.TreeMap;
@Component
-public class CupboardFileDao implements CupboardDAO {
+public class CupboardFileDAO implements CupboardDAO {
private final Map<Integer, Need> needs; // cache
private final ObjectMapper objectMapper;
private static int nextId;
private final String filename;
- public CupboardFileDao(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException {
+ public CupboardFileDAO(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException {
this.filename = filename;
this.objectMapper = objectMapper;
needs = new TreeMap<>();
@@ -61,18 +61,6 @@ public class CupboardFileDao implements CupboardDAO {
}
/**
- * Returns an array of needs filtered by a search
- *
- * @param search The search substring
- * @return The requested array
- */
- private Need[] getNeedsArray(String search) {
- return needs.values().stream()
- .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase()))
- .toArray(Need[]::new);
- }
-
- /**
* Saves the needs to json
*
* @return True if the save was successful, false otherwise
@@ -93,13 +81,6 @@ public class CupboardFileDao implements CupboardDAO {
}
@Override
- public Need[] findNeeds(String targetName) {
- synchronized (needs) {
- return getNeedsArray(targetName);
- }
- }
-
- @Override
public Need getNeed(int id) {
synchronized (needs) {
return needs.getOrDefault(id, null);
@@ -107,7 +88,7 @@ public class CupboardFileDao implements CupboardDAO {
}
@Override
- public Need createNeed(Need need) throws IOException {
+ public Need addNeed(Need need) throws IOException {
synchronized (needs) {
Need newNeed = new Need(need);
newNeed.setID(nextId());
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java
new file mode 100644
index 0000000..355aae4
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java
@@ -0,0 +1,32 @@
+package com.ufund.api.ufundapi.persistence;
+
+import com.ufund.api.ufundapi.model.UserAuth;
+
+import java.io.IOException;
+
+public interface UserAuthDAO {
+
+ /**
+ * Get a user authentication profile
+ *
+ * @param key The auth key
+ * @return The authentication profile or null if there was none
+ */
+ UserAuth getUserAuth(String key) throws IOException;
+
+ /**
+ * Add a user authentication profile
+ *
+ * @param userAuth The user auth profile to add
+ * @throws IOException Thrown on any file writing error
+ */
+ void addUserAuth(UserAuth userAuth) throws IOException;
+
+ /**
+ * Remove a user authentication profile
+ *
+ * @param key The key of the user auth profile to remove
+ * @throws IOException Thrown on any file writing error
+ */
+ void removeUserAuth(String key) throws IOException;
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java
new file mode 100644
index 0000000..1fc1e92
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java
@@ -0,0 +1,73 @@
+package com.ufund.api.ufundapi.persistence;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ufund.api.ufundapi.model.UserAuth;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class UserAuthFIleDAO implements UserAuthDAO {
+
+ private final Map<String, UserAuth> userAuthMap;
+ private final ObjectMapper objectMapper;
+ private final String filename;
+
+ public UserAuthFIleDAO(ObjectMapper objectMapper, @Value("${authKeys.file}") String filename) throws IOException {
+ this.userAuthMap = new HashMap<>();
+ this.objectMapper = objectMapper;
+ this.filename = filename;
+ load();
+ }
+
+ /**
+ * Loads the data from the file into the map
+ *
+ * @throws IOException Thrown if there was an issue reading the file
+ */
+ private void load() throws IOException {
+ userAuthMap.clear();
+
+ UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class);
+
+ for (UserAuth userAuth : userAuthKeysArray) {
+ userAuthMap.put(userAuth.getKey(), userAuth);
+ }
+ }
+
+ /**
+ * Saves the data from the map into the json file
+ *
+ * @throws IOException Thrown on any problem writing the file
+ */
+ private void save() throws IOException {
+ objectMapper.writeValue(new File(filename), userAuthMap.values());
+ }
+
+ @Override
+ public UserAuth getUserAuth(String key) {
+ synchronized (userAuthMap) {
+ return userAuthMap.get(key);
+ }
+ }
+
+ @Override
+ public void addUserAuth(UserAuth userAuth) throws IOException {
+ synchronized (userAuthMap) {
+ userAuthMap.put(userAuth.getKey(), userAuth);
+ save();
+ }
+ }
+
+ @Override
+ public void removeUserAuth(String key) throws IOException {
+ synchronized (userAuthMap) {
+ userAuthMap.remove(key);
+ save();
+ }
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
index d456abc..29d46cf 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
@@ -21,17 +21,17 @@ public interface UserDAO {
User[] getUsers() throws IOException;
/**
- * Retrieves a {@linkplain User user} with the given name
+ * Retrieves a {@linkplain User user} with the given username
*
- * @param id The ID of the {@link User user} to get
+ * @param username The ID of the {@link User user} to get
*
- * @return a {@link User user} object with the matching name
+ * @return a {@link User user} object with the matching username
* <br>
- * null if no {@link User user} with a matching name is found
+ * null if no {@link User user} with a matching username is found
*
* @throws IOException if an issue with underlying storage
*/
- User getUser(String name) throws IOException;
+ User getUser(String username) throws IOException;
/**
* Creates and saves a {@linkplain User user}
@@ -44,25 +44,24 @@ public interface UserDAO {
*
* @throws IOException if an issue with underlying storage
*/
- User createUser(User user) throws IOException;
+ User addUser(User user) throws IOException;
/**
* Updates and saves a {@linkplain User user}
*
- * @param newUser {@link User user} object to be updated and saved
- * @param name {@link String name} name of object to be updated
+ * @param user {@link User user} object to be updated and saved
*
* @return updated {@link User user} if successful, null if
* {@link User user} could not be found
*
* @throws IOException if underlying storage cannot be accessed
*/
- User updateUser(User newUser, String name) throws IOException;
+ User updateUser(User user) throws IOException;
/**
* Deletes a {@linkplain User user} with the given id
*
- * @param id The id of the {@link User user}
+ * @param username The id of the {@link User user}
*
* @return true if the {@link User user} was deleted
* <br>
@@ -70,5 +69,5 @@ public interface UserDAO {
*
* @throws IOException if underlying storage cannot be accessed
*/
- boolean deleteUser(String name) throws IOException;
+ boolean deleteUser(String username) throws IOException;
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
index 18eec18..f17f8f2 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
@@ -2,8 +2,8 @@ package com.ufund.api.ufundapi.persistence;
import java.io.File;
import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -21,7 +21,7 @@ public class UserFileDAO implements UserDAO {
public UserFileDAO(@Value("${users.file}") String filename, ObjectMapper objectMapper) throws IOException {
this.filename = filename;
this.objectMapper = objectMapper;
- users = new TreeMap<>();
+ users = new HashMap<>();
load(); // load the users from the file
}
@@ -36,7 +36,7 @@ public class UserFileDAO implements UserDAO {
User[] usersArray = objectMapper.readValue(new File(filename), User[].class);
for (User user : usersArray) {
- users.put(user.getName(), user);
+ users.put(user.getUsername(), user);
}
}
@@ -47,100 +47,54 @@ public class UserFileDAO implements UserDAO {
* @throws IOException If there was an IO issue saving the file
*/
private boolean save() throws IOException {
- User[] userArray = getUserArray();
-
- objectMapper.writeValue(new File(filename), userArray);
+ objectMapper.writeValue(new File(filename), users.values());
return true;
}
- /**
- * Return an array of the needs
- *
- * @return An array of all the needs
- */
- private User[] getUserArray() {
- return users.values().toArray(User[]::new);
- }
-
@Override
- public User[] getUsers() throws IOException {
+ public User[] getUsers() {
synchronized (users) {
- return getUserArray();
+ return users.values().toArray(User[]::new);
}
}
- /**
- * Return the user with the String name name or null otherwise
- *
- * @param name Name of desired user
- *
- * @return Desired user, null otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User getUser(String name) throws IOException {
+ public User getUser(String username) {
synchronized (users) {
- return users.getOrDefault(name, null);
+ return users.getOrDefault(username, null);
}
}
- /**
- * Create a User user
- *
- * @param user User to create
- *
- * @return Desired created user
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User createUser(User user) throws IOException {
+ public User addUser(User user) throws IOException {
synchronized (users) {
- if (getUser(user.getName()) == null) {
- User newUser = new User(user);
- users.put(newUser.getName(), newUser);
- save();
- return newUser;
- } else {
- return null;
+ var res = users.putIfAbsent(user.getUsername(), user);
+ save();
+ if (res == null) {
+ return user;
}
+ return res;
}
}
- /**
- * Update a user that matches the supplied name
- *
- * @param name The name of the user
- * @param newUser New user data
- *
- * @return Desired user, null otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User updateUser(User newUser, String name) throws IOException {
+ public User updateUser(User user) throws IOException {
synchronized (users) {
- if (users.containsKey(name)) {
- users.put(name, newUser);
+ if (users.containsKey(user.getUsername())) {
+ users.put(user.getUsername(), user);
save();
- return newUser;
+ return user;
} else {
return null;
}
}
}
- /**
- * Delete a user matching the name
- *
- * @param name The name of the user
- *
- * @return True if deleted, false otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public boolean deleteUser(String name) throws IOException {
+ public boolean deleteUser(String username) throws IOException {
synchronized (users) {
- if (users.containsKey(name)) {
- users.remove(name);
+ if (users.containsKey(username)) {
+ users.remove(username);
return save();
} else {
return false;
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
new file mode 100644
index 0000000..591d891
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
@@ -0,0 +1,63 @@
+package com.ufund.api.ufundapi.service;
+
+import com.ufund.api.ufundapi.model.UserAuth;
+import com.ufund.api.ufundapi.persistence.UserAuthDAO;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Component
+public class AuthService {
+
+ private final UserAuthDAO userAuthDAO;
+ private final UserService userService;
+
+ public AuthService(UserAuthDAO userAuthDAO, UserService userService) {
+ this.userAuthDAO = userAuthDAO;
+ this.userService = userService;
+ }
+
+ /**
+ * Check if the provided key has access to the provided user.
+ *
+ * @param username The username 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.
+ */
+ public void authenticate(String username, String key) throws IllegalAccessException, IOException {
+ var userAuth = userAuthDAO.getUserAuth(key);
+ if (userAuth == null || !userAuth.getUsername().equals(username)) {
+ throw new IllegalAccessException("Unauthorized");
+ }
+ }
+
+ /**
+ * Attempt to log in with the provided credentials
+ *
+ * @param username The username of the user
+ * @param password The password of the user
+ * @return An API key if the authentication was successful.
+ * @throws IllegalAccessException Thrown if the username or password was incorrect
+ * @throws IOException If there was an issue saving the authentication
+ */
+ public String login(String username, String password) throws IllegalAccessException, IOException {
+ var usr = userService.getUser(username);
+ if (usr == null || !usr.verifyPassword(password)) {
+ throw new IllegalAccessException("Unauthorized");
+ }
+ var userAuth = UserAuth.generate(username);
+ userAuthDAO.addUserAuth(userAuth);
+ return userAuth.getKey();
+ }
+
+ /**
+ * Logs out the current user
+ *
+ * @param key The API key to of the client
+ * @throws IOException Thrown if there was an error saving the authentication
+ */
+ public void logout(String key) throws IOException {
+ userAuthDAO.removeUserAuth(key);
+ }
+
+}
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
new file mode 100644
index 0000000..78f8f85
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
@@ -0,0 +1,106 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.springframework.stereotype.Component;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.Need;
+import com.ufund.api.ufundapi.persistence.CupboardDAO;
+
+@Component
+public class CupboardService {
+
+ private final CupboardDAO cupboardDAO;
+
+ public CupboardService(CupboardDAO cupboardDAO) {
+ this.cupboardDAO = cupboardDAO;
+ }
+
+ /**
+ * Creates a new Need
+ *
+ * @param name The name of the need to create
+ * @param maxGoal The max goal of the new need
+ * @param goalType The goal type of the new need
+ * @return The need that was created
+ * @throws IOException Thrown if there was any issue saving the data
+ * @throws DuplicateKeyException If there already exists a need with the same name
+ */
+ public Need createNeed(String name, double maxGoal, Need.GoalType goalType) throws IOException, DuplicateKeyException {
+
+ if (maxGoal <= 0) {
+ throw new IllegalArgumentException("Max Goal must be greater than zero");
+ }
+
+ for (Need searchNeed : cupboardDAO.getNeeds()) {
+ if (searchNeed.getName().equalsIgnoreCase(name)) {
+ throw new DuplicateKeyException("Duplicate names are not allowed");
+ }
+ }
+
+ Need need = new Need(name, goalType, maxGoal);
+ return cupboardDAO.addNeed(need);
+
+ }
+
+ /**
+ * Get all the needs in the cupboard
+ *
+ * @return An array containing all needs
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public Need[] getNeeds() throws IOException {
+ return cupboardDAO.getNeeds();
+ }
+
+ /**
+ * Returns an array of needs filtered by a search
+ *
+ * @param search The search substring
+ * @return The requested array
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public Need[] searchNeeds(String search) throws IOException {
+ return Arrays.stream(cupboardDAO.getNeeds())
+ .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase()))
+ .toArray(Need[]::new);
+ }
+
+ /**
+ * Gets a need with the specified ID
+ *
+ * @param id the ID of the need
+ * @return The resulting Need or null if the need was not found
+ */
+ public Need getNeed(int id) throws IOException {
+ return cupboardDAO.getNeed(id);
+ }
+
+ /**
+ * Updates a need
+ *
+ * @param id The ID of the need to update
+ * @param need The need object to set (note: the ID is ignored)
+ * @return The updated need object
+ * @throws IOException Thrown if there was an issue saving the changes
+ */
+ public Need updateNeed(Need need, int id) throws IOException {
+ if (need.getId() != id) {
+ throw new IllegalArgumentException("ID in URL and body must match");
+ }
+ return cupboardDAO.updateNeed(need);
+ }
+
+ /**
+ * Delete a need from the cupboard
+ *
+ * @param id the ID of the need
+ * @return True if the need was deleted
+ * @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/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java
new file mode 100644
index 0000000..935ee72
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java
@@ -0,0 +1,72 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.User;
+import com.ufund.api.ufundapi.persistence.UserDAO;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserService {
+
+ private final UserDAO userDAO;
+
+ public UserService(UserDAO userDao) {
+ this.userDAO = userDao;
+ }
+
+ /**
+ * Creates a new user
+ *
+ * @param username The username of the user
+ * @param password The password of the user
+ * @return The created user object
+ * @throws IOException Thrown on any problem saving the file
+ */
+ public User createUser(String username, String password) throws IOException, DuplicateKeyException {
+ if (userDAO.getUser(username) != null) {
+ throw new DuplicateKeyException("A user with this name already exists");
+ }
+ User user = User.create(username, password);
+ return userDAO.addUser(user);
+ }
+
+ /**
+ * Gets a user with the given username
+ *
+ * @param username The username of the user
+ * @return The user object with that username
+ * @throws IOException If there was any problem saving the file
+ */
+ public User getUser(String username) throws IOException {
+ return userDAO.getUser(username);
+ }
+
+ /**
+ * Updates a user
+ *
+ * @param user The ID of the user to update
+ * @param username The user object to set (note: the ID is ignored)
+ * @return The updated user object
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public User updateUser(User user, String username) throws IOException {
+ if (!user.getUsername().equals(username)) {
+ throw new IllegalArgumentException("ID in URL and body must match");
+ }
+ return userDAO.updateUser(user);
+ }
+
+ /**
+ * Deletes a user
+ *
+ * @param username The username of the user to delete
+ * @return True if the user was deleted
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public boolean deleteUser(String username) throws IOException {
+ return userDAO.deleteUser(username);
+ }
+
+}