memberPlumbing/doorUpdater.py

244 lines
9.3 KiB
Python
Executable File

#!/usr/bin/env python3
from common import doors, membershipworks, memberLevels, doorSpecificSchedules
from hid.Credential import Credential
from hid.DoorController import ROOT, E
class Member():
def __init__(self, forename="", surname="", membershipWorksID="",
middleName="", email="", phone="",
cardholderID=None, doorAccess=[], onHold=False,
credentials=set(), levels=[], extraLevels=[], schedules=[]):
self.forename = forename
self.surname = surname
self.membershipWorksID = membershipWorksID
self.middleName = middleName
self.email = email
self.phone = phone
self.cardholderID = cardholderID
self.doorAccess = doorAccess
self.onHold = onHold
self.credentials = credentials
self.levels = levels
self.schedules = schedules
def __repr__(self):
return f"""Name: {self.forename} | {self.middleName} | {self.surname}
MembershipWorks ID: {self.membershipWorksID}
Email: {self.email}
Phone: {self.phone}
Cardholder ID: {self.cardholderID}
doorAccess: {self.doorAccess}
On Hold? {self.onHold}
Credentials: {self.credentials}
Levels: {self.levels}
Schedules: {self.schedules}
"""
class MembershipworksMember(Member):
def __init__(self, data):
super().__init__(data["First Name"],
data["Last Name"],
membershipWorksID=data["Account ID"],
email=data["Email"],
phone=data["Phone"],
onHold=data["Account on Hold"] != "")
if data["Access Card Number"] != "":
self.credentials = set([Credential(
code=(data["Access Card Facility Code"],
data["Access Card Number"]))])
else:
self.credentials = set()
levels = {k: v for k, v in memberLevels.items() if data[k] == k}
self.levels = list(levels.keys())
self.schedules = list(levels.values())
self.extraLevels = {
schedule: sum((doors for prop, doors in props.items()
if data[prop] == "Y"),
[])
for schedule, props in doorSpecificSchedules.items()
}
self.doorAccess = [
door for door, doorData in doors.items()
if data["Access " + doorData.access + "?"] == "Y"
]
def to_DoorMember(self, door):
doorLevels = [k for k, v in self.extraLevels.items() if door.name in v]
if door.name not in self.doorAccess or self.onHold:
schedules = []
else:
schedules = self.schedules + doorLevels
dm = DoorMember(door, **self.__dict__) # this is probably terrible
dm.levels = self.levels + doorLevels
dm.schedules = schedules
return dm
class DoorMember(Member):
def __init__(self, door, *args, **kwargs):
super().__init__(*args, **kwargs)
self.door = door
@classmethod
def from_cardholder(cls, data, door):
ch = cls(door=door,
forename=data.get('forename', ""),
surname=data.get('surname', ""),
membershipWorksID=data.attrib.get('custom2', ""),
middleName=data.attrib.get('middleName', ""),
email=data.attrib.get('email', ""),
phone=data.attrib.get('phone', ""),
cardholderID=data.attrib['cardholderID'])
ch.credentials = set(
Credential(hex=(c.attrib['rawCardNumber']))
for c in data.findall('{*}Credential'))
ch.levels = data.attrib.get('custom1', "").split('|')
ch.schedules = [r.attrib['scheduleName']
for r in data.findall('{*}Role')]
return ch
def attribs(self):
return {"forename": self.forename,
"surname": self.surname,
"middleName": self.middleName,
"email": self.email,
"phone": self.phone,
"custom1": "|".join(self.levels).replace("&", "and"),
"custom2": self.membershipWorksID}
def make_schedules(self, schedulesMap):
roles = [
E.Role({"roleID": self.cardholderID,
"scheduleID": schedulesMap[schedule],
"resourceID": "0"})
for schedule in self.schedules]
return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID},
E.Roles(*roles))
def make_credentials(self, newCredentials, cardFormats):
out = [
E.Credential(
{"formatName": str(credential.code[0]),
"cardNumber": str(credential.code[1]),
"formatID": cardFormats[str(credential.code[0])],
"isCard": "true",
"cardholderID": self.cardholderID})
for credential in newCredentials]
return E.Credentials({"action": "AD"}, *out)
def update_door(door, members):
cardFormats = door.get_cardFormats()
cardholders = {member.membershipWorksID: member
for member in [DoorMember.from_cardholder(ch, door)
for ch in door.get_cardholders()]}
schedulesMap = door.get_scheduleMap()
allCredentials = set(Credential(hex=c.attrib['rawCardNumber'])
for c in door.get_credentials())
# TODO: can I combine requests?
for membershipworksMember in members:
member = membershipworksMember.to_DoorMember(door)
# cardholder exists, compare contents
if member.membershipWorksID in cardholders:
ch = cardholders.pop(member.membershipWorksID)
member.cardholderID = ch.cardholderID
if member.attribs() != ch.attribs(): # update cardholder attributes
print(f"- Updating profile for {member.forename} {member.surname}")
print(f" - Old: {ch.attribs()}")
print(f" - New: {member.attribs()}")
door.doXMLRequest(ROOT(
E.Cardholders(
{"action": "UD", "cardholderID": member.cardholderID},
E.CardHolder(member.attribs()))))
if member.credentials != ch.credentials:
print(f"- Updating card for {member.forename} {member.surname}")
print(f" - {ch.credentials} -> {member.credentials}")
oldCards = ch.credentials
newCards = member.credentials
allNewCards = set(
card for m in members if m != membershipworksMember
for card in m.credentials)
# cards removed, and won't be reassigned to someone else
for card in (oldCards - newCards) - allNewCards:
door.doXMLRequest(ROOT(E.Credentials(
{"action": "UD",
"rawCardNumber": card.hex,
"isCard": "true"},
E.Credential({"cardholderID": ""}))))
if newCards - oldCards: # cards added
for card in newCards & allNewCards: # new card exists in another member
print([m for m in members
for card in m.credentials
if card in newCards])
raise Exception(f"Duplicate Card in input data! {card}")
# card existed in door, and needs to be reassigned
for card in newCards & allCredentials:
door.doXMLRequest(ROOT(E.Credentials(
{"action": "UD",
"rawCardNumber": card.hex,
"isCard": "true"},
E.Credential({"cardholderID": member.cardholderID}))))
# cards that never existed, and need to be created
if newCards - allCredentials:
door.doXMLRequest(ROOT(member.make_credentials(
newCards - allCredentials, cardFormats)))
if member.schedules != ch.schedules:
print("- Updating schedule for" +
f" {member.forename} {member.surname}:" +
f" {ch.schedules} -> {member.schedules}")
door.doXMLRequest(ROOT(
member.make_schedules(schedulesMap)))
else: # cardholder did not exist, so add them
print(f"- Adding Member:")
print(member)
resp = door.doXMLRequest(ROOT(
E.Cardholders(
{"action": "AD"},
E.Cardholder(member.attribs()))))
member.cardholderID = resp.find('{*}Cardholders/{*}Cardholder') \
.attrib["cardholderID"]
door.doXMLRequest(ROOT(
member.make_credentials(member.credentials, cardFormats),
member.make_schedules(schedulesMap)))
# TODO: delete cardholders that are no longer members?
def main():
memberData = membershipworks.get_members(
['members', 'staff', 'misc'],
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf,xeh,xse")
members = [MembershipworksMember(m) for m in memberData]
for door in doors.values():
print(door.name, door.ip)
update_door(door, members)
if __name__ == '__main__':
main()