Prompt for pin only on open / unlock (#23751)

* Prompt for pin only on open / unlock

* Fixed test cases
This commit is contained in:
Penny Wood 2019-05-08 22:55:30 +08:00 committed by Paulus Schoutsen
parent ce1974b014
commit 7f0953766b
2 changed files with 56 additions and 53 deletions

View File

@ -742,11 +742,10 @@ class LockUnlockTrait(_Trait):
async def execute(self, command, data, params, challenge): async def execute(self, command, data, params, challenge):
"""Execute an LockUnlock command.""" """Execute an LockUnlock command."""
_verify_pin_challenge(data, challenge)
if params['lock']: if params['lock']:
service = lock.SERVICE_LOCK service = lock.SERVICE_LOCK
else: else:
_verify_pin_challenge(data, challenge)
service = lock.SERVICE_UNLOCK service = lock.SERVICE_UNLOCK
await self.hass.services.async_call(lock.DOMAIN, service, { await self.hass.services.async_call(lock.DOMAIN, service, {
@ -1095,36 +1094,36 @@ class OpenCloseTrait(_Trait):
domain = self.state.domain domain = self.state.domain
if domain == cover.DOMAIN: if domain == cover.DOMAIN:
if self.state.attributes.get(ATTR_DEVICE_CLASS) in ( svc_params = {ATTR_ENTITY_ID: self.state.entity_id}
cover.DEVICE_CLASS_DOOR, cover.DEVICE_CLASS_GARAGE
):
_verify_pin_challenge(data, challenge)
if params['openPercent'] == 0: if params['openPercent'] == 0:
await self.hass.services.async_call( service = cover.SERVICE_CLOSE_COVER
cover.DOMAIN, cover.SERVICE_CLOSE_COVER, { should_verify = False
ATTR_ENTITY_ID: self.state.entity_id
}, blocking=True, context=data.context)
elif params['openPercent'] == 100: elif params['openPercent'] == 100:
await self.hass.services.async_call( service = cover.SERVICE_OPEN_COVER
cover.DOMAIN, cover.SERVICE_OPEN_COVER, { should_verify = True
ATTR_ENTITY_ID: self.state.entity_id
}, blocking=True, context=data.context)
elif (self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & elif (self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) &
cover.SUPPORT_SET_POSITION): cover.SUPPORT_SET_POSITION):
await self.hass.services.async_call( service = cover.SERVICE_SET_COVER_POSITION
cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, { should_verify = True
ATTR_ENTITY_ID: self.state.entity_id, svc_params[cover.ATTR_POSITION] = params['openPercent']
cover.ATTR_POSITION: params['openPercent']
}, blocking=True, context=data.context)
else: else:
raise SmartHomeError( raise SmartHomeError(
ERR_FUNCTION_NOT_SUPPORTED, ERR_FUNCTION_NOT_SUPPORTED,
'Setting a position is not supported') 'Setting a position is not supported')
if (should_verify and
self.state.attributes.get(ATTR_DEVICE_CLASS)
in (cover.DEVICE_CLASS_DOOR,
cover.DEVICE_CLASS_GARAGE)):
_verify_pin_challenge(data, challenge)
await self.hass.services.async_call(
cover.DOMAIN, service, svc_params,
blocking=True, context=data.context)
if (self.state.attributes.get(ATTR_ASSUMED_STATE) or if (self.state.attributes.get(ATTR_ASSUMED_STATE) or
self.state.state == STATE_UNKNOWN): self.state.state == STATE_UNKNOWN):
print("YOO")
self.override_position = params['openPercent'] self.override_position = params['openPercent']

View File

@ -845,38 +845,20 @@ async def test_lock_unlock_lock(hass):
None) None)
trt = trait.LockUnlockTrait(hass, trt = trait.LockUnlockTrait(hass,
State('lock.front_door', lock.STATE_UNLOCKED), State('lock.front_door', lock.STATE_LOCKED),
PIN_CONFIG) PIN_CONFIG)
assert trt.sync_attributes() == {} assert trt.sync_attributes() == {}
assert trt.query_attributes() == { assert trt.query_attributes() == {
'isLocked': False 'isLocked': True
} }
assert trt.can_execute(trait.COMMAND_LOCKUNLOCK, {'lock': True}) assert trt.can_execute(trait.COMMAND_LOCKUNLOCK, {'lock': True})
calls = async_mock_service(hass, lock.DOMAIN, lock.SERVICE_LOCK) calls = async_mock_service(hass, lock.DOMAIN, lock.SERVICE_LOCK)
# No challenge data await trt.execute(trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True}, {})
with pytest.raises(error.ChallengeNeeded) as err:
await trt.execute(
trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True}, {})
assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_PIN_NEEDED
# invalid pin
with pytest.raises(error.ChallengeNeeded) as err:
await trt.execute(
trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True},
{'pin': 9999})
assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
await trt.execute(trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True},
{'pin': '1234'})
assert len(calls) == 1 assert len(calls) == 1
assert calls[0].data == { assert calls[0].data == {
@ -908,18 +890,18 @@ async def test_lock_unlock_unlock(hass):
with pytest.raises(error.ChallengeNeeded) as err: with pytest.raises(error.ChallengeNeeded) as err:
await trt.execute( await trt.execute(
trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {}) trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {})
assert len(calls) == 0 assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED assert err.value.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_PIN_NEEDED assert err.value.challenge_type == const.CHALLENGE_PIN_NEEDED
# invalid pin # invalid pin
with pytest.raises(error.ChallengeNeeded) as err: with pytest.raises(error.ChallengeNeeded) as err:
await trt.execute( await trt.execute(
trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False},
{'pin': 9999}) {'pin': 9999})
assert len(calls) == 0 assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED assert err.value.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED assert err.value.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
await trt.execute( await trt.execute(
trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {'pin': '1234'}) trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {'pin': '1234'})
@ -929,6 +911,17 @@ async def test_lock_unlock_unlock(hass):
ATTR_ENTITY_ID: 'lock.front_door' ATTR_ENTITY_ID: 'lock.front_door'
} }
# Test without pin
trt = trait.LockUnlockTrait(hass,
State('lock.front_door', lock.STATE_LOCKED),
BASIC_CONFIG)
with pytest.raises(error.SmartHomeError) as err:
await trt.execute(
trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': False}, {})
assert len(calls) == 1
assert err.value.code == const.ERR_CHALLENGE_NOT_SETUP
async def test_fan_speed(hass): async def test_fan_speed(hass):
"""Test FanSpeed trait speed control support for fan domain.""" """Test FanSpeed trait speed control support for fan domain."""
@ -1243,18 +1236,18 @@ async def test_openclose_cover_secure(hass, device_class):
await trt.execute( await trt.execute(
trait.COMMAND_OPENCLOSE, PIN_DATA, trait.COMMAND_OPENCLOSE, PIN_DATA,
{'openPercent': 50}, {}) {'openPercent': 50}, {})
assert len(calls) == 0 assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED assert err.value.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_PIN_NEEDED assert err.value.challenge_type == const.CHALLENGE_PIN_NEEDED
# invalid pin # invalid pin
with pytest.raises(error.ChallengeNeeded) as err: with pytest.raises(error.ChallengeNeeded) as err:
await trt.execute( await trt.execute(
trait.COMMAND_OPENCLOSE, PIN_DATA, trait.COMMAND_OPENCLOSE, PIN_DATA,
{'openPercent': 50}, {'pin': '9999'}) {'openPercent': 50}, {'pin': '9999'})
assert len(calls) == 0 assert len(calls) == 0
assert err.code == const.ERR_CHALLENGE_NEEDED assert err.value.code == const.ERR_CHALLENGE_NEEDED
assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED assert err.value.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
await trt.execute( await trt.execute(
trait.COMMAND_OPENCLOSE, PIN_DATA, trait.COMMAND_OPENCLOSE, PIN_DATA,
@ -1265,6 +1258,17 @@ async def test_openclose_cover_secure(hass, device_class):
cover.ATTR_POSITION: 50 cover.ATTR_POSITION: 50
} }
# no challenge on close
calls = async_mock_service(
hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER)
await trt.execute(
trait.COMMAND_OPENCLOSE, PIN_DATA,
{'openPercent': 0}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'cover.bla'
}
@pytest.mark.parametrize('device_class', ( @pytest.mark.parametrize('device_class', (
binary_sensor.DEVICE_CLASS_DOOR, binary_sensor.DEVICE_CLASS_DOOR,