commit 7779d8bdfdf9fdd4c6996d2776ea87c045766e98
Author: Jakub Danek <jakub.danek@yoso.fi>
Date:   Fri Oct 11 15:57:07 2019 +0200

    re #37 secured access to deleting vacation requests
    
    Disable unused edit vacation endpoint.

diff --git a/server/src/main/java/org/danekja/ymanager/business/ApiManager.java b/server/src/main/java/org/danekja/ymanager/business/ApiManager.java
index 08c47be..3c5ecdc 100644
--- a/server/src/main/java/org/danekja/ymanager/business/ApiManager.java
+++ b/server/src/main/java/org/danekja/ymanager/business/ApiManager.java
@@ -1,5 +1,6 @@
 package org.danekja.ymanager.business;
 
+import org.danekja.ymanager.business.auth.anot.CanModifyVacation;
 import org.danekja.ymanager.business.auth.anot.IsOwner;
 import org.danekja.ymanager.domain.*;
 import org.danekja.ymanager.dto.DefaultSettings;
@@ -14,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataAccessException;
 import org.springframework.stereotype.Component;
 
+import javax.annotation.security.DenyAll;
 import java.time.LocalDate;
 import java.util.List;
 import java.util.Optional;
@@ -206,8 +208,9 @@ public class ApiManager implements Manager {
     }
 
     @Override
-    @IsOwner
-    public void changeVacation(Long userId, VacationDay vacationDay) throws RESTFullException {
+    //TODO not called anyway, allow after reimplementation
+    @DenyAll
+    public void changeVacation(VacationDay vacationDay) throws RESTFullException {
         try {
             Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationDay.getId());
             if (vacation.isPresent()) {
@@ -268,9 +271,9 @@ public class ApiManager implements Manager {
     }
 
     @Override
-    public void deleteVacation(Long userId, Long vacationId) throws RESTFullException {
+    @CanModifyVacation
+    public void deleteVacation(Long vacationId) throws RESTFullException {
         try {
-            User user = userRepository.getUser(userId);
             Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationId);
 
             if (!vacation.isPresent()) {
@@ -278,6 +281,7 @@ public class ApiManager implements Manager {
             }
 
             Vacation vacationDay = vacation.get();
+            User user = userRepository.getUser(vacationDay.getUserId());
 
             if (vacationDay.getDate().isAfter(LocalDate.now())) {
                 if (!vacationDay.getStatus().equals(Status.REJECTED)) {
diff --git a/server/src/main/java/org/danekja/ymanager/business/Manager.java b/server/src/main/java/org/danekja/ymanager/business/Manager.java
index 4c431a5..2591676 100644
--- a/server/src/main/java/org/danekja/ymanager/business/Manager.java
+++ b/server/src/main/java/org/danekja/ymanager/business/Manager.java
@@ -25,9 +25,9 @@ public interface Manager {
 
     void changeSettings(Long userId, UserSettings settings) throws RESTFullException;
 
-    void changeVacation(Long userId, VacationDay vacationDay) throws RESTFullException;
+    void changeVacation(VacationDay vacationDay) throws RESTFullException;
 
     void changeRequest(RequestType type, BasicRequest request) throws RESTFullException;
 
-    void deleteVacation(Long userId, Long vacationId) throws RESTFullException;
+    void deleteVacation(Long vacationId) throws RESTFullException;
 }
diff --git a/server/src/main/java/org/danekja/ymanager/business/auth/anot/CanModifyVacation.java b/server/src/main/java/org/danekja/ymanager/business/auth/anot/CanModifyVacation.java
new file mode 100644
index 0000000..a236fda
--- /dev/null
+++ b/server/src/main/java/org/danekja/ymanager/business/auth/anot/CanModifyVacation.java
@@ -0,0 +1,20 @@
+package org.danekja.ymanager.business.auth.anot;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+
+import java.lang.annotation.*;
+
+/**
+ * Methods annotated with this interface can be called either by EMPLOYER role
+ * or owner of the given vacation instance.
+ * <p>
+ * This annotation expects method to have {@code Long vacationId} argument.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+@PreAuthorize("hasPermission(#vacationId, 'Vacation', null)")
+public @interface CanModifyVacation {
+
+}
diff --git a/server/src/main/java/org/danekja/ymanager/business/auth/permissions/VacationModifyPermisionEvaluator.java b/server/src/main/java/org/danekja/ymanager/business/auth/permissions/VacationModifyPermisionEvaluator.java
new file mode 100644
index 0000000..3dc7e24
--- /dev/null
+++ b/server/src/main/java/org/danekja/ymanager/business/auth/permissions/VacationModifyPermisionEvaluator.java
@@ -0,0 +1,71 @@
+package org.danekja.ymanager.business.auth.permissions;
+
+import org.danekja.ymanager.domain.User;
+import org.danekja.ymanager.domain.UserRole;
+import org.danekja.ymanager.domain.Vacation;
+import org.danekja.ymanager.repository.VacationRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * {@link TypedPermissionEvaluator} for checking permissions for {@link Vacation} objects.
+ * <p>
+ * Invoke via {@code hasPermissions(<id parameter name>, 'Vacation', null)} permission syntax in security annotations.
+ */
+@Component
+public class VacationModifyPermisionEvaluator implements TypedPermissionEvaluator {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VacationModifyPermisionEvaluator.class);
+
+    private final VacationRepository vacationRepository;
+
+    @Autowired
+    public VacationModifyPermisionEvaluator(VacationRepository vacationRepository) {
+        this.vacationRepository = vacationRepository;
+    }
+
+    @Override
+    public String getTargetType() {
+        return Vacation.class.getSimpleName();
+    }
+
+    @Override
+    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
+        if (targetDomainObject != null && Vacation.class.isAssignableFrom(targetDomainObject.getClass())) {
+            Vacation target = (Vacation) targetDomainObject;
+
+            LOG.debug("Checking permission of {} to vacation: {}", authentication.getName(), target.getId());
+
+            if (authentication.getAuthorities().parallelStream().anyMatch(o -> Objects.equals(o.getAuthority(), UserRole.EMPLOYER.name()))) {
+
+                LOG.debug("Permission of {} to vacation: {} granted to employer", authentication.getName(), target.getId());
+                return true;
+
+            } else {
+
+                boolean canDo = Objects.equals(target.getUserId(), ((User) authentication.getPrincipal()).getId());
+                LOG.debug("Permission of {} to vacation: {} for employee: {}", authentication.getName(), target.getId(), canDo);
+                return canDo;
+
+            }
+        }
+
+        LOG.warn("Checking permission of {} to vacation: {} failed - null or invalid type of object", authentication.getName(), targetDomainObject);
+        return false;
+    }
+
+    @Override
+    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
+        if (targetId != null && Long.class.isAssignableFrom(targetId.getClass()) && Objects.equals(Vacation.class.getSimpleName(), targetType)) {
+            return hasPermission(authentication, vacationRepository.getVacationDay((Long) targetId).orElse(null), permission);
+        }
+
+        return false;
+    }
+}
diff --git a/server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java b/server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java
index cc43acd..ab429b9 100644
--- a/server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java
+++ b/server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java
@@ -139,13 +139,12 @@ public class ApiController extends BaseController {
     @RequestMapping(value = "/user/calendar/edit", method=PUT)
     public ResponseEntity userCalendarEdit(
             @RequestParam(value = "lang", required = false) String lang,
-            @RequestBody VacationDay vacationDay,
-            Authentication auth)
+            @RequestBody VacationDay vacationDay)
     {
-        //TODO make api endpoint contain userId in path as part of #39, also drop the edit part of path
+        //TODO make api endpoint point to vacation endpoint as part of #39, also drop the edit part of path
         //TODO drop the auth parameter afterwards
         return handle(Language.getLanguage(lang), () ->
-                manager.changeVacation(((User) auth.getPrincipal()).getId(), vacationDay)
+                manager.changeVacation(vacationDay)
         );
     }
 
@@ -164,11 +163,11 @@ public class ApiController extends BaseController {
 
     @RequestMapping(value = "/calendar/{id}/delete", method=DELETE)
     public ResponseEntity calendarDelete(
-            @PathVariable("id") String id,
+            @PathVariable("id") Long id,
             @RequestParam(value = "lang", required = false) String lang)
     {
         return handle(Language.getLanguage(lang), () ->
-            manager.deleteVacation(getUserId("me"), StringUtils.isNumeric(id) ? Long.parseLong(id) : -1)
+                manager.deleteVacation(id)
         );
     }
 
