diff options
11 files changed, 121 insertions, 70 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));      } diff --git a/ufund-ui/src/app/components/home-page/home-page.component.ts b/ufund-ui/src/app/components/home-page/home-page.component.ts index 5b2277c..95e8962 100644 --- a/ufund-ui/src/app/components/home-page/home-page.component.ts +++ b/ufund-ui/src/app/components/home-page/home-page.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core';  @Component({ -  selector: 'app-home-page', -  standalone: false, -  templateUrl: './home-page.component.html', -  styleUrl: './home-page.component.css' +    selector: 'app-home-page', +    standalone: false, +    templateUrl: './home-page.component.html', +    styleUrl: './home-page.component.css'  })  export class HomePageComponent { diff --git a/ufund-ui/src/app/components/need-page/need-page.component.ts b/ufund-ui/src/app/components/need-page/need-page.component.ts index 597d0e0..e38554c 100644 --- a/ufund-ui/src/app/components/need-page/need-page.component.ts +++ b/ufund-ui/src/app/components/need-page/need-page.component.ts @@ -2,30 +2,30 @@ import {Component, Input} from '@angular/core';  import {GoalType, Need} from '../../models/Need';  import {ActivatedRoute} from "@angular/router";  import {CupboardService} from "../../services/cupboard.service"; -import { NgFor } from '@angular/common'; +import {NgFor} from '@angular/common';  @Component({ -  selector: 'app-need-page', -  standalone: false, -  templateUrl: './need-page.component.html', -  styleUrl: './need-page.component.css' +    selector: 'app-need-page', +    standalone: false, +    templateUrl: './need-page.component.html', +    styleUrl: './need-page.component.css'  })  export class NeedPageComponent { -  constructor( -     private route: ActivatedRoute, -     private cupboardService: CupboardService, -  ) {} +    constructor( +        private route: ActivatedRoute, +        private cupboardService: CupboardService, +    ) {} -  public GoalType = GoalType; +    public GoalType = GoalType; -  @Input() need?: Need; +    @Input() need?: Need; -  ngOnInit(): void { -    const id = Number(this.route.snapshot.paramMap.get('id')); -    this.cupboardService.getNeed(id).subscribe(n => this.need = n); -  } +    ngOnInit(): void { +        const id = Number(this.route.snapshot.paramMap.get('id')); +        this.cupboardService.getNeed(id).subscribe(n => this.need = n); +    } -  back() { -    window.history.back(); -  } -}
\ No newline at end of file +    back() { +        window.history.back(); +    } +} diff --git a/ufund-ui/src/app/models/Need.ts b/ufund-ui/src/app/models/Need.ts index 9e97fd4..5cd4e39 100644 --- a/ufund-ui/src/app/models/Need.ts +++ b/ufund-ui/src/app/models/Need.ts @@ -1,13 +1,13 @@  export interface Need { -  name: string, -  id: number, -  filterAttributes: string[], -  type: GoalType; -  maxGoal: number; -  current: number; +    name: string, +    id: number, +    filterAttributes: string[], +    type: GoalType; +    maxGoal: number; +    current: number;  }  export enum GoalType { -  MONETARY, -  PHYSICAL +    MONETARY, +    PHYSICAL  } diff --git a/ufund-ui/src/app/models/User.ts b/ufund-ui/src/app/models/User.ts index f4396f6..e6848fa 100644 --- a/ufund-ui/src/app/models/User.ts +++ b/ufund-ui/src/app/models/User.ts @@ -1,5 +1,3 @@ -import {Need} from './Need'; -  export enum userType {      HELPER,      MANAGER  | 
