2022-11-06 13:30:11 +00:00
|
|
|
import select
|
|
|
|
import socket
|
|
|
|
import time
|
2022-11-06 18:21:06 +00:00
|
|
|
import datetime
|
2022-11-06 13:30:11 +00:00
|
|
|
from threading import Lock, RLock, Condition, Thread
|
|
|
|
from typing import Callable, Optional, IO, Container
|
|
|
|
|
|
|
|
from . import mqtt, util
|
|
|
|
from .util import timestamp
|
|
|
|
|
|
|
|
class DoorControlNfc(util.Loggable):
|
|
|
|
"""Validates tokens read from a socket and tells a DoorControl to toggle its target"""
|
|
|
|
def __init__(self, config, control, mqtt_client = None):
|
2022-11-11 15:37:44 +00:00
|
|
|
super().__init__("nfc")
|
2022-11-06 13:30:11 +00:00
|
|
|
self._config = config
|
|
|
|
self._nfc = self._open_nfc_fifo()
|
|
|
|
self._control = control
|
|
|
|
self._stop = True
|
|
|
|
self._mutex = RLock()
|
|
|
|
self._cond = Condition(self._mutex)
|
|
|
|
self._fifo = None
|
|
|
|
self._mqttc = mqtt_client
|
|
|
|
self._read_valid_tokens()
|
|
|
|
self.last_invalid_token = mqtt.Value(self._mqttc,
|
|
|
|
"door/token/last_invalid", persistent=True)
|
|
|
|
|
|
|
|
def _open_nfc_fifo(self) -> IO:
|
|
|
|
"""Opens config.nfc_fifo as the FIFO through which detected tokens are passed in."""
|
|
|
|
self._logger().debug(f"Opening FIFO {self._config.nfc_fifo}")
|
2022-11-06 17:27:26 +00:00
|
|
|
return open(self._config.nfc_fifo, "r")
|
2022-11-06 13:30:11 +00:00
|
|
|
|
|
|
|
def _read_valid_tokens(self) -> Container:
|
|
|
|
"""Refreshes all tokens from config.valid_tokens"""
|
|
|
|
with self._mutex:
|
|
|
|
valid = {}
|
|
|
|
try:
|
|
|
|
self._logger().info("Loading tokens")
|
|
|
|
lines =[ s.strip() for s in open(self._config.valid_tokens, "r").readlines() ]
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
l = line.split('|')
|
2022-11-21 18:33:44 +00:00
|
|
|
if len(l) == 6:
|
2022-11-06 13:30:11 +00:00
|
|
|
if not l[0].strip().startswith('#'):
|
2022-11-21 18:33:44 +00:00
|
|
|
token, name, organization, email, valid_thru, paid = l
|
2022-11-06 13:30:11 +00:00
|
|
|
try:
|
|
|
|
if len(valid_thru.strip()) > 0:
|
|
|
|
valid_thru = datetime.date.fromisoformat(valid_thru)
|
|
|
|
else:
|
|
|
|
valid_thru = None
|
|
|
|
except Exception:
|
2022-11-06 18:19:57 +00:00
|
|
|
self._logger().exception(f"Could not parse valid thru date {valid_thru} for token {token} in line {i+1}")
|
2022-11-06 13:30:11 +00:00
|
|
|
valid_thru = None
|
|
|
|
self._logger().debug(f"Got token {token} associated with {name} <{email}> of {organization}, valid thru {valid_thru}")
|
|
|
|
if token in valid:
|
|
|
|
self._logger().warning(f"Overwriting token {token}")
|
|
|
|
valid[token] = {
|
|
|
|
'name': name,
|
|
|
|
'organization': organization,
|
|
|
|
'email': email,
|
2022-11-21 18:33:44 +00:00
|
|
|
'valid_thru': valid_thru,
|
|
|
|
'paid': paid.strip().lower() == 'yes'
|
2022-11-06 13:30:11 +00:00
|
|
|
}
|
|
|
|
else:
|
2022-11-21 18:33:44 +00:00
|
|
|
self._logger().warning(f"Skipping line {i} ({line}) since it does not contain exactly 6 data field")
|
2022-11-06 13:30:11 +00:00
|
|
|
except Exception as e:
|
|
|
|
valid = {}
|
|
|
|
self._logger().error(f"Error reading token file. Exception: {e}")
|
|
|
|
|
|
|
|
self._tokens = valid
|
|
|
|
|
|
|
|
def _run(self):
|
|
|
|
with self._cond:
|
|
|
|
while not self._stop:
|
|
|
|
if not self._fifo:
|
|
|
|
self._read_valid_tokens()
|
|
|
|
self._fifo = self._open_nfc_fifo()
|
|
|
|
if not self._fifo:
|
|
|
|
time.sleep(5)
|
|
|
|
continue
|
|
|
|
|
|
|
|
self._cond.release()
|
|
|
|
readable, _, _ = select.select(
|
|
|
|
[self._fifo],
|
|
|
|
[], [], 1)
|
|
|
|
self._cond.acquire()
|
|
|
|
|
|
|
|
if self._fifo in readable:
|
|
|
|
token = self._fifo.readline().strip()
|
|
|
|
if token == "":
|
|
|
|
self._fifo = None
|
|
|
|
continue
|
|
|
|
self._logger().debug(f"Token from nfc_fifo: {token}")
|
|
|
|
self.handle_token(token)
|
|
|
|
|
|
|
|
def handle_token(self, token):
|
|
|
|
with self._mutex:
|
2022-11-22 18:25:33 +00:00
|
|
|
if token not in self._tokens:
|
|
|
|
split_token = token.split(':')[0]
|
|
|
|
if split_token in self._tokens:
|
|
|
|
self._logger().warning(f"Old-style token: {split_token} -> {token}")
|
|
|
|
token = split_token
|
2022-11-06 13:30:11 +00:00
|
|
|
if token in self._tokens:
|
|
|
|
data = self._tokens[token]
|
|
|
|
|
|
|
|
if data['valid_thru'] is not None:
|
|
|
|
# if a valid thru date has been set we check if the token is still valid
|
|
|
|
authorized = datetime.date.today() <= data['valid_thru']
|
2022-11-21 18:33:44 +00:00
|
|
|
if not authorized:
|
|
|
|
self._logger().info(f"Expired token: {token} of {data['name']}")
|
|
|
|
elif not data['paid']:
|
|
|
|
authorized = datetime.date.today() <= datetime.date(2023, 3, 1)
|
|
|
|
if not authorized:
|
|
|
|
self._logger().info(f"Unpaid token: {token} of {data['name']}")
|
2022-11-06 13:30:11 +00:00
|
|
|
else:
|
|
|
|
authorized = True
|
|
|
|
|
|
|
|
if authorized:
|
|
|
|
self._logger().info(f"Valid token {token} of {data['name']}")
|
|
|
|
self._control.toggle()
|
|
|
|
else:
|
|
|
|
self._logger().warning(f"Invalid token: {token}")
|
|
|
|
self.last_invalid_token(f"{timestamp()};{token}")
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
with self._mutex:
|
|
|
|
if self._stop:
|
|
|
|
self._stop = False
|
|
|
|
self._task = Thread(target=self._run, daemon=True)
|
|
|
|
self._task.start()
|
|
|
|
print("Done")
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
with self._cond:
|
|
|
|
if not self._stop:
|
|
|
|
self._stop = True
|
|
|
|
self._cond.notify()
|
|
|
|
self._task.join()
|
|
|
|
|
|
|
|
|