From 45d898d51f07777a49b77828b51fb4474e522b31 Mon Sep 17 00:00:00 2001 From: Juan Marcos Bellini Date: Tue, 21 Nov 2017 17:01:27 -0300 Subject: [PATCH 1/5] Add pairing method without an owner - Also modify the actual pairing process to avoid storing a session --- .../server/services/DeviceService.java | 8 ++++ .../server/services/DeviceServiceImpl.java | 44 ++++++------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java b/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java index 41b07ab..e99b3f5 100644 --- a/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java +++ b/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java @@ -104,6 +104,14 @@ public interface DeviceService { */ String pair(long ownerId, long deviceId); + /** + * Performs pairing of {@link ar.edu.itba.iot.carne_iot.server.models.Device}s, using a system {@link User}. + * + * @param deviceId The id of the {@link ar.edu.itba.iot.carne_iot.server.models.Device} to be paired. + * @return The token generated by the pairing process. + */ + String pair(long deviceId); + /** * Makes a {@link Device} start cooking (i.e change its state to {@link Device.State#ACTIVE}). * This is an idempotent operation. diff --git a/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java b/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java index 1b6cbc2..313ca9a 100644 --- a/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java +++ b/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java @@ -8,11 +8,9 @@ import ar.edu.itba.iot.carne_iot.server.exceptions.UniqueViolationException; import ar.edu.itba.iot.carne_iot.server.models.Device; import ar.edu.itba.iot.carne_iot.server.models.DeviceRegistration; -import ar.edu.itba.iot.carne_iot.server.models.Session; import ar.edu.itba.iot.carne_iot.server.models.User; import ar.edu.itba.iot.carne_iot.server.persistence.daos.DeviceDao; import ar.edu.itba.iot.carne_iot.server.persistence.daos.DeviceRegistrationDao; -import ar.edu.itba.iot.carne_iot.server.persistence.daos.SessionDao; import ar.edu.itba.iot.carne_iot.server.persistence.daos.UserDao; import ar.edu.itba.iot.carne_iot.server.persistence.query_helpers.DeviceQueryHelper; import ar.edu.itba.iot.carne_iot.server.persistence.query_helpers.DeviceRegistrationQueryHelper; @@ -27,8 +25,8 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.time.LocalDate; import java.util.Collections; -import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -41,9 +39,11 @@ public class DeviceServiceImpl implements DeviceService, UniqueViolationExceptionThrower { /** - * Amount of tries to perform the pairing process. + * A system {@link User} to be used to create device tokens. */ - private static final int MAX_TRIES = 10; + private static final User deviceUser = new User("device", + LocalDate.of(1900, 1, 1), + "username", "email@email.com", "hashed"); /** * DAO for managing {@link User}s data. @@ -75,24 +75,18 @@ public class DeviceServiceImpl implements DeviceService, UniqueViolationExceptio */ private final JwtTokenGenerator jwtTokenGenerator; - /** - * DAO for retrieving {@link Session}s data. - */ - private final SessionDao sessionDao; - @Autowired public DeviceServiceImpl(UserDao userDao, DeviceDao deviceDao, DeviceRegistrationDao deviceRegistrationDao, DeviceQueryHelper deviceQueryHelper, DeviceRegistrationQueryHelper deviceRegistrationQueryHelper, - JwtTokenGenerator jwtTokenGenerator, SessionDao sessionDao) { + JwtTokenGenerator jwtTokenGenerator) { this.userDao = userDao; this.deviceDao = deviceDao; this.deviceRegistrationDao = deviceRegistrationDao; this.deviceQueryHelper = deviceQueryHelper; this.deviceRegistrationQueryHelper = deviceRegistrationQueryHelper; this.jwtTokenGenerator = jwtTokenGenerator; - this.sessionDao = sessionDao; } @@ -209,7 +203,6 @@ public void unregisterDevice(long deviceId) { } @Override - @Transactional @PreAuthorize("@devicePermissionProvider.isOwnerOrAdmin(#deviceId)") public String pair(long ownerId, long deviceId) { final User user = userDao.findById(ownerId).orElseThrow(NoSuchEntityException::new); @@ -220,25 +213,14 @@ public String pair(long ownerId, long deviceId) { throw new CustomIllegalStateException(OPERATION_OVER_UNREGISTERED_DEVICE); } - // Try to create the token... - boolean validToken = false; - int tries = 0; - JwtTokenGenerator.TokenAndSessionContainer container = null; - while (!validToken && tries < MAX_TRIES) { - container = jwtTokenGenerator.generateDeviceToken(user, device); - validToken = !sessionDao.existsByOwnerAndJti(user, container.getJti()); - tries++; - } - if (tries >= MAX_TRIES) { - throw new RuntimeException("Could not create a session after " + MAX_TRIES + "tries"); - } - - // Store token in order to pass the - Objects.requireNonNull(container, "The container was not initialized correctly"); - final Session session = new Session(user, container.getJti()); // Actually not a session, but whatever - sessionDao.save(session); + return jwtTokenGenerator.generateDeviceToken(user, device).getToken(); + } - return container.getToken(); + @Override + @PreAuthorize("@adminPermissionProvider.isAdmin()") + public String pair(long deviceId) { + final Device device = deviceDao.findById(deviceId).orElseThrow(NoSuchEntityException::new); + return jwtTokenGenerator.generateDeviceToken(deviceUser, device).getToken(); } @Override From f5a4889cff4e7a33d0819b6b700c413cc9ba9dcd Mon Sep 17 00:00:00 2001 From: Juan Marcos Bellini Date: Tue, 21 Nov 2017 17:02:11 -0300 Subject: [PATCH 2/5] Change jwt compiling process - Now it does not check blacklisted state in device tokens --- .../carne_iot/server/web/security/authentication/JwtAgent.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/security/authentication/JwtAgent.java b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/security/authentication/JwtAgent.java index ddc39b4..8cb2e04 100644 --- a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/security/authentication/JwtAgent.java +++ b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/security/authentication/JwtAgent.java @@ -130,14 +130,13 @@ public JwtTokenData compile(String rawToken) throws IllegalArgumentException { final long userId = (long) claims.get(USER_ID_CLAIM_NAME); final long jti = (long) claims.get(JWT_ID_CLAIM_NAME); @SuppressWarnings("unchecked") final Set roles = (Set) claims.get(ROLES_CLAIM_NAME); - - checkJwtBlacklist(userId, jti); final String username = claims.getSubject(); if (roles.contains(Role.ROLE_DEVICE)) { final long deviceId = (long) claims.get(DEVICE_ID_CLAIMS_NAME); return new DeviceJwtTokenData(userId, username, roles, deviceId); } + checkJwtBlacklist(userId, jti); // Device tokens are not blacklisted return new JwtTokenData(userId, username, roles); } catch (MalformedJwtException | SignatureException | ExpiredJwtException | UnsupportedJwtException | MissingClaimException e) { From ae4f5af9d556ba92e5767d059558b03aa2031473 Mon Sep 17 00:00:00 2001 From: Juan Marcos Bellini Date: Tue, 21 Nov 2017 17:03:08 -0300 Subject: [PATCH 3/5] Add endpoint to pair a device without an owner - Just for admins --- .../rest_endpoints/DevicesEndpoint.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java index 78047b4..9022385 100644 --- a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java +++ b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java @@ -182,4 +182,22 @@ public Response updateTemperature(@PathParam("id") @Base64url final Long id, Str return Response.noContent().build(); } + + @POST + @Path("/{deviceId : .+}/pair") + public Response pairDevice(@PathParam("deviceId") @Base64url final Long deviceId) { + + LOGGER.debug("Trying to pair device with id {}", deviceId); + + // Create a JWT for the paired device + final String token = deviceService.pair(deviceId); + + LOGGER.debug("Device {} successfully paired", deviceId); + + // TODO: if token can be invalidated, add an "invalidation" url + + return Response.noContent() + .header("X-Device-Token", token) + .build(); + } } From 7b32e95fe3c10a78ce7ed3cffd3bba3dc35fe65c Mon Sep 17 00:00:00 2001 From: Juan Marcos Bellini Date: Tue, 21 Nov 2017 17:40:51 -0300 Subject: [PATCH 4/5] Remove state of Devices --- .../iot/carne_iot/server/models/Device.java | 52 ++----------------- .../server/services/DeviceService.java | 16 ------ .../server/services/DeviceServiceImpl.java | 14 ----- .../controller/dtos/entities/DeviceDto.java | 8 --- .../rest_endpoints/DevicesEndpoint.java | 28 ---------- .../migration/V0_0_4__Remove_Device_State.sql | 2 + 6 files changed, 6 insertions(+), 114 deletions(-) create mode 100644 server-webapp/src/main/resources/db/migration/V0_0_4__Remove_Device_State.sql diff --git a/server-core/src/main/java/ar/edu/itba/iot/carne_iot/server/models/Device.java b/server-core/src/main/java/ar/edu/itba/iot/carne_iot/server/models/Device.java index 57e6b84..84b47c6 100644 --- a/server-core/src/main/java/ar/edu/itba/iot/carne_iot/server/models/Device.java +++ b/server-core/src/main/java/ar/edu/itba/iot/carne_iot/server/models/Device.java @@ -1,15 +1,16 @@ package ar.edu.itba.iot.carne_iot.server.models; -import ar.edu.itba.iot.carne_iot.server.error_handling.errros.IllegalStateError; import ar.edu.itba.iot.carne_iot.server.error_handling.errros.ValidationError; import ar.edu.itba.iot.carne_iot.server.error_handling.helpers.ValidationExceptionThrower; import ar.edu.itba.iot.carne_iot.server.error_handling.helpers.ValidationHelper; -import ar.edu.itba.iot.carne_iot.server.exceptions.CustomIllegalStateException; import ar.edu.itba.iot.carne_iot.server.exceptions.ValidationException; import ar.edu.itba.iot.carne_iot.server.models.constants.ValidationConstants; import ar.edu.itba.iot.carne_iot.server.models.constants.ValidationErrorConstants; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; import java.math.BigDecimal; import java.time.Instant; import java.util.LinkedList; @@ -41,13 +42,6 @@ public class Device implements ValidationExceptionThrower { @Column(name = "last_temperature_update") private Instant lastTemperatureUpdate; - /** - * The actual state of this device. - */ - @Column(name = "state", length = 64) - @Enumerated(EnumType.STRING) - private State state; - /* package */ Device() { // For Hibernate @@ -62,7 +56,6 @@ public Device(long id) { this.id = id; this.temperature = null; this.lastTemperatureUpdate = null; - this.state = State.IDLE; } /** @@ -87,13 +80,6 @@ public Instant getLastTemperatureUpdate() { return lastTemperatureUpdate; } - /** - * @return The actual state of this device. - */ - public State getState() { - return state; - } - /** * Sets the actual temperature measured by this device. * @@ -101,38 +87,12 @@ public State getState() { * @throws ValidationException If the temperature is not valid. */ public void setTemperature(BigDecimal temperature) throws ValidationException { - if (this.state != State.ACTIVE) { - throw new CustomIllegalStateException(CHANGE_TEMPERATURE_IN_NOT_ACTIVE_STATE); - } validateTemperature(temperature); this.temperature = temperature; this.lastTemperatureUpdate = Instant.now(); } - /** - * Changes this device state to active. - */ - public void startCooking() { - this.state = State.ACTIVE; - } - - /** - * Changes this device state to idle. - */ - public void stopCooking() { - this.state = State.IDLE; - } - - - /** - * Enum listing all possible states for a {@link Device}. - */ - public enum State { - IDLE, - ACTIVE, - } - // ==================== // Validators @@ -163,8 +123,4 @@ private void validateTemperature(BigDecimal temperature) throws ValidationExcept /* package */ static final int PRECISION = 5; /* package */ static final int SCALE = 2; - - private static final IllegalStateError CHANGE_TEMPERATURE_IN_NOT_ACTIVE_STATE = - new IllegalStateError("Device must be active to set the temperature", - Device.class.getSimpleName()); } diff --git a/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java b/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java index e99b3f5..07bfa1b 100644 --- a/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java +++ b/server-services-interfaces/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceService.java @@ -112,22 +112,6 @@ public interface DeviceService { */ String pair(long deviceId); - /** - * Makes a {@link Device} start cooking (i.e change its state to {@link Device.State#ACTIVE}). - * This is an idempotent operation. - * - * @param deviceId The id of the {@link Device} that will start cooking. - */ - void startCooking(long deviceId); - - /** - * Makes a {@link Device} stop cooking (i.e change its state to {@link Device.State#IDLE}). - * This is an idempotent operation. - * - * @param deviceId The id of the {@link Device} that will stop cooking. - */ - void stopCooking(long deviceId); - /** * Updates the temperature of a given {@link Device}. * diff --git a/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java b/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java index 313ca9a..69f9c56 100644 --- a/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java +++ b/server-services/src/main/java/ar/edu/itba/iot/carne_iot/server/services/DeviceServiceImpl.java @@ -223,20 +223,6 @@ public String pair(long deviceId) { return jwtTokenGenerator.generateDeviceToken(deviceUser, device).getToken(); } - @Override - @Transactional - @PreAuthorize("@devicePermissionProvider.isOwnerOrAdmin(#deviceId)") - public void startCooking(long deviceId) { - performChangeOfState(deviceId, Device::startCooking); - } - - @Override - @Transactional - @PreAuthorize("@devicePermissionProvider.isOwnerOrAdmin(#deviceId)") - public void stopCooking(long deviceId) { - performChangeOfState(deviceId, Device::stopCooking); - } - @Override @Transactional @PreAuthorize("hasRole(T(ar.edu.itba.iot.carne_iot.server.models.Role).ROLE_DEVICE) " + diff --git a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/dtos/entities/DeviceDto.java b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/dtos/entities/DeviceDto.java index e3a7f2c..c967648 100644 --- a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/dtos/entities/DeviceDto.java +++ b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/dtos/entities/DeviceDto.java @@ -29,9 +29,6 @@ public class DeviceDto implements Resoursable { @JsonSerialize(using = Java8InstantSerializer.class) private Instant lastTemperatureUpdate; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Device.State state; - public DeviceDto() { // For Jersey @@ -46,7 +43,6 @@ public DeviceDto() { this.id = device.getId(); this.temperature = device.getTemperature(); this.lastTemperatureUpdate = device.getLastTemperatureUpdate(); - this.state = device.getState(); } public Long getId() { @@ -60,8 +56,4 @@ public BigDecimal getTemperature() { public Instant getLastTemperatureUpdate() { return lastTemperatureUpdate; } - - public Device.State getState() { - return state; - } } diff --git a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java index 9022385..135f2ce 100644 --- a/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java +++ b/server-webapp/src/main/java/ar/edu/itba/iot/carne_iot/server/web/controller/rest_endpoints/DevicesEndpoint.java @@ -136,34 +136,6 @@ public Response unregisterDevice(@PathParam("id") @Base64url final Long id) { // Device state changers // ====================================== - @PUT - @Path("/{id : .+}/cooking") - public Response startCooking(@PathParam("id") @Base64url final Long id) { - if (id == null) { - throw new IllegalParamValueException(Collections.singletonList("id")); - } - - LOGGER.debug("Device with id {} started cooking", id); - - deviceService.startCooking(id); - - return Response.noContent().build(); - } - - @DELETE - @Path("/{id : .+}/cooking") - public Response stopCooking(@PathParam("id") @Base64url final Long id) { - if (id == null) { - throw new IllegalParamValueException(Collections.singletonList("id")); - } - - LOGGER.debug("Device with id {} stopped cooking", id); - - deviceService.stopCooking(id); - - return Response.noContent().build(); - } - @PUT @Path("/{id : .+}/temperature") @Consumes(MediaType.APPLICATION_JSON) diff --git a/server-webapp/src/main/resources/db/migration/V0_0_4__Remove_Device_State.sql b/server-webapp/src/main/resources/db/migration/V0_0_4__Remove_Device_State.sql new file mode 100644 index 0000000..3cdd7dd --- /dev/null +++ b/server-webapp/src/main/resources/db/migration/V0_0_4__Remove_Device_State.sql @@ -0,0 +1,2 @@ +ALTER TABLE devices + DROP COLUMN state; From fe23380c11f565e7dbc78322f30cb98eed8dadc4 Mon Sep 17 00:00:00 2001 From: Juan Marcos Bellini Date: Tue, 21 Nov 2017 17:52:40 -0300 Subject: [PATCH 5/5] Change version in poms to 0.0.4.RELEASE --- pom.xml | 2 +- server-core/pom.xml | 2 +- server-persistence-interfaces/pom.xml | 2 +- server-persistence/pom.xml | 2 +- server-services-interfaces/pom.xml | 2 +- server-services/pom.xml | 2 +- server-webapp/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index cb86885..a1666df 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE pom ${project.groupId}:${project.artifactId} diff --git a/server-core/pom.xml b/server-core/pom.xml index 2e978b6..1920da8 100644 --- a/server-core/pom.xml +++ b/server-core/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-core diff --git a/server-persistence-interfaces/pom.xml b/server-persistence-interfaces/pom.xml index 291fdb6..b6edb4d 100644 --- a/server-persistence-interfaces/pom.xml +++ b/server-persistence-interfaces/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-persistence-interfaces diff --git a/server-persistence/pom.xml b/server-persistence/pom.xml index 820a5ff..36bde53 100644 --- a/server-persistence/pom.xml +++ b/server-persistence/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-persistence diff --git a/server-services-interfaces/pom.xml b/server-services-interfaces/pom.xml index 163c611..faa4ada 100644 --- a/server-services-interfaces/pom.xml +++ b/server-services-interfaces/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-services-interfaces diff --git a/server-services/pom.xml b/server-services/pom.xml index 9706f1b..c01dd63 100644 --- a/server-services/pom.xml +++ b/server-services/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-services diff --git a/server-webapp/pom.xml b/server-webapp/pom.xml index c61ce19..cec3705 100644 --- a/server-webapp/pom.xml +++ b/server-webapp/pom.xml @@ -8,7 +8,7 @@ ar.edu.itba.iot.carne-iot server - 0.0.3.RELEASE + 0.0.4.RELEASE server-webapp