Skip to content

Commit

Permalink
fix: missing userid needs to be passed to add lock record (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
shauntarves authored May 6, 2024
1 parent 945d1c7 commit 81f7eaa
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 11 deletions.
18 changes: 16 additions & 2 deletions wyze_sdk/api/devices/locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def _encrypt_access_code(self, access_code: str) -> str:
secret = self._ford_client().get_crypt_secret()["secret"]
return CBCEncryptor(self._ford_client().WYZE_FORD_IV_HEX).encrypt(MD5Hasher().hash(secret), access_code).hex()

def create_access_code(self, device_mac: str, access_code: str, name: Optional[str], permission: Optional[LockKeyPermission] = None, periodicity: Optional[LockKeyPeriodicity] = None, **kwargs) -> WyzeResponse:
def create_access_code(self, device_mac: str, access_code: str, name: Optional[str], permission: Optional[LockKeyPermission] = None, periodicity: Optional[LockKeyPeriodicity] = None, userid: Optional[str] = None, **kwargs) -> WyzeResponse:
"""Creates a guest access code on a lock.
:param str device_mac: The device mac. e.g. ``ABCDEF1234567890``
Expand All @@ -208,7 +208,7 @@ def create_access_code(self, device_mac: str, access_code: str, name: Optional[s
permission = LockKeyPermission(type=LockKeyPermissionType.ALWAYS)

uuid = Lock.parse_uuid(mac=device_mac)
return self._ford_client().add_password(uuid=uuid, password=self._encrypt_access_code(access_code=access_code), name=name, permission=permission, periodicity=periodicity, userid=self._user_id)
return self._ford_client().add_password(uuid=uuid, password=self._encrypt_access_code(access_code=access_code), name=name, permission=permission, periodicity=periodicity, userid=userid if userid is not None else self._user_id)

def delete_access_code(self, device_mac: str, access_code_id: int, **kwargs) -> WyzeResponse:
"""Deletes an access code from a lock.
Expand Down Expand Up @@ -244,6 +244,20 @@ def update_access_code(self, device_mac: str, access_code_id: int, access_code:
uuid = Lock.parse_uuid(mac=device_mac)
return self._ford_client().update_password(uuid=uuid, password_id=str(access_code_id), password=self._encrypt_access_code(access_code=access_code), name=name, permission=permission, periodicity=periodicity)

def rename_access_code(self, device_mac: str, access_code_id: int, access_code: Optional[str] = None, name: Optional[str] = None, permission: LockKeyPermission = None, periodicity: Optional[LockKeyPeriodicity] = None, **kwargs) -> WyzeResponse:
"""Renames an existing access code on a lock.
:param str device_mac: The device mac. e.g. ``ABCDEF1234567890``
:param int access_code_id: The id of the access code to reset.
:param str name: The new name for the guest access code.
:rtype: WyzeResponse
:raises WyzeRequestError: if the new access code is not valid
"""
uuid = Lock.parse_uuid(mac=device_mac)
return self._ford_client().set_nickname(uuid=uuid, password_id=str(access_code_id), nickname=name)

@property
def gateways(self) -> LockGatewaysClient:
"""Returns a lock gateway client.
Expand Down
21 changes: 20 additions & 1 deletion wyze_sdk/models/devices/locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,26 @@ def __init__(
self._is_online = self._extract_property(prop_def=LockProps.onoff_line(), others=others)
show_unknown_key_warning(self, others)


"""
Locks are funny objects...
The access codes that are set on locks are referred to as "passwords"
throughout the lock infrastructure. When a new code/password is created,
it is assigned some non-obivous fields:
* description: this is not actually a description, but rather used to
shuttle around a "status" or "state" of the lock in case there was
some kind of error
* name: not to be confused with the nickname or "usename", this field
is IMMUTABLE and remains stuck to the "Guest name" provided when the
code/password was created
* username: also called a "nickname", this field is the descriptive
"guest code name" that can be changed
All of this seems to be further complicated by different endpoints using
these field names differently. For example, the GET `.../lock/v1/auth`
endpoint puts the username value in a field called name. However, the
`.../lock/v1/pwd` calls to actually control the codes/passwords does not.
"""
class Lock(LockableMixin, ContactMixin, VoltageMixin, Device):

type = "Lock"
Expand Down
12 changes: 6 additions & 6 deletions wyze_sdk/service/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from abc import ABCMeta
from contextlib import suppress
from json import dumps
from typing import Dict, Optional, Union
from typing import Any, Dict, Optional, Union
from urllib.parse import urljoin

import requests
Expand Down Expand Up @@ -108,7 +108,7 @@ def _do_request(
self._logger.debug(f"Failed to send a request to server: {e}")
raise e

def do_post(self, url: str, headers: dict, payload: dict, params: Optional[dict] = None) -> WyzeResponse:
def do_post(self, url: str, headers: dict, payload: dict, params: Optional[dict] = None, method: Optional[Any] = 'POST') -> WyzeResponse:
with requests.Session() as client:
if headers is not None:
# add the request-specific headers
Expand All @@ -118,7 +118,7 @@ def do_post(self, url: str, headers: dict, payload: dict, params: Optional[dict]
# we have to use a prepared request because the requests module
# doesn't allow us to specify the separators in our json dumping
# and the server expects no extra whitespace
req = client.prepare_request(requests.Request('POST', url, json=payload, params=params))
req = client.prepare_request(requests.Request(method, url, json=payload, params=params))

self._logger.debug('unmodified prepared request')
self._logger.debug(req)
Expand Down Expand Up @@ -192,16 +192,16 @@ def api_call(
POST requests.
"""
has_json = json is not None
if has_json and http_verb != "POST":
if has_json and http_verb != "POST" and http_verb != "PATCH" and http_verb != "PUT":
msg = "JSON data can only be submitted as POST requests. GET requests should use the 'params' argument."
raise WyzeRequestError(msg)

api_url = self._get_url(self.base_url, api_endpoint)
headers = headers or {}
headers.update(self.headers)

if http_verb == "POST":
return self.do_post(url=api_url, headers=headers, payload=json, params=params)
if http_verb == "POST" or http_verb == "PATCH" or http_verb == "PUT":
return self.do_post(url=api_url, headers=headers, payload=json, params=params, method=http_verb)
elif http_verb == "GET":
return self.do_get(url=api_url, headers=headers, payload=params)

Expand Down
12 changes: 10 additions & 2 deletions wyze_sdk/service/ford_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def api_call(
) -> WyzeResponse:
nonce = self.request_verifier.clock.nonce()

if http_verb == "POST":
if http_verb == "POST" or http_verb == "PATCH" or http_verb == "PUT":
if json is None:
json = {}
# this must be done here so that it will be included in the signing
Expand All @@ -114,7 +114,7 @@ def api_call(
"timestamp": str(nonce),
})
json.update({
"sign": self.generate_dynamic_signature(path=api_method, method="post", body=super().get_sorted_params(sorted(json.items()))),
"sign": self.generate_dynamic_signature(path=api_method, method=http_verb.lower(), body=super().get_sorted_params(sorted(json.items()))),
})
elif http_verb == "GET":
if params is None:
Expand Down Expand Up @@ -233,3 +233,11 @@ def delete_password(self, *, uuid: str, password_id: str, **kwargs) -> FordRespo
'passwordid': password_id,
})
return self.api_call('/openapi/lock/v1/pwd/operations/delete', http_verb="POST", json=kwargs)

def set_nickname(self, *, uuid: str, password_id: str, nickname: str, **kwargs) -> FordResponse:
kwargs.update({
'uuid': uuid,
'passwordid': password_id,
'nickname': nickname,
})
return self.api_call('/openapi/lock/v1/pwd/nickname', http_verb="PUT", json=kwargs)

0 comments on commit 81f7eaa

Please sign in to comment.