Repository navigation
zigbee-lock: centralize credential/user table updates and tighten addCredential/updateCredential validation #3331
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| -- Copyright 2026 SmartThings, Inc. | ||
| -- Licensed under the Apache License, Version 2.0 | ||
|
|
||
| local tables = require "lock_utils.tables" | ||
| local consts = require "lock_utils.constants" | ||
|
|
||
| local lock_utils = {} | ||
|
|
@@ -52,6 +53,77 @@ function lock_utils.sync_device_state(device) | |
| end | ||
|
|
||
|
|
||
| -- [[ CREDENTIAL TABLE MANAGEMENT ]] -- | ||
|
|
||
| -- Add a credential and, optionally, a new user for it. `args.userIndex` is the credential's user. | ||
| -- When `add_user` is true the user is created too, so a user never exists without a credential; when false | ||
| -- the credential is attached to the already-existing user `args.userIndex`. | ||
| -- The credential is added first; the user is only added if that succeeded, so a failed or | ||
| -- colliding credential add leaves no user behind. Returns the result status of the credential add. | ||
| function lock_utils.add_credential_and_user(device, args, add_user) | ||
| local name = "Guest " .. args.userIndex | ||
| local credential_status = tables.add_entry(device, "credentials", { | ||
| userIndex = args.userIndex, | ||
| credentialIndex = args.credentialIndex, | ||
| credentialType = args.credentialType or consts.CRED_TYPE_PIN, | ||
| credentialName = args.credentialName or name, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not mistaken using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but we should. It is an optional field that is currently basically not utilized. If it is used, it can effectively pass a userName to a newly created user without explicitly calling update. Description updated. |
||
| }) | ||
| if add_user and credential_status == consts.COMMAND_RESULT.SUCCESS then | ||
| tables.add_entry(device, "users", { | ||
|
tpmanley marked this conversation as resolved.
Resolved
|
||
| userIndex = args.userIndex, | ||
| userName = args.credentialName or name, | ||
| userType = "guest", | ||
| }) | ||
| end | ||
| return credential_status | ||
| end | ||
|
|
||
| -- Apply the arguments of an updateCredential command to the credentials table. | ||
| -- Returns the command result status and the credential's userIndex. | ||
| function lock_utils.update_commanded_credential(device, credential_args) | ||
| local existing = tables.find_entry(device, "credentials", credential_args.credentialIndex) | ||
| if not existing then return consts.COMMAND_RESULT.FAILURE end | ||
| local name = credential_args.credentialName | ||
| if name ~= nil and name ~= existing.credentialName then | ||
| local status = tables.update_entry(device, "credentials", credential_args.credentialIndex, { credentialName = name }) | ||
| if status ~= consts.COMMAND_RESULT.SUCCESS then return status end | ||
| end | ||
| return consts.COMMAND_RESULT.SUCCESS, existing.userIndex | ||
| end | ||
|
|
||
| -- Per command: update the tables after the lock confirmed a set-credential command we sent. | ||
| -- Returns the command result status, or nil for any other command, plus the affected userIndex. | ||
| -- For addCredential, a userIndex of 0 creates a new user (its index is chosen here); otherwise the credential | ||
| -- is attached to the existing user with that index. | ||
| function lock_utils.apply_commanded_credential(device, command_in_progress, credential_args) | ||
| if command_in_progress == consts.LOCK_CREDENTIALS.ADD then | ||
| local user_index = credential_args.userIndex | ||
| if (user_index == 0) then user_index = tables.next_index(device, "users") end | ||
| local user_not_found = not tables.find_entry(device, "users", user_index) | ||
| local status = lock_utils.add_credential_and_user(device, { | ||
| userIndex = user_index, | ||
| credentialIndex = credential_args.credentialIndex, | ||
| credentialType = credential_args.credentialType, | ||
| credentialName = credential_args.credentialName, | ||
| }, user_not_found) | ||
| return status, status == consts.COMMAND_RESULT.SUCCESS and user_index or nil | ||
| elseif command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then | ||
| return lock_utils.update_commanded_credential(device, credential_args) | ||
| end | ||
| end | ||
|
|
||
| -- Per report: ensure a user and PIN credential exist for a code the lock reports at `credential_index` | ||
| -- without any command from us (sync, or a programming event). If a credential already exists there it | ||
| -- is left in place | ||
| function lock_utils.add_reported_credential(device, credential_index) | ||
| if tables.find_entry(device, "credentials", credential_index) then return end | ||
| lock_utils.add_credential_and_user(device, { | ||
| userIndex = tables.next_index(device, "users"), | ||
| credentialIndex = credential_index, | ||
| }, true) | ||
| end | ||
|
|
||
|
|
||
| -- [[ COMMAND RESULT STATE MANAGEMENT ]] -- | ||
|
|
||
| function lock_utils.emit_command_result(device, capability, command_name, status_code, additional_info) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inconsistent with our plan to allow credentials to be created even if the users table is full. I don't think it's a blocker, but it looks like we have two different policies for handling this case dependent on whether the add originated from a keypad or from the capability command. Worth revisiting in the follow up ticket, but I wouldn't consider this a blocker here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is closer to the expectation, where we don't allow a cred to be made if it can't have an associated user. I think the other case is the "problem", since we can't know that ahead of time, but yes, they both speak to the deeper problem at hand.