aiokwikset 0.1.0b1__tar.gz → 0.2.2a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiokwikset might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aiokwikset
3
- Version: 0.1.0b1
3
+ Version: 0.2.2a1
4
4
  Summary: Python interface for Kwikset Smart Locks
5
5
  Home-page: https://github.com/explosivo22/aiokwikset
6
6
  Author: Brad Barbour
@@ -53,7 +53,8 @@ async def main() -> None:
53
53
  api = API("<EMAIL>")
54
54
 
55
55
  #start auth
56
- pre_auth = await api.authenticate('<PASSWORD>')
56
+ #<CODE_TYPE> = [phone, email]
57
+ pre_auth = await api.authenticate('<PASSWORD>','<CODE_TYPE>')
57
58
 
58
59
  #MFA verification
59
60
  await api.verify_user(pre_auth, input("Code:"))
@@ -71,7 +72,16 @@ async def main() -> None:
71
72
  device_info = await api.device.get_device_info(devices[0]['deviceid'])
72
73
 
73
74
  # Lock the specific device
74
- lock = await api.device.lock_device(devices_info, user_info)
75
+ lock = await api.device.lock_device(device_info, user_info)
76
+
77
+ # Set led status
78
+ led = await api.device.set_ledstatus(device_info, "false")
79
+
80
+ # Set audio status
81
+ audio = await api.device.set_audiostatus(device_info, "false")
82
+
83
+ # Set secure screen status
84
+ screen = await api.device.set_securescreenstatus(device_info, "false")
75
85
 
76
86
 
77
87
  asyncio.run(main())
@@ -37,7 +37,8 @@ async def main() -> None:
37
37
  api = API("<EMAIL>")
38
38
 
39
39
  #start auth
40
- pre_auth = await api.authenticate('<PASSWORD>')
40
+ #<CODE_TYPE> = [phone, email]
41
+ pre_auth = await api.authenticate('<PASSWORD>','<CODE_TYPE>')
41
42
 
42
43
  #MFA verification
43
44
  await api.verify_user(pre_auth, input("Code:"))
@@ -55,7 +56,16 @@ async def main() -> None:
55
56
  device_info = await api.device.get_device_info(devices[0]['deviceid'])
56
57
 
57
58
  # Lock the specific device
58
- lock = await api.device.lock_device(devices_info, user_info)
59
+ lock = await api.device.lock_device(device_info, user_info)
60
+
61
+ # Set led status
62
+ led = await api.device.set_ledstatus(device_info, "false")
63
+
64
+ # Set audio status
65
+ audio = await api.device.set_audiostatus(device_info, "false")
66
+
67
+ # Set secure screen status
68
+ screen = await api.device.set_securescreenstatus(device_info, "false")
59
69
 
60
70
 
61
71
  asyncio.run(main())
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import aioboto3
3
+ import botocore.exceptions
3
4
  import attr
4
5
  import datetime
5
6
 
@@ -17,7 +18,7 @@ from aiohttp.client_exceptions import (
17
18
  ClientPayloadError
18
19
  )
19
20
 
20
- from .errors import RequestError
21
+ from .errors import RequestError, NotAuthorized
21
22
  from .device import Device
22
23
  from .user import User
23
24
  from .exceptions import TokenVerificationException
@@ -169,7 +170,7 @@ class API(object):
169
170
  if not self.access_token:
170
171
  raise AttributeError('Access Token Required to Check Token')
171
172
  now = datetime.datetime.now()
172
- dec_access_token = jwt.get_unverified_claims(self.access_token)
173
+ dec_access_token = jwt.get_unverified_claims(self.id_token)
173
174
 
174
175
  if now > datetime.datetime.fromtimestamp(dec_access_token['exp']):
175
176
  expired = True
