Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: missing userid needs to be passed to add lock record #177

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix: missing userid needs to be passed to add lock record
shauntarves committed May 6, 2024
commit ee99c8a2223ca8d8b110e75f6b9d0878572a7a0f
18 changes: 16 additions & 2 deletions wyze_sdk/api/devices/locks.py
Original file line number Diff line number Diff line change
@@ -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``
@@ -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.
@@ -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.
21 changes: 20 additions & 1 deletion wyze_sdk/models/devices/locks.py
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 6 additions & 6 deletions wyze_sdk/service/base.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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)
@@ -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)

12 changes: 10 additions & 2 deletions wyze_sdk/service/ford_service.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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:
@@ -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)