controllers fixed and updated for guest or host checks

This commit is contained in:
Tommaso Rodolfo Masera 2020-04-22 17:17:07 +02:00
parent a428d57fe1
commit c6d5a1acd7
7 changed files with 128 additions and 111 deletions

View File

@ -17,13 +17,26 @@ import org.springframework.web.bind.annotation.*;
@RestController @RestController
@EnableAutoConfiguration @EnableAutoConfiguration
@RequestMapping("/dimmableLight") @RequestMapping("/dimmableLight")
public class DimmableLightController { public class DimmableLightController extends GuestEnabledController<DimmableLight> {
@Autowired private UserRepository userRepository; private DimmableLightRepository dimmableLightRepository;
@Autowired private DimmableLightRepository dimmableLightRepository; private SceneRepository sceneRepository;
@Autowired private SceneRepository sceneRepository; private StateRepository<State<?>> stateRepository;
@Autowired private StateRepository<State<?>> stateRepository; private DeviceService deviceService;
@Autowired private DeviceService deviceService;
@Autowired
public DimmableLightController(
UserRepository userRepository,
DimmableLightRepository dimmableLightRepository,
SceneRepository sceneRepository,
StateRepository<State<?>> stateRepository,
DeviceService deviceService) {
super(userRepository, dimmableLightRepository);
this.dimmableLightRepository = dimmableLightRepository;
this.sceneRepository = sceneRepository;
this.stateRepository = stateRepository;
this.deviceService = deviceService;
}
@GetMapping @GetMapping
public List<DimmableLight> findAll() { public List<DimmableLight> findAll() {
@ -60,31 +73,6 @@ public class DimmableLightController {
return save(new DimmableLight(), dl, principal.getName(), null); return save(new DimmableLight(), dl, principal.getName(), null);
} }
private DimmableLight fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId)
throws NotFoundException {
if (hostId == null) {
return dimmableLightRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
} else {
/*
* Slightly less extremely verbose check through various repositories to control user/guest authorization.
*/
DimmableLight dl =
dimmableLightRepository
.findByIdAndUserId(id, hostId)
.orElseThrow(NotFoundException::new);
User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
User guest = userRepository.findByUsername(principal.getName());
dl.setFromHost(true);
if (!host.getGuests().contains(guest)) {
throw new NotFoundException();
} else {
return dl;
}
}
}
/* /*
Logic for saving either as owner or guest is handled in method save of this controller Logic for saving either as owner or guest is handled in method save of this controller
*/ */

View File

@ -0,0 +1,37 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.returnIfGuest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
public abstract class GuestEnabledController<T extends Device> {
private UserRepository userRepository;
private DeviceRepository<T> deviceRepository;
public GuestEnabledController(
final UserRepository userRepository, final DeviceRepository<T> deviceRepository) {
this.userRepository = userRepository;
this.deviceRepository = deviceRepository;
}
protected T fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId)
throws NotFoundException {
if (hostId == null) {
return deviceRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
} else {
/*
* Slightly less extremely verbose check through various repositories to control user/guest authorization.
*/
T device =
deviceRepository
.findByIdAndUserId(id, hostId)
.orElseThrow(NotFoundException::new);
return returnIfGuest(userRepository, device, hostId, principal);
}
}
}

View File

@ -25,42 +25,35 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@EnableAutoConfiguration @EnableAutoConfiguration
@RequestMapping("/regularLight") @RequestMapping("/regularLight")
public class RegularLightController { public class RegularLightController extends GuestEnabledController<RegularLight> {
@Autowired private UserRepository userRepository; private RegularLightRepository regularLightRepository;
@Autowired private RegularLightRepository regularLightService; private SceneRepository sceneRepository;
@Autowired private SceneRepository sceneRepository; private StateRepository<State<?>> stateRepository;
@Autowired private StateRepository<State<?>> stateRepository; private DeviceService deviceService;
@Autowired private DeviceService deviceService;
private RegularLight fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId) @Autowired
throws NotFoundException { public RegularLightController(
if (hostId == null) { UserRepository userRepository,
return regularLightService.findById(id).orElseThrow(NotFoundException::new); RegularLightRepository regularLightRepository,
} else { SceneRepository sceneRepository,
RegularLight rl = StateRepository<State<?>> stateRepository,
regularLightService DeviceService deviceService) {
.findByIdAndUserId(id, hostId) super(userRepository, regularLightRepository);
.orElseThrow(NotFoundException::new); this.regularLightRepository = regularLightRepository;
User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new); this.sceneRepository = sceneRepository;
User guest = userRepository.findByUsername(principal.getName()); this.stateRepository = stateRepository;
rl.setFromHost(true); this.deviceService = deviceService;
if (!host.getGuests().contains(guest)) {
throw new NotFoundException();
} else {
return rl;
}
}
} }
@GetMapping @GetMapping
public List<RegularLight> findAll() { public List<RegularLight> findAll() {
return toList(regularLightService.findAll()); return toList(regularLightRepository.findAll());
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public RegularLight findById(@PathVariable("id") long id) throws NotFoundException { public RegularLight findById(@PathVariable("id") long id) throws NotFoundException {
return regularLightService.findById(id).orElseThrow(NotFoundException::new); return regularLightRepository.findById(id).orElseThrow(NotFoundException::new);
} }
private RegularLight save( private RegularLight save(
@ -97,10 +90,10 @@ public class RegularLightController {
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void delete(@PathVariable("id") long id) { public void delete(@PathVariable("id") long id) {
regularLightService.deleteById(id); regularLightRepository.deleteById(id);
} }
// the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId} // the full url should be: "/regularLight/{id}/state?sceneId={sceneId}
// however it is not necessary to specify the query in the mapping // however it is not necessary to specify the query in the mapping
@PostMapping("/{id}/state") @PostMapping("/{id}/state")
public State<? extends Switchable> sceneBinding( public State<? extends Switchable> sceneBinding(
@ -109,7 +102,7 @@ public class RegularLightController {
final Principal principal) final Principal principal)
throws NotFoundException, DuplicateStateException { throws NotFoundException, DuplicateStateException {
RegularLight d = RegularLight d =
regularLightService regularLightRepository
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Switchable> s = d.cloneState(); State<? extends Switchable> s = d.cloneState();

View File

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RoomSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils;
import java.security.Principal; import java.security.Principal;
import java.util.*; import java.util.*;
import javax.validation.Valid; import javax.validation.Valid;
@ -37,13 +38,7 @@ public class RoomController {
if (hostId == null) { if (hostId == null) {
return list; return list;
} else { } else {
User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); return Utils.returnIfGuest(userRepository, list, hostId, principal);
User guest = userRepository.findByUsername(principal.getName());
if (!host.getGuests().contains(guest)) {
throw new NotFoundException();
} else {
return list;
}
} }
} }

View File

@ -68,6 +68,16 @@ public abstract class Device {
@Transient @GsonExclude private boolean fromHost = false; @Transient @GsonExclude private boolean fromHost = false;
@Transient @GsonExclude private boolean fromGuest = false;
public boolean isFromGuest() {
return fromGuest;
}
public void setFromGuest(boolean fromGuest) {
this.fromGuest = fromGuest;
}
public void setFromHost(boolean fromHost) { public void setFromHost(boolean fromHost) {
this.fromHost = fromHost; this.fromHost = fromHost;
} }

View File

@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service; package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.beans.Transient;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -16,56 +14,54 @@ import org.springframework.stereotype.Component;
public class DeviceService { public class DeviceService {
// FIXME: TO BE MERGED WITH USER STORY 5 (MATTEO'S STUFF) // FIXME: TO BE MERGED WITH USER STORY 5 (MATTEO'S STUFF)
@Autowired DeviceRepository<Device> deviceRepository; @Autowired private DeviceRepository<Device> deviceRepository;
@Autowired UserRepository userRepository; @Autowired private UserRepository userRepository;
@Autowired SensorSocketEndpoint endpoint; @Autowired private SensorSocketEndpoint endpoint;
/* public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
TODO: remember to put a @Transient @GsonIgnore (but not @SocketGsonIgnore) property on device to signal a device update
TODO: coming from DeviceService.saveAsGuest()
*/
public <T extends Device> T saveAsGuest(
@Transient @GsonExclude T device, String guestUsername, Long hostId)
throws NotFoundException { throws NotFoundException {
device = deviceRepository.save(device);
final User currentUser = userRepository.findByUsername(guestUsername); final User currentUser = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
final Set<User> guests = host.getGuests(); final Set<User> guests = Set.copyOf(host.getGuests());
// filter out currentUser from guests as we do not want to broadcast an update to the // We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
// updating user itself // true.
if (guests.contains(currentUser)) {
guests.remove(currentUser);
}
// broadcasting from not a host
device.setFromHost(false); device.setFromHost(false);
// broadcast device update for host device.setFromGuest(true);
// broadcast device update to host
endpoint.queueDeviceUpdate(device, host); endpoint.queueDeviceUpdate(device, host);
userRepository.save(host);
endpoint.flushDeviceUpdates(); // We're telling all guests that a higher entity has issued a device update. Therefore,
// fromHost becomes true.
device.setFromHost(true);
device.setFromGuest(false);
for (final User guest : guests) { for (final User guest : guests) {
if (guest.equals(currentUser)) {
continue;
}
// enqueue all device updates for all other guests // enqueue all device updates for all other guests
endpoint.queueDeviceUpdate(device, guest); endpoint.queueDeviceUpdate(device, guest);
userRepository.save(guest);
} }
// broadcast device updates for all other guests // broadcast device updates for all other guests
endpoint.flushDeviceUpdates(); return device;
return deviceRepository.save(device);
} }
public <T extends Device> T saveAsOwner(T device, String username) { public <T extends Device> T saveAsOwner(T device, String username) {
device = deviceRepository.save(device);
final User user = userRepository.findByUsername(username); final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests(); final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host // make sure we're broadcasting from host
device.setFromHost(true); device.setFromHost(true);
device.setFromGuest(false);
for (final User guest : guests) { for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest // broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest); endpoint.queueDeviceUpdate(device, guest);
userRepository.save(guest);
} }
// after queueing the device update for each user, flush them all in a single message // after queueing the device update for each user, flush them all in a single message
// can be moved inside the foreach loop to send a single message for each update enqueued // can be moved inside the foreach loop to send a single message for each update enqueued
endpoint.flushDeviceUpdates(); return device;
return deviceRepository.save(device);
} }
} }

View File

@ -1,7 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.utils; package ch.usi.inf.sa4.sanmarinoes.smarthut.utils;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import java.security.Principal;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -9,24 +12,19 @@ import java.util.stream.StreamSupport;
public final class Utils { public final class Utils {
private Utils() {} private Utils() {}
@FunctionalInterface public static <U> U returnIfGuest(
public interface ConsumerWithException<T> { UserRepository userRepository, U toReturn, Long hostId, Principal principal)
void apply(T input) throws Throwable; throws NotFoundException {
User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
User guest = userRepository.findByUsername(principal.getName());
if (!host.getGuests().contains(guest)) {
throw new NotFoundException();
} else {
return toReturn;
}
} }
public static <T> List<T> toList(Iterable<? extends T> iterable) { public static <T> List<T> toList(Iterable<? extends T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
} }
public static <T> Predicate<T> didThrow(ConsumerWithException<T> consumer) {
return (t) -> {
try {
consumer.apply(t);
return true;
} catch (Throwable e) {
System.err.println(e.getMessage());
return false;
}
};
}
} }