From eba1f16ee1d5f1e6676b8186077bb3dfb4c49f81 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Mon, 2 May 2016 13:33:42 -0700 Subject: [PATCH] added changes to address security loophole in the IFTTT app addressed comments PENG-157 IFTTT should not allow undefined commands - added changes to address security loophole in the IFTTT app - addressed comments - removed an extra whitespace --- smartapps/smartthings/ifttt.src/ifttt.groovy | 64 +++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/smartapps/smartthings/ifttt.src/ifttt.groovy b/smartapps/smartthings/ifttt.src/ifttt.groovy index cad83d2..74dd2b2 100644 --- a/smartapps/smartthings/ifttt.src/ifttt.groovy +++ b/smartapps/smartthings/ifttt.src/ifttt.groovy @@ -131,19 +131,69 @@ def update() { def type = params.deviceType def data = request.JSON def devices = settings[type] + def device = settings[type]?.find { it.id == params.id } def command = data.command log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}" - if (command) { - def device = devices?.find { it.id == params.id } - if (!device) { - httpError(404, "Device not found") - } else { - device."$command"() - } + + if (!device) { + httpError(404, "Device not found") + } + + if (validateCommand(device, type, command)) { + device."$command"() + } else { + httpError(403, "Access denied. This command is not supported by current capability.") } } +/** + * Validating the command passed by the user based on capability. + * @return boolean + */ +def validateCommand(device, deviceType, command) { + def capabilityCommands = getDeviceCapabilityCommands(device.capabilities) + def currentDeviceCapability = getCapabilityName(deviceType) + if (capabilityCommands[currentDeviceCapability]) { + return command in capabilityCommands[currentDeviceCapability] ? true : false + } else { + // Handling other device types here, which don't accept commands + httpError(400, "Bad request.") + } +} + +/** + * Need to get the attribute name to do the lookup. Only + * doing it for the device types which accept commands + * @return attribute name of the device type + */ +def getCapabilityName(type) { + switch(type) { + case "switches": + return "Switch" + case "alarms": + return "Alarm" + case "locks": + return "Lock" + default: + return type + } +} + +/** + * Constructing the map over here of + * supported commands by device capability + * @return a map of device capability -> supported commands + */ +def getDeviceCapabilityCommands(deviceCapabilities) { + def map = [:] + deviceCapabilities.collect { + map[it.name] = it.commands.collect{ it.name.toString() } + } + return map +} + + def show() { def type = params.deviceType def devices = settings[type]