@@ -185,12 +186,13 @@ class API(object):
185
186
  """
186
187
  auth_params = {'SECRET_HASH': '', 'REFRESH_TOKEN': self.refresh_token}
187
188
 
188
- async with self.get_client() as client:
189
- refresh_response = await client.initiate_auth(
190
- ClientId=self.client_id,
191
- AuthFlow='REFRESH_TOKEN_AUTH',
192
- AuthParameters=auth_params,
193
- )
189
+ try:
190
+ async with self.get_client() as client:
191
+ refresh_response = await client.initiate_auth(
192
+ ClientId=self.client_id,
193
+ AuthFlow='REFRESH_TOKEN_AUTH',
194
+ AuthParameters=auth_params,
195
+ )
194
196
 
195
197
  self._set_attributes(
196
198
  refresh_response,
@@ -209,6 +211,11 @@ class API(object):
209
211
 
210
212
  if not self.user:
211
213
  self.user = User(self._request)
214
+
215
+ #attempt to catch the NotAuthorizedException
216
+ except botocore.exceptions.ClientError as err:
217
+ if err.response['Error']['Code'] == 'NotAuthorizedException':
218
+ raise NotAuthorized("Refresh Token has been revoked.")
212
219
 
213
220
  def _set_attributes(self, response, attribute_dict):
214
221
  """
@@ -236,7 +243,29 @@ class API(object):
236
243
  client_id=self.client_id, client=self.get_client(),
237
244
  client_secret=self.client_secret)
238
245
 
246
+ self.code_type = code_type
247
+
239
248
  pre_verification = await self.aws.authenticate_user()
249
+
250
+ if pre_verification.get('AuthenticationResult'):
251
+ print("2-step verification disabled")
252
+ await self.verify_token(pre_verification['AuthenticationResult']['IdToken'],
253
+ 'id_token', 'id')
254
+ self.refresh_token = pre_verification['AuthenticationResult']['RefreshToken']
255
+ await self.verify_token(pre_verification['AuthenticationResult']['AccessToken'],
256
+ 'access_token', 'access')
257
+ self.token_type = pre_verification['AuthenticationResult']['TokenType']
258
+
259
+ if not self.device:
260
+ self.device = Device(self._request)
261
+
262
+ if not self.user:
263
+ self.user = User(self._request)
264
+
265
+ return
266
+
267
+ print("2-step verification enabled")
268
+
240
269
  return pre_verification
241
270
 
242
271
  async def verify_user(self, pre_verification, code):
@@ -249,6 +278,7 @@ class API(object):
249
278
  Session=pre_verification['Session'],
250
279
  ChallengeResponses=challenge_responses)
251
280
 
281
+
252
282
  await self.verify_token(tokens['AuthenticationResult']['IdToken'],
253
283
  'id_token', 'id')
254
284
  self.refresh_token = tokens['AuthenticationResult']['RefreshToken']
@@ -6,9 +6,12 @@ import hmac
6
6
  import re
7
7
 
8
8
  import aioboto3
9
+ import botocore.exceptions
9
10
  import os
10
11
  import six
11
12
 
13
+ from .errors import NotAuthorized
14
+
12
15
  # https://github.com/aws/amazon-cognito-identity-js/blob/master/src/AuthenticationHelper.js#L22
13
16
  n_hex = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + \
14
17
  '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + \
@@ -240,35 +243,44 @@ class AWSKWIKSET(object):
240
243
  boto_client = self.client or client
241
244
  auth_params = self.get_auth_params()
242
245
 
