Merge branch 'sonar-test' into 'dev'

on error fixing for sonarqube code smells and vulnerabilities check

See merge request sa4-2020/the-sanmarinoes/backend!115
This commit is contained in:
Claudio Maggioni 2020-05-07 10:39:18 +02:00
commit 5a31f4cf98
25 changed files with 354 additions and 245 deletions

View file

@ -2,6 +2,7 @@ plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE' id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id "de.aaschmid.cpd" version "3.1" id "de.aaschmid.cpd" version "3.1"
id "org.sonarqube" version "2.7"
id 'java' id 'java'
} }
group = 'ch.usi.inf.sa4.sanmarinoes' group = 'ch.usi.inf.sa4.sanmarinoes'
@ -49,4 +50,4 @@ gradle.projectsEvaluated {
test { test {
useJUnitPlatform() useJUnitPlatform()
} }

4
gradle.properties Normal file
View file

@ -0,0 +1,4 @@
systemProp.sonar.host.url=https://lab.si.usi.ch:9000
systemProp.sonar.login=871fdfcb09345b1841f1730596ac32aacf3a86fb
systemProp.sonar.projectKey=SMASmarthutBackend
systemProp.sonar.scm.disabled=true

View file

@ -46,67 +46,67 @@ public class EmailConfigurationService {
@NotNull private String registrationRedirect; @NotNull private String registrationRedirect;
public String getRegistrationSubject() { public synchronized String getRegistrationSubject() {
return registrationSubject; return registrationSubject;
} }
public void setRegistrationSubject(String registrationSubject) { public synchronized void setRegistrationSubject(String registrationSubject) {
this.registrationSubject = registrationSubject; this.registrationSubject = registrationSubject;
} }
public String getRegistration() { public synchronized String getRegistration() {
return registration; return registration;
} }
public void setRegistration(String registration) { public synchronized void setRegistration(String registration) {
this.registration = registration; this.registration = registration;
} }
public String getRegistrationPath() { public synchronized String getRegistrationPath() {
return registrationPath; return registrationPath;
} }
public void setRegistrationPath(String registrationPath) { public synchronized void setRegistrationPath(String registrationPath) {
this.registrationPath = registrationPath; this.registrationPath = registrationPath;
} }
public String getResetPasswordSubject() { public synchronized String getResetPasswordSubject() {
return resetPasswordSubject; return resetPasswordSubject;
} }
public void setResetPasswordSubject(String resetPasswordSubject) { public synchronized void setResetPasswordSubject(String resetPasswordSubject) {
this.resetPasswordSubject = resetPasswordSubject; this.resetPasswordSubject = resetPasswordSubject;
} }
public String getResetPassword() { public synchronized String getResetPassword() {
return resetPassword; return resetPassword;
} }
public void setResetPassword(String resetPassword) { public synchronized void setResetPassword(String resetPassword) {
this.resetPassword = resetPassword; this.resetPassword = resetPassword;
} }
public String getResetPasswordPath() { public synchronized String getResetPasswordPath() {
return resetPasswordPath; return resetPasswordPath;
} }
public void setResetPasswordPath(String resetPasswordPath) { public synchronized void setResetPasswordPath(String resetPasswordPath) {
this.resetPasswordPath = resetPasswordPath; this.resetPasswordPath = resetPasswordPath;
} }
public String getResetPasswordRedirect() { public synchronized String getResetPasswordRedirect() {
return resetPasswordRedirect; return resetPasswordRedirect;
} }
public void setResetPasswordRedirect(String resetPasswordRedirect) { public synchronized void setResetPasswordRedirect(String resetPasswordRedirect) {
this.resetPasswordRedirect = resetPasswordRedirect; this.resetPasswordRedirect = resetPasswordRedirect;
} }
public String getRegistrationRedirect() { public synchronized String getRegistrationRedirect() {
return registrationRedirect; return registrationRedirect;
} }
public void setRegistrationRedirect(String registrationRedirect) { public synchronized void setRegistrationRedirect(String registrationRedirect) {
this.registrationRedirect = registrationRedirect; this.registrationRedirect = registrationRedirect;
} }
} }

View file

@ -233,9 +233,9 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
final Map<String, TypeAdapter<?>> labelToDelegate = final Map<String, TypeAdapter<?>> labelToDelegate =
new LinkedHashMap<String, TypeAdapter<?>>(); new LinkedHashMap<String, TypeAdapter<?>>(labelToSubtype.size());
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate =
new LinkedHashMap<Class<?>, TypeAdapter<?>>(); new LinkedHashMap<Class<?>, TypeAdapter<?>>(labelToSubtype.size());
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) { for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = TypeAdapter<?> delegate =
gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
@ -245,7 +245,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
return new TypeAdapter<R>() { return new TypeAdapter<R>() {
@Override @Override
public R read(JsonReader in) throws IOException { public R read(JsonReader in) {
JsonElement jsonElement = Streams.parse(in); JsonElement jsonElement = Streams.parse(in);
JsonElement labelJsonElement; JsonElement labelJsonElement;
if (maintainType) { if (maintainType) {

View file

@ -6,16 +6,14 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@ -30,8 +28,6 @@ public class AuthenticationController {
private final JWTUserDetailsService userDetailsService; private final JWTUserDetailsService userDetailsService;
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public AuthenticationController( public AuthenticationController(
AuthenticationManager authenticationManager, AuthenticationManager authenticationManager,
UserRepository userRepository, UserRepository userRepository,
@ -82,9 +78,9 @@ public class AuthenticationController {
authenticationManager.authenticate( authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)); new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) { } catch (DisabledException e) {
throw new UnauthorizedException(true); throw new UnauthorizedException(true, e);
} catch (BadCredentialsException e) { } catch (BadCredentialsException e) {
throw new UnauthorizedException(false); throw new UnauthorizedException(false, e);
} }
} }
} }

View file

@ -33,6 +33,22 @@ public abstract class InputDeviceConnectionController<
this.input = input; this.input = input;
this.outputs = outputs; this.outputs = outputs;
} }
public I getInput() {
return input;
}
public List<O> getOutputs() {
return outputs;
}
}
protected DeviceRepository<I> getInputRepository() {
return inputRepository;
}
protected DeviceRepository<O> getOutputReposiory() {
return outputReposiory;
} }
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@ -65,7 +81,7 @@ public abstract class InputDeviceConnectionController<
inputRepository inputRepository
.findByIdAndUsername(inputId, username) .findByIdAndUsername(inputId, username)
.orElseThrow(() -> new NotFoundException("input device")); .orElseThrow(() -> new NotFoundException("input device"));
final List<O> outputDevices = new ArrayList<>(); final List<O> outputDevices = new ArrayList<>(outputs.size());
for (final Long outputId : outputs) { for (final Long outputId : outputs) {
outputDevices.add( outputDevices.add(
outputReposiory outputReposiory
@ -87,12 +103,12 @@ public abstract class InputDeviceConnectionController<
Long inputId, List<Long> outputs, String username) throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputs, username); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.outputs) { for (final O o : pair.getOutputs()) {
connector.connect(pair.input, o, true); connector.connect(pair.getInput(), o, true);
} }
deviceService.saveAllAsOwner(pair.outputs, username); deviceService.saveAllAsOwner(pair.getOutputs(), username);
return pair.input.getOutputs(); return pair.getInput().getOutputs();
} }
/** /**
@ -107,12 +123,12 @@ public abstract class InputDeviceConnectionController<
Long inputId, List<Long> outputs, String username) throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputs, username); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.outputs) { for (final O o : pair.getOutputs()) {
connector.connect(pair.input, o, false); connector.connect(pair.getInput(), o, false);
} }
deviceService.saveAllAsOwner(pair.outputs, username); deviceService.saveAllAsOwner(pair.getOutputs(), username);
return pair.input.getOutputs(); return pair.getInput().getOutputs();
} }
@PostMapping("/{id}/lights") @PostMapping("/{id}/lights")

View file

@ -17,14 +17,17 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/knobDimmer") @RequestMapping("/knobDimmer")
public class KnobDimmerController extends InputDeviceConnectionController<KnobDimmer, Dimmable> { public class KnobDimmerController extends InputDeviceConnectionController<KnobDimmer, Dimmable> {
@Autowired private DeviceService deviceService; private final DeviceService deviceService;
@Autowired private KnobDimmerRepository knobDimmerRepository; private final KnobDimmerRepository knobDimmerRepository;
@Autowired @Autowired
protected KnobDimmerController( protected KnobDimmerController(
KnobDimmerRepository inputRepository, DimmableRepository<Dimmable> outputRepository) { KnobDimmerRepository inputRepository,
DimmableRepository<Dimmable> outputRepository,
DeviceService deviceService) {
super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR); super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR);
this.knobDimmerRepository = inputRepository; this.knobDimmerRepository = inputRepository;
this.deviceService = deviceService;
} }
@GetMapping("/{id}") @GetMapping("/{id}")

View file

@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.*;
public class SwitchController extends InputDeviceConnectionController<Switch, Switchable> { public class SwitchController extends InputDeviceConnectionController<Switch, Switchable> {
private SwitchRepository switchRepository; private SwitchRepository switchRepository;
private SwitchableRepository<Switchable> switchableRepository;
private DeviceService deviceService; private DeviceService deviceService;
/** /**

View file

@ -36,8 +36,7 @@ public class ThermostatController {
newT = thermostatRepository.save(newT); newT = thermostatRepository.save(newT);
newT.setOn(t.isTurnOn()); newT.setOn(t.isTurnOn());
newT = deviceService.saveAsOwner(newT, principal.getName()); return deviceService.saveAsOwner(newT, principal.getName());
return newT;
} }
@PostMapping @PostMapping

View file

@ -2,7 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
@ -78,7 +77,7 @@ public class UserAccountController {
* @throws DuplicateRegistrationException if a user exists with same email or username * @throws DuplicateRegistrationException if a user exists with same email or username
*/ */
@PostMapping @PostMapping
public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData) public void registerUser(@Valid @RequestBody UserRegistrationRequest registrationData)
throws DuplicateRegistrationException { throws DuplicateRegistrationException {
final User existingEmailUser = final User existingEmailUser =
userRepository.findByEmailIgnoreCase(registrationData.getEmail()); userRepository.findByEmailIgnoreCase(registrationData.getEmail());
@ -112,8 +111,6 @@ public class UserAccountController {
confirmationTokenRepository.save(token); confirmationTokenRepository.save(token);
sendEmail(toSave.getEmail(), token, true); sendEmail(toSave.getEmail(), token, true);
return new OkResponse();
} }
} }
@ -125,7 +122,7 @@ public class UserAccountController {
* @throws UserNotFoundException if given email does not belong to any user * @throws UserNotFoundException if given email does not belong to any user
*/ */
@PostMapping("/init-reset-password") @PostMapping("/init-reset-password")
public OkResponse initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest) public void initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest)
throws UserNotFoundException { throws UserNotFoundException {
final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail()); final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail());
@ -148,8 +145,6 @@ public class UserAccountController {
confirmationTokenRepository.save(token); confirmationTokenRepository.save(token);
sendEmail(toReset.getEmail(), token, false); sendEmail(toReset.getEmail(), token, false);
return new OkResponse();
} }
/** /**
@ -160,7 +155,7 @@ public class UserAccountController {
* @throws EmailTokenNotFoundException if given token is not a valid token for password reset * @throws EmailTokenNotFoundException if given token is not a valid token for password reset
*/ */
@PutMapping("/reset-password") @PutMapping("/reset-password")
public OkResponse resetPassword( public void resetPassword(
@Valid @RequestBody PasswordResetRequest resetRequest, @Valid @RequestBody PasswordResetRequest resetRequest,
final HttpServletResponse response) final HttpServletResponse response)
throws EmailTokenNotFoundException, IOException { throws EmailTokenNotFoundException, IOException {
@ -178,8 +173,6 @@ public class UserAccountController {
// Delete token to prevent further password changes // Delete token to prevent further password changes
confirmationTokenRepository.delete(token); confirmationTokenRepository.delete(token);
return new OkResponse();
} }
/** /**

View file

@ -1,6 +0,0 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
/** A dummy DTO to return when there is no data to return */
public class OkResponse {
private boolean success = true;
}

View file

@ -16,4 +16,16 @@ public class UserResponse {
us.username = u.getUsername(); us.username = u.getUsername();
return us; return us;
} }
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getName() {
return name;
}
} }

View file

@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ResponseStatus;
public class UnauthorizedException extends Exception { public class UnauthorizedException extends Exception {
private final boolean isUserDisabled; private final boolean isUserDisabled;
public UnauthorizedException(boolean isDisabled) { public UnauthorizedException(boolean isDisabled, Throwable cause) {
super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials")); super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials"), cause);
this.isUserDisabled = isDisabled; this.isUserDisabled = isDisabled;
} }

View file

@ -32,7 +32,7 @@ public class ConfirmationToken {
private User user; private User user;
@Column(nullable = false) @Column(nullable = false)
private Boolean resetPassword; private boolean resetPassword;
public ConfirmationToken(User user) { public ConfirmationToken(User user) {
this.user = user; this.user = user;
@ -76,11 +76,11 @@ public class ConfirmationToken {
this.user = user; this.user = user;
} }
public Boolean getResetPassword() { public boolean getResetPassword() {
return resetPassword; return resetPassword;
} }
public void setResetPassword(Boolean resetPassword) { public void setResetPassword(boolean resetPassword) {
this.resetPassword = resetPassword; this.resetPassword = resetPassword;
} }
} }

View file

@ -15,9 +15,9 @@ public class Sensor extends InputDevice implements RangeTriggerable {
public static final Map<SensorType, BigDecimal> TYPICAL_VALUES = public static final Map<SensorType, BigDecimal> TYPICAL_VALUES =
Map.of( Map.of(
SensorType.TEMPERATURE, new BigDecimal(17.0), SensorType.TEMPERATURE, BigDecimal.valueOf(17.0),
SensorType.HUMIDITY, new BigDecimal(40.0), SensorType.HUMIDITY, BigDecimal.valueOf(40.0),
SensorType.LIGHT, new BigDecimal(1000)); SensorType.LIGHT, BigDecimal.valueOf(1000));
@Override @Override
public double readTriggerState() { public double readTriggerState() {

View file

@ -6,11 +6,9 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Transient; import javax.persistence.Transient;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.springframework.stereotype.Component;
/** A thermostat capable of controlling cooling and heating. */ /** A thermostat capable of controlling cooling and heating. */
@Entity @Entity
@Component
public class Thermostat extends Switchable implements BooleanTriggerable { public class Thermostat extends Switchable implements BooleanTriggerable {
@Override @Override

View file

@ -56,7 +56,7 @@ public class User {
@Column(nullable = false) @Column(nullable = false)
@GsonExclude @GsonExclude
private Boolean isEnabled = false; private boolean isEnabled = false;
public Long getId() { public Long getId() {
return id; return id;
@ -98,11 +98,11 @@ public class User {
this.password = password; this.password = password;
} }
public Boolean getEnabled() { public boolean getEnabled() {
return isEnabled; return isEnabled;
} }
public void setEnabled(Boolean enabled) { public void setEnabled(boolean enabled) {
isEnabled = enabled; isEnabled = enabled;
} }
@ -162,16 +162,17 @@ public class User {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
User user = (User) o; User user = (User) o;
return id.equals(user.id) return cameraEnabled == user.cameraEnabled
&& name.equals(user.name) && isEnabled == user.isEnabled
&& username.equals(user.username) && Objects.equals(id, user.id)
&& password.equals(user.password) && Objects.equals(name, user.name)
&& email.equals(user.email) && Objects.equals(username, user.username)
&& isEnabled.equals(user.isEnabled); && Objects.equals(password, user.password)
&& Objects.equals(email, user.email);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, name, username, password, email, isEnabled); return Objects.hash(id, name, username, password, email, isEnabled, cameraEnabled);
} }
} }

View file

@ -0,0 +1,20 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Thermostat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DevicePopulationService {
@Autowired private ThermostatService thermostatService;
public void populateComputedFields(Iterable<Device> devices) {
for (Device d : devices) {
if (d instanceof Thermostat) {
thermostatService.populateMeasuredTemperature((Thermostat) d);
}
}
}
}

View file

@ -0,0 +1,141 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DevicePropagationService {
@Autowired private SensorSocketEndpoint endpoint;
@Autowired private EagerUserRepository userRepository;
@Autowired private DeviceRepository<Device> deviceRepository;
void propagateUpdateAsGuest(Device device, User host, User guest) {
final Set<User> guests = Set.copyOf(host.getGuests());
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
// true.
// broadcast device update to host
endpoint.queueDeviceUpdate(device, host, true, null, false);
// We're telling all guests that a higher entity has issued a device update. Therefore,
// fromHost becomes true.
for (final User aGuest : guests) {
if (aGuest.equals(guest)) {
continue;
}
// enqueue all device updates for all other guests
endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false);
}
}
void saveAllAsGuestSceneApplication(List<Device> devices, String guestUsername, Long hostId) {
final User guest = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
deviceRepository.saveAll(devices);
devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest));
}
void renameIfDuplicate(Device toCreate, String username) {
while (deviceRepository.findDuplicates(toCreate.getName(), username)
- (toCreate.getId() <= 0 ? 0 : 1)
> 0) {
toCreate.setName(toCreate.getName() + " (new)");
}
}
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
throws NotFoundException {
final User currentUser = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
if (!host.getGuests().contains(currentUser)) throw new NotFoundException();
renameIfDuplicate(device, host.getUsername());
device = deviceRepository.save(device);
propagateUpdateAsGuest(device, host, currentUser);
return device;
}
/**
* Saves all the devices given in devices assuming that the owner updated them in one way or
* another. Takes care of the appropriate websocket updates and trigger checking as well. No
* checking is done to verify that the user whose username is given is in fact the owner of
* these devices
*
* @param devices the list of devices to save
* @param username the username of the owner of these devices
* @param fromScene true if the update comes from the a scene application side effect. Disables
* trigger checking to avoid recursive invocations of automations
* @param fromTrigger true if the update comes from a scene application executed by an
* automation. Propagates updated through socket to owner as well. No effect if fromScene is
* false.
* @param <T> the type of device contained in the list
* @return the updated list of devices, ready to be fed to GSON
*/
public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
devices.forEach(d -> renameIfDuplicate(d, username));
devices = deviceRepository.saveAll(devices);
devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger));
return toList(devices);
}
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
return saveAllAsOwner(devices, username, false, false);
}
public <T extends Device> T saveAsOwner(T device, String username) {
renameIfDuplicate(device, username);
device = deviceRepository.save(device);
propagateUpdateAsOwner(device, username, false);
return device;
}
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
Device d =
deviceRepository
.findByIdAndUsername(id, username)
.orElseThrow(NotFoundException::new);
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true);
}
deviceRepository.delete(d);
}
/**
* Propagates the update through the socket assuming that the user that modified the device is
* the owner of that device
*
* @param device the updated device
* @param username the username of the owner of that device
* @param causedByTrigger if true, send the update to the owner as well
*/
private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) {
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false);
}
if (causedByTrigger) {
endpoint.queueDeviceUpdate(device, user, false, user.getId(), false);
}
}
}

View file

@ -4,11 +4,9 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
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.socket.SensorSocketEndpoint;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -17,28 +15,42 @@ import org.springframework.stereotype.Component;
@Component @Component
public class DeviceService { public class DeviceService {
@Autowired private DeviceRepository<Device> deviceRepository; private final DeviceRepository<Device> deviceRepository;
@Autowired private AutomationRepository automationRepository; private final AutomationRepository automationRepository;
@Autowired private SceneRepository sceneRepository; private final SceneRepository sceneRepository;
@Autowired private SceneService sceneService; private final SceneService sceneService;
@Autowired private TriggerRepository<Trigger<? extends Device>> triggerRepository; private final TriggerRepository<Trigger<? extends Device>> triggerRepository;
@Autowired private RoomRepository roomRepository; private final RoomRepository roomRepository;
@Autowired private EagerUserRepository userRepository; private final EagerUserRepository userRepository;
@Autowired private SensorSocketEndpoint endpoint; private final DevicePopulationService devicePopulationService;
@Autowired private ThermostatService thermostatService; private final DevicePropagationService devicePropagationService;
@Autowired
public DeviceService(
DeviceRepository<Device> deviceRepository,
AutomationRepository automationRepository,
SceneRepository sceneRepository,
SceneService sceneService,
TriggerRepository<Trigger<? extends Device>> triggerRepository,
RoomRepository roomRepository,
EagerUserRepository userRepository,
DevicePopulationService devicePopulationService,
DevicePropagationService devicePropagationService) {
this.deviceRepository = deviceRepository;
this.automationRepository = automationRepository;
this.sceneRepository = sceneRepository;
this.sceneService = sceneService;
this.triggerRepository = triggerRepository;
this.roomRepository = roomRepository;
this.userRepository = userRepository;
this.devicePopulationService = devicePopulationService;
this.devicePropagationService = devicePropagationService;
}
public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException { public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException {
roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new); roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new);
} }
private void renameIfDuplicate(Device toCreate, String username) {
while (deviceRepository.findDuplicates(toCreate.getName(), username)
- (toCreate.getId() <= 0 ? 0 : 1)
> 0) {
toCreate.setName(toCreate.getName() + " (new)");
}
}
private void triggerTriggers(Device device, final String username) { private void triggerTriggers(Device device, final String username) {
final long deviceId = device.getId(); final long deviceId = device.getId();
@ -66,122 +78,24 @@ public class DeviceService {
return findAll(null, hostId, username); return findAll(null, hostId, username);
} }
public List<Device> findAll(Long roomId, Long hostId, String username)
throws NotFoundException {
try {
Iterable<Device> devices;
User host = null;
if (hostId == null) {
if (roomId != null) {
roomRepository
.findByIdAndUsername(roomId, username)
.orElseThrow(NotFoundException::new);
devices = deviceRepository.findByRoomId(roomId);
} else {
devices = deviceRepository.findAllByUsername(username);
}
} else {
final User guest = userRepository.findByUsername(username);
host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
if (!guest.getHosts().contains(host)) {
throw new NotFoundException();
}
if (roomId != null) {
Room r = roomRepository.findById(roomId).orElseThrow(NotFoundException::new);
if (!r.getUserId().equals(hostId)) {
throw new NotFoundException();
}
devices = deviceRepository.findByRoomId(roomId);
} else {
devices = deviceRepository.findAllByUsername(host.getUsername());
}
}
populateComputedFields(devices);
if (host != null && !host.isCameraEnabled()) {
return StreamSupport.stream(devices.spliterator(), true)
.filter(d -> !(d instanceof SecurityCamera))
.collect(Collectors.toList());
} else {
return toList(devices);
}
} catch (NotFoundException e) {
e.printStackTrace();
throw e;
}
}
public void populateComputedFields(Iterable<Device> devices) {
for (Device d : devices) {
if (d instanceof Thermostat) {
thermostatService.populateMeasuredTemperature((Thermostat) d);
}
}
}
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId) public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
throws NotFoundException { throws NotFoundException {
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);
if (!host.getGuests().contains(currentUser)) throw new NotFoundException(); if (!host.getGuests().contains(currentUser)) throw new NotFoundException();
renameIfDuplicate(device, host.getUsername()); devicePropagationService.renameIfDuplicate(device, host.getUsername());
device = deviceRepository.save(device); device = deviceRepository.save(device);
propagateUpdateAsGuest(device, host, currentUser); devicePropagationService.propagateUpdateAsGuest(device, host, currentUser);
return device; return device;
} }
private void propagateUpdateAsGuest(Device device, User host, User guest) { public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
final Set<User> guests = Set.copyOf(host.getGuests()); devicePropagationService.deleteByIdAsOwner(id, username);
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
// true.
// broadcast device update to host
endpoint.queueDeviceUpdate(device, host, true, null, false);
// We're telling all guests that a higher entity has issued a device update. Therefore,
// fromHost becomes true.
for (final User aGuest : guests) {
if (aGuest.equals(guest)) {
continue;
}
// enqueue all device updates for all other guests
endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false);
}
} }
List<Device> saveAllAsGuestSceneApplication( public void populateComputedFields(Iterable<Device> devices) {
List<Device> devices, String guestUsername, Long hostId) { devicePopulationService.populateComputedFields(devices);
final User guest = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
deviceRepository.saveAll(devices);
devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest));
return devices;
}
/**
* Propagates the update through the socket assuming that the user that modified the device is
* the owner of that device
*
* @param device the updated device
* @param username the username of the owner of that device
* @param causedByTrigger if true, send the update to the owner as well
*/
private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) {
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false);
}
if (causedByTrigger) {
endpoint.queueDeviceUpdate(device, user, false, user.getId(), false);
}
} }
/** /**
@ -202,15 +116,12 @@ public class DeviceService {
*/ */
public <T extends Device> List<T> saveAllAsOwner( public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) { Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
devices.forEach(d -> renameIfDuplicate(d, username)); List<T> toReturn =
devices = deviceRepository.saveAll(devices); devicePropagationService.saveAllAsOwner(devices, username, fromScene, fromTrigger);
devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger));
if (!fromScene) { if (!fromScene) {
devices.forEach((d) -> triggerTriggers(d, username)); toReturn.forEach((d) -> this.triggerTriggers(d, username));
} }
return toReturn;
return toList(devices);
} }
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) { public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
@ -218,29 +129,51 @@ public class DeviceService {
} }
public <T extends Device> T saveAsOwner(T device, String username) { public <T extends Device> T saveAsOwner(T device, String username) {
renameIfDuplicate(device, username); T toReturn = devicePropagationService.saveAsOwner(device, username);
device = deviceRepository.save(device); triggerTriggers(toReturn, username);
propagateUpdateAsOwner(device, username, false);
triggerTriggers(device, username);
return device; return device;
} }
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException { public List<Device> findAll(Long roomId, Long hostId, String username)
Device d = throws NotFoundException {
deviceRepository Iterable<Device> devices;
.findByIdAndUsername(id, username) User host = null;
if (hostId == null) {
if (roomId != null) {
roomRepository
.findByIdAndUsername(roomId, username)
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
devices = deviceRepository.findByRoomId(roomId);
} else {
devices = deviceRepository.findAllByUsername(username);
}
} else {
final User guest = userRepository.findByUsername(username);
host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
final User user = userRepository.findByUsername(username); if (!guest.getHosts().contains(host)) {
final Set<User> guests = user.getGuests(); throw new NotFoundException();
// make sure we're broadcasting from host }
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest if (roomId != null) {
endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true); Room r = roomRepository.findById(roomId).orElseThrow(NotFoundException::new);
if (!r.getUserId().equals(hostId)) {
throw new NotFoundException();
}
devices = deviceRepository.findByRoomId(roomId);
} else {
devices = deviceRepository.findAllByUsername(host.getUsername());
}
} }
deviceRepository.delete(d); devicePopulationService.populateComputedFields(devices);
if (host != null && !host.isCameraEnabled()) {
return StreamSupport.stream(devices.spliterator(), true)
.filter(d -> !(d instanceof SecurityCamera))
.collect(Collectors.toList());
} else {
return toList(devices);
}
} }
} }

View file

@ -10,30 +10,31 @@ import org.springframework.stereotype.Component;
public class SceneService { public class SceneService {
@Autowired private DeviceRepository<Device> deviceRepository; @Autowired private DeviceRepository<Device> deviceRepository;
@Autowired private DeviceService deviceService; @Autowired private DevicePopulationService devicePopulationService;
@Autowired private DevicePropagationService devicePropagationService;
@Autowired private StateRepository<State<?>> stateRepository; @Autowired private StateRepository<State<?>> stateRepository;
private List<Device> copyStatesToDevices(Scene fromScene) { private List<Device> copyStatesToDevices(Scene fromScene) {
final List<Device> updated = new ArrayList<>(); final List<Device> updated = new ArrayList<>(fromScene.getStates().size());
for (final State<?> s : fromScene.getStates()) { for (final State<?> s : fromScene.getStates()) {
s.apply(); s.apply();
updated.add(s.getDevice()); updated.add(s.getDevice());
} }
deviceService.populateComputedFields(updated); devicePopulationService.populateComputedFields(updated);
return updated; return updated;
} }
public List<Device> apply(Scene newScene, String username, boolean fromTrigger) { public List<Device> apply(Scene newScene, String username, boolean fromTrigger) {
List<Device> updated = copyStatesToDevices(newScene); List<Device> updated = copyStatesToDevices(newScene);
deviceService.saveAllAsOwner(updated, username, true, fromTrigger); devicePropagationService.saveAllAsOwner(updated, username, true, fromTrigger);
return updated; return updated;
} }
public List<Device> applyAsGuest(Scene newScene, String username, Long hostId) { public List<Device> applyAsGuest(Scene newScene, String username, Long hostId) {
List<Device> updated = copyStatesToDevices(newScene); List<Device> updated = copyStatesToDevices(newScene);
deviceService.saveAllAsGuestSceneApplication(updated, username, hostId); devicePropagationService.saveAllAsGuestSceneApplication(updated, username, hostId);
return updated; return updated;
} }

View file

@ -16,7 +16,7 @@ public class ThermostatService {
@Autowired private SensorSocketEndpoint endpoint; @Autowired private SensorSocketEndpoint endpoint;
@Autowired private DeviceService deviceService; @Autowired private DevicePropagationService deviceService;
@Autowired private ThermostatRepository thermostatRepository; @Autowired private ThermostatRepository thermostatRepository;

View file

@ -5,7 +5,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
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.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.service.DeviceService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DevicePopulationService;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
@ -20,15 +20,15 @@ import org.springframework.stereotype.Component;
@Component @Component
public class SensorSocketEndpoint extends Endpoint { public class SensorSocketEndpoint extends Endpoint {
private Gson gson = GsonConfig.socketGson(); private final Gson gson = GsonConfig.socketGson();
@Autowired private DeviceService deviceService; @Autowired private DevicePopulationService deviceService;
private UserRepository userRepository; private final UserRepository userRepository;
private JWTTokenUtils jwtTokenUtils; private final JWTTokenUtils jwtTokenUtils;
private Multimap<User, Session> authorizedClients = private final Multimap<User, Session> authorizedClients =
Multimaps.synchronizedMultimap(HashMultimap.create()); Multimaps.synchronizedMultimap(HashMultimap.create());
// messages are now stored as strings as a "hack" to capture and clone the state of the device, // messages are now stored as strings as a "hack" to capture and clone the state of the device,

View file

@ -4,7 +4,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
@ -42,9 +41,9 @@ public class AuthenticationTests extends SmartHutTest {
@Override @Override
protected void setUp() { protected void setUp() {
final ResponseEntity<OkResponse> res = final ResponseEntity<Object> res =
this.restTemplate.postForEntity( this.restTemplate.postForEntity(
this.url("/register"), getDisabledUser(), OkResponse.class); this.url("/register"), getDisabledUser(), Object.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getStatusCode().equals(HttpStatus.OK));
registerTestUser(restTemplate, userRepository, tokenRepository); registerTestUser(restTemplate, userRepository, tokenRepository);
@ -178,8 +177,8 @@ public class AuthenticationTests extends SmartHutTest {
request.setEmail("smarthut.sm@example.com"); request.setEmail("smarthut.sm@example.com");
request.setPassword("password"); request.setPassword("password");
final ResponseEntity<OkResponse> res = final ResponseEntity<Object> res =
this.restTemplate.postForEntity(url("/register"), request, OkResponse.class); this.restTemplate.postForEntity(url("/register"), request, Object.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getStatusCode().equals(HttpStatus.OK));
assertThat(res.getBody() != null); assertThat(res.getBody() != null);
} }

View file

@ -2,7 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
@ -40,8 +39,8 @@ public abstract class SmartHutTest {
final TestRestTemplate restTemplate, final TestRestTemplate restTemplate,
final UserRepository userRepository, final UserRepository userRepository,
final ConfirmationTokenRepository tokenRepository) { final ConfirmationTokenRepository tokenRepository) {
final ResponseEntity<OkResponse> res2 = final ResponseEntity<Object> res2 =
restTemplate.postForEntity(this.url("/register"), enabledUser, OkResponse.class); restTemplate.postForEntity(this.url("/register"), enabledUser, Object.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK)); assertThat(res2.getStatusCode().equals(HttpStatus.OK));
final User persistedEnabledUser = userRepository.findByUsername("enabled"); final User persistedEnabledUser = userRepository.findByUsername("enabled");