243
- async with boto_client as client:
244
- response = await client.initiate_auth(
245
- AuthFlow='CUSTOM_AUTH',
246
- AuthParameters=auth_params,
247
- ClientId=self.client_id
248
- )
249
- if response['ChallengeName'] == self.PASSWORD_VERIFIER_CHALLENGE:
250
- challenge_response = self.process_challenge(
251
- response['ChallengeParameters'])
252
-
253
- custom_challenge = await client.respond_to_auth_challenge(
254
- ClientId=self.client_id,
255
- ChallengeName=self.PASSWORD_VERIFIER_CHALLENGE,
256
- Session=response['Session'],
257
- ChallengeResponses=challenge_response)
258
-
259
- if custom_challenge['ChallengeName'] == self.CUSTOM_VERIFIER_CHALLENGE:
260
- custom_challenge_response = self.process_custom_challenge()
261
-
262
- custom_challenge_code = await client.respond_to_auth_challenge(
246
+ try:
247
+ async with boto_client as client:
248
+ response = await client.initiate_auth(
249
+ AuthFlow='CUSTOM_AUTH',
250
+ AuthParameters=auth_params,
251
+ ClientId=self.client_id
252
+ )
253
+ if response['ChallengeName'] == self.PASSWORD_VERIFIER_CHALLENGE:
254
+ challenge_response = self.process_challenge(
255
+ response['ChallengeParameters'])
256
+
257
+ custom_challenge = await client.respond_to_auth_challenge(
263
258
  ClientId=self.client_id,
264
- ChallengeName=self.CUSTOM_VERIFIER_CHALLENGE,
265
- Session=custom_challenge['Session'],
266
- ChallengeResponses=custom_challenge_response)
267
-
268
- return custom_challenge_code
259
+ ChallengeName=self.PASSWORD_VERIFIER_CHALLENGE,
260
+ Session=response['Session'],
261
+ ChallengeResponses=challenge_response)
262
+
263
+ #2-step verification is disabled
264
+ if custom_challenge.get('AuthenticationResult'):
265
+ return custom_challenge
266
+
267
+ #2-step verification is enabled
268
+ if custom_challenge['ChallengeName'] == self.CUSTOM_VERIFIER_CHALLENGE:
269
+ custom_challenge_response = self.process_custom_challenge()
270
+
271
+ custom_challenge_code = await client.respond_to_auth_challenge(
272
+ ClientId=self.client_id,
273
+ ChallengeName=self.CUSTOM_VERIFIER_CHALLENGE,
274
+ Session=custom_challenge['Session'],
275
+ ChallengeResponses=custom_challenge_response)
276
+
277
+ return custom_challenge_code
278
+ else:
279
+ raise NotImplementedError('The %s challenge is not supported'
280
+ % response['ChallengeName'])
269
281
  else:
270
282
  raise NotImplementedError('The %s challenge is not supported'
271
- % response['ChallengeName'])
272
- else:
273
- raise NotImplementedError('The %s challenge is not supported'
274
- % response['ChallengeName'])
283
+ % response['ChallengeName'])
284
+ except botocore.exceptions.ClientError as err:
285
+ if err.response['Error']['Code'] == 'NotAuthorizedException':
286
+ raise NotAuthorized("Refresh Token has been revoked")
@@ -13,4 +13,5 @@ GET_HOMES_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/
13
13
  GET_HOME_DEVICES_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/homes/%s/devices'
14
14
  GET_DEVICE_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/devices_v2/%s'
15
15
 
16
- COMMAND_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/devices/%s/status'
16
+ LOCK_COMMAND_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/devices/%s/status'
17
+ ACCESSORY_COMMAND_URL = 'https://ynk95r1v52.execute-api.us-east-1.amazonaws.com/prod_v1/devices/%s/%s'
@@ -2,7 +2,7 @@
2
2
  from typing import Awaitable, Callable
3
3
  import json
4
4
 
5
- from .const import COMMAND_URL, GET_HOME_DEVICES_URL, GET_DEVICE_URL
5
+ from .const import LOCK_COMMAND_URL, ACCESSORY_COMMAND_URL, GET_HOME_DEVICES_URL, GET_DEVICE_URL
6
6
 
7
7
 
8
8
  class Device(): # pylint: disable=too-few-public-methods
@@ -39,7 +39,7 @@ class Device(): # pylint: disable=too-few-public-methods
39
39
  for items in device_info['data']:
40
40
  return items
41
41
 
42
- async def _set_action(self, dev, user, action: str):
42
+ async def _lock_action(self, dev, user, action: str):
43
43
  data = json.dumps({
44
44
  "action": action,
45
45
  "source": "{\"name\":\"%s\",\"device\":\"%s\"}"
@@ -47,13 +47,34 @@ class Device(): # pylint: disable=too-few-public-methods
47
47
 
48
48
  r = await self._request(
49
49
  'patch',
50
- COMMAND_URL % (dev['serialnumber']),
50
+ LOCK_COMMAND_URL % (dev['serialnumber']),
51
+ data=data,
52
+ )
53
+ return r['data']
54
+
55
+ async def _set_action(self, dev, action, status):
56
+ data = json.dumps({
57
+ action: status,
58
+ })
59
+
60
+ r = await self._request(
61
+ 'patch',
62
+ ACCESSORY_COMMAND_URL % (dev['serialnumber'], action),
51
63
  data=data,
52
64
  )
53
65
  return r['data']
54
66
 
55
67
  async def lock_device(self, dev, user):
56
- return await self._set_action(dev, user, 'lock')
68
+ return await self._lock_action(dev, user, 'lock')
57
69
 
58
70
  async def unlock_device(self, dev, user):
59
- return await self._set_action(dev, user, 'unlock')
71
+ return await self._lock_action(dev, user, 'unlock')
72
+
73
+ async def set_ledstatus(self, dev, status):
74
+ return await self._set_action(dev, 'ledstatus', status)
75
+
76
+ async def set_audiostatus(self, dev, status):
77
+ return await self._set_action(dev, 'audiostatus', status)
78
+
79
+ async def set_securescreenstatus(self, dev, status):
80
+ return await self._set_action(dev, 'securescreenstatus', status)
@@ -0,0 +1,19 @@
1
+ """Define package errors."""
2
+ from typing import Any
3
+
4
+ class KwiksetError(Exception):
5
+ """Define a base error."""
6
+
7
+ pass
8
+
9
+
10
+ class RequestError(KwiksetError):
11
+ """Define an error related to invalid requests."""
12
+
13
+ pass
14
+
15
+ class NotAuthorized(Exception):
16
+ """Raised when the refresh token has been revoked"""
17
+
18
+ def __init__(self, *args: Any) -> None:
19
+ Exception.__init__(self, *args)
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  class WarrantException(Exception):
2
4
  """Base class for all Warrant exceptions"""
3
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aiokwikset
3
- Version: 0.1.0b1
3
+ Version: 0.2.2a1
4
4
  Summary: Python interface for Kwikset Smart Locks
5
5
  Home-page: https://github.com/explosivo22/aiokwikset
6
6
  Author: Brad Barbour
@@ -53,7 +53,8 @@ async def main() -> None:
53
53
  api = API("<EMAIL>")
54
54
 
55
55
  #start auth
56
- pre_auth = await api.authenticate('<PASSWORD>')
56
+ #<CODE_TYPE> = [phone, email]
57
+ pre_auth = await api.authenticate('<PASSWORD>','<CODE_TYPE>')
57
58
 
58
59
  #MFA verification
59
60
  await api.verify_user(pre_auth, input("Code:"))
@@ -71,7 +72,16 @@ async def main() -> None:
71
72
  device_info = await api.device.get_device_info(devices[0]['deviceid'])
72
73
 
73
74
  # Lock the specific device
74
- lock = await api.device.lock_device(devices_info, user_info)
75
+ lock = await api.device.lock_device(device_info, user_info)
76
+
77
+ # Set led status
78
+ led = await api.device.set_ledstatus(device_info, "false")
79
+
80
+ # Set audio status
81
+ audio = await api.device.set_audiostatus(device_info, "false")
82
+
83
+ # Set secure screen status
84
+ screen = await api.device.set_securescreenstatus(device_info, "false")
75
85
 
76
86
 
77
87
  asyncio.run(main())
@@ -13,7 +13,7 @@ except FileNotFoundError:
13
13
 
14
14
  setup(
15
15
  name="aiokwikset",
16
- version="0.1.0-b1",
16
+ version="0.2.2a1",
17
17
  description="Python interface for Kwikset Smart Locks",
18
18
  long_description=long_description,
19
19
  long_description_content_type='text/markdown',
@@ -1,13 +0,0 @@
1
- """Define package errors."""
2
-
3
-
4
- class KwiksetError(Exception):
5
- """Define a base error."""
6
-
7
- pass
8
-
9
-
10
- class RequestError(KwiksetError):
11
- """Define an error related to invalid requests."""
12
-
13
- pass
File without changes
File without changes