From 646033244f615c22414ae0ff0a6e1d22198f773e Mon Sep 17 00:00:00 2001 From: jeanGaston Date: Wed, 22 May 2024 12:32:30 +0200 Subject: [PATCH] add all files --- .gitignore | 2 + Client/main.py | 158 +++++++++++ Client/mfrc522.py | 380 +++++++++++++++++++++++++++ Client/ssd1306.py | 164 ++++++++++++ README.md | 1 + Server/Dockerfile | 34 +++ Server/Program/.dockerignore | 12 + Server/Program/WebAPI.py | 65 +++++ Server/Program/Webserver.py | 103 ++++++++ Server/Program/data.db | Bin 0 -> 32768 bytes Server/Program/database.py | 269 +++++++++++++++++++ Server/Program/entrypoint.sh | 17 ++ Server/Program/env.py | 10 + Server/Program/ldapSync.py | 164 ++++++++++++ Server/Program/requirements.txt | 4 + Server/Program/server.py | 15 ++ Server/Program/static/style.css | 102 +++++++ Server/Program/templates/LDAP.html | 16 ++ Server/Program/templates/index.html | 65 +++++ Server/Program/templates/logsdb.html | 83 ++++++ Server/Program/templates/userdb.html | 39 +++ Server/Program/templates/webgui.html | 27 ++ Server/docker-compose.yml | 17 ++ 23 files changed, 1747 insertions(+) create mode 100644 .gitignore create mode 100644 Client/main.py create mode 100644 Client/mfrc522.py create mode 100644 Client/ssd1306.py create mode 100644 Server/Dockerfile create mode 100644 Server/Program/.dockerignore create mode 100644 Server/Program/WebAPI.py create mode 100644 Server/Program/Webserver.py create mode 100644 Server/Program/data.db create mode 100644 Server/Program/database.py create mode 100644 Server/Program/entrypoint.sh create mode 100644 Server/Program/env.py create mode 100644 Server/Program/ldapSync.py create mode 100644 Server/Program/requirements.txt create mode 100644 Server/Program/server.py create mode 100644 Server/Program/static/style.css create mode 100644 Server/Program/templates/LDAP.html create mode 100644 Server/Program/templates/index.html create mode 100644 Server/Program/templates/logsdb.html create mode 100644 Server/Program/templates/userdb.html create mode 100644 Server/Program/templates/webgui.html create mode 100644 Server/docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3550301 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +Server/.env diff --git a/Client/main.py b/Client/main.py new file mode 100644 index 0000000..29cc79b --- /dev/null +++ b/Client/main.py @@ -0,0 +1,158 @@ +import network +import urequests as requests +import ujson as json +import time +from machine import Pin, SPI, I2C +from mfrc522 import MFRC522 +from ssd1306 import SSD1306_I2C + +# Global variables +DOOR_ID = 1 +WLAN_SSID = '[Your SSID]' +WLAN_PASS = '[Your password]' +SERVER_IP = '[Your server IP]' +SERVER_PORT = 5000 + +# Initialize RFID reader +reader = MFRC522(spi_id=0, sck=6, miso=4, mosi=7, cs=5, rst=22) + +# Initialize I2C for the OLED display +i2c = I2C(id=0, scl=Pin(1), sda=Pin(0), freq=200000) +oled = None + +# Initialize greenLED +greenled = Pin(16, Pin.OUT) +greenled.on() +time.sleep(0.5) +greenled.off() +# Initialize redLED +redled = Pin(21, Pin.OUT) +redled.on() +time.sleep(0.5) +redled.off() + +def init_oled(): + global oled + try: + oled = SSD1306_I2C(128, 64, i2c) + oled.fill(0) + oled.text('Initializing...', 0, 0) + oled.show() + except Exception as e: + print("display error:", e) + #init_oled() +def display_message(message, ip_address): + try: + oled.fill(0) + oled.text(f'Door ID: {DOOR_ID}', 0, 0) # Display Door ID at the top + oled.text("___________________", 0, 3) + + lines = message.split('\n') + for i, line in enumerate(lines): + oled.text(line, 0, 20 + i * 10) # Adjust the y position for each line + oled.text("__________________", 0, 47) + oled.text(ip_address, 0, 57) # Display IP address at the bottom + oled.show() + except Exception as e: + greenled.off() + redled.off() + print("display error:", e) + init_oled() + +# Connect to WiFi +def connect_wifi(ssid, password): + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + wlan.connect(ssid, password) + while not wlan.isconnected(): + time.sleep(0.5) + print("Connecting to WiFi...") + ip_address = wlan.ifconfig()[0] + print("Connected to WiFi:", ip_address) + display_message('WiFi Connected', ip_address) + + # Test connection to the server + + try: + response = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/") + if response.status_code == 200: + print("Server connection successful") + display_message('Server Connected', ip_address) + else: + print("Server connection failed") + display_message('Server Fail', ip_address) + time.sleep(5) + while response.status_code != 200 : + wlan.connect(ssid, password) + response = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/") + display_message('Reconnecting ...', ip_address) + time.sleep(1) + except Exception as e: + print("Server connection error:", e) + display_message(f'Server Error \n {e}', ip_address) + time.sleep(5) +# while response.status_code != 200 : +# wlan.connect(ssid, password) +# try : +# response = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/") +# display_message('Reconnecting ...', ip_address) +# time.sleep(1) +# except: +# pass +# Function to send RFID UID to the server +def send_rfid_to_server(rfid_uid): + url = f"http://{SERVER_IP}:{SERVER_PORT}/access" + headers = {'Content-Type': 'application/json'} + data = { + 'rfid_uid': rfid_uid, + 'door_id': DOOR_ID + } + response = requests.post(url, headers=headers, data=json.dumps(data)) + return response.json() + +# Main loop to scan RFID tags +def main(): + # Retry mechanism for OLED initialization + for _ in range(3): + try: + init_oled() + break + except Exception as e: + print("OLED init error:", e) + time.sleep(1) + + connect_wifi(WLAN_SSID, WLAN_PASS) + ip_address = network.WLAN(network.STA_IF).ifconfig()[0] + display_message('Scan your tag', ip_address) + + while True: + + (status, tag_type) = reader.request(reader.REQIDL) + if status == reader.OK: + (status, uid) = reader.SelectTagSN() + if status == reader.OK: + rfid_uid_decimal = ''.join([str(i) for i in uid]) + print("RFID UID:", rfid_uid_decimal) + display_message('Checking...', ip_address) + + response = send_rfid_to_server(rfid_uid_decimal) + + if response.get('access_granted'): + user_upn = response.get('upn') + print("Access Granted:", user_upn) + display_message(f'Access Granted\n{user_upn}', ip_address) + # Turn on the LED to indicate door open + greenled.on() + # Add code here to open the door (e.g., trigger a relay) + else: + print("Access Denied") + display_message('Access Denied', ip_address) + redled.on() + + time.sleep(2) # Delay to avoid rapid repeated reads + greenled.off() + redled.off() # Turn off the LED + display_message('Scan your tag', ip_address) + +if __name__ == "__main__": + main() diff --git a/Client/mfrc522.py b/Client/mfrc522.py new file mode 100644 index 0000000..6fa7124 --- /dev/null +++ b/Client/mfrc522.py @@ -0,0 +1,380 @@ +from machine import Pin, SPI +from os import uname + + +class MFRC522: + + DEBUG = False + OK = 0 + NOTAGERR = 1 + ERR = 2 + + REQIDL = 0x26 + REQALL = 0x52 + AUTHENT1A = 0x60 + AUTHENT1B = 0x61 + + PICC_ANTICOLL1 = 0x93 + PICC_ANTICOLL2 = 0x95 + PICC_ANTICOLL3 = 0x97 + + + def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0): + + self.sck = Pin(sck, Pin.OUT) + self.mosi = Pin(mosi, Pin.OUT) + self.miso = Pin(miso) + self.rst = Pin(rst, Pin.OUT) + self.cs = Pin(cs, Pin.OUT) + + self.rst.value(0) + self.cs.value(1) + + board = uname()[0] + + if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': + self.spi = SPI(0) + self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso)) + elif (board == 'esp8266') or (board == 'esp32'): + self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) + self.spi.init() + elif board == 'rp2': + self.spi = SPI(spi_id,baudrate=baudrate,sck=self.sck, mosi= self.mosi, miso= self.miso) + else: + raise RuntimeError("Unsupported platform") + + self.rst.value(1) + self.init() + + def _wreg(self, reg, val): + + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e))) + self.spi.write(b'%c' % int(0xff & val)) + self.cs.value(1) + + def _rreg(self, reg): + + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80))) + val = self.spi.read(1) + self.cs.value(1) + + return val[0] + + def _sflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) | mask) + + def _cflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) & (~mask)) + + def _tocard(self, cmd, send): + + recv = [] + bits = irq_en = wait_irq = n = 0 + stat = self.ERR + + if cmd == 0x0E: + irq_en = 0x12 + wait_irq = 0x10 + elif cmd == 0x0C: + irq_en = 0x77 + wait_irq = 0x30 + + self._wreg(0x02, irq_en | 0x80) + self._cflags(0x04, 0x80) + self._sflags(0x0A, 0x80) + self._wreg(0x01, 0x00) + + for c in send: + self._wreg(0x09, c) + self._wreg(0x01, cmd) + + if cmd == 0x0C: + self._sflags(0x0D, 0x80) + + i = 2000 + while True: + n = self._rreg(0x04) + i -= 1 + if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)): + break + + self._cflags(0x0D, 0x80) + + if i: + if (self._rreg(0x06) & 0x1B) == 0x00: + stat = self.OK + + if n & irq_en & 0x01: + stat = self.NOTAGERR + elif cmd == 0x0C: + n = self._rreg(0x0A) + lbits = self._rreg(0x0C) & 0x07 + if lbits != 0: + bits = (n - 1) * 8 + lbits + else: + bits = n * 8 + + if n == 0: + n = 1 + elif n > 16: + n = 16 + + for _ in range(n): + recv.append(self._rreg(0x09)) + else: + stat = self.ERR + + return stat, recv, bits + + def _crc(self, data): + + self._cflags(0x05, 0x04) + self._sflags(0x0A, 0x80) + + for c in data: + self._wreg(0x09, c) + + self._wreg(0x01, 0x03) + + i = 0xFF + while True: + n = self._rreg(0x05) + i -= 1 + if not ((i != 0) and not (n & 0x04)): + break + + return [self._rreg(0x22), self._rreg(0x21)] + + def init(self): + + self.reset() + self._wreg(0x2A, 0x8D) + self._wreg(0x2B, 0x3E) + self._wreg(0x2D, 30) + self._wreg(0x2C, 0) + self._wreg(0x15, 0x40) + self._wreg(0x11, 0x3D) + self.antenna_on() + + def reset(self): + self._wreg(0x01, 0x0F) + + def antenna_on(self, on=True): + + if on and ~(self._rreg(0x14) & 0x03): + self._sflags(0x14, 0x03) + else: + self._cflags(0x14, 0x03) + + def request(self, mode): + + self._wreg(0x0D, 0x07) + (stat, recv, bits) = self._tocard(0x0C, [mode]) + + if (stat != self.OK) | (bits != 0x10): + stat = self.ERR + + return stat, bits + + def anticoll(self,anticolN): + + ser_chk = 0 + ser = [anticolN, 0x20] + + self._wreg(0x0D, 0x00) + (stat, recv, bits) = self._tocard(0x0C, ser) + + if stat == self.OK: + if len(recv) == 5: + for i in range(4): + ser_chk = ser_chk ^ recv[i] + if ser_chk != recv[4]: + stat = self.ERR + else: + stat = self.ERR + + return stat, recv + + + def PcdSelect(self, serNum,anticolN): + backData = [] + buf = [] + buf.append(anticolN) + buf.append(0x70) + #i = 0 + ###xorsum=0; + for i in serNum: + buf.append(i) + #while i<5: + # buf.append(serNum[i]) + # i = i + 1 + pOut = self._crc(buf) + buf.append(pOut[0]) + buf.append(pOut[1]) + (status, backData, backLen) = self._tocard( 0x0C, buf) + if (status == self.OK) and (backLen == 0x18): + return 1 + else: + return 0 + + + def SelectTag(self, uid): + byte5 = 0 + + #(status,puid)= self.anticoll(self.PICC_ANTICOLL1) + #print("uid",uid,"puid",puid) + for i in uid: + byte5 = byte5 ^ i + puid = uid + [byte5] + + if self.PcdSelect(puid,self.PICC_ANTICOLL1) == 0: + return (self.ERR,[]) + return (self.OK , uid) + + def tohexstring(self,v): + s="[" + for i in v: + if i != v[0]: + s = s+ ", " + s=s+ "0x{:02X}".format(i) + s= s+ "]" + return s + + + + + def SelectTagSN(self): + valid_uid=[] + (status,uid)= self.anticoll(self.PICC_ANTICOLL1) + #print("Select Tag 1:",self.tohexstring(uid)) + if status != self.OK: + return (self.ERR,[]) + + if self.DEBUG: print("anticol(1) {}".format(uid)) + if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: + return (self.ERR,[]) + if self.DEBUG: print("pcdSelect(1) {}".format(uid)) + + #check if first byte is 0x88 + if uid[0] == 0x88 : + #ok we have another type of card + valid_uid.extend(uid[1:4]) + (status,uid)=self.anticoll(self.PICC_ANTICOLL2) + #print("Select Tag 2:",self.tohexstring(uid)) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(2) {}".format(uid)) + rtn = self.PcdSelect(uid,self.PICC_ANTICOLL2) + if self.DEBUG: print("pcdSelect(2) return={} uid={}".format(rtn,uid)) + if rtn == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect2() {}".format(uid)) + #now check again if uid[0] is 0x88 + if uid[0] == 0x88 : + valid_uid.extend(uid[1:4]) + (status , uid) = self.anticoll(self.PICC_ANTICOLL3) + #print("Select Tag 3:",self.tohexstring(uid)) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(3) {}".format(uid)) + if self.MFRC522_PcdSelect(uid,self.PICC_ANTICOLL3) == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect(3) {}".format(uid)) + valid_uid.extend(uid[0:5]) + # if we are here than the uid is ok + # let's remove the last BYTE whic is the XOR sum + + return (self.OK , valid_uid[:len(valid_uid)-1]) + #return (self.OK , valid_uid) + + + + + + + def auth(self, mode, addr, sect, ser): + return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] + + def authKeys(self,uid,addr,keyA=None, keyB=None): + status = self.ERR + if keyA is not None: + status = self.auth(self.AUTHENT1A, addr, keyA, uid) + elif keyB is not None: + status = self.auth(self.AUTHENT1B, addr, keyB, uid) + return status + + + def stop_crypto1(self): + self._cflags(0x08, 0x08) + + def read(self, addr): + + data = [0x30, addr] + data += self._crc(data) + (stat, recv, _) = self._tocard(0x0C, data) + return stat, recv + + def write(self, addr, data): + + buf = [0xA0, addr] + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + else: + buf = [] + for i in range(16): + buf.append(data[i]) + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + return stat + + + def writeSectorBlock(self,uid, sector, block, data, keyA=None, keyB = None): + absoluteBlock = sector * 4 + (block % 4) + if absoluteBlock > 63 : + return self.ERR + if len(data) != 16: + return self.ERR + if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR : + return self.write(absoluteBlock, data) + return self.ERR + + def readSectorBlock(self,uid ,sector, block, keyA=None, keyB = None): + absoluteBlock = sector * 4 + (block % 4) + if absoluteBlock > 63 : + return self.ERR, None + if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR : + return self.read(absoluteBlock) + return self.ERR, None + + def MFRC522_DumpClassic1K(self,uid, Start=0, End=64, keyA=None, keyB=None): + for absoluteBlock in range(Start,End): + status = self.authKeys(uid,absoluteBlock,keyA,keyB) + # Check if authenticated + print("{:02d} S{:02d} B{:1d}: ".format(absoluteBlock, absoluteBlock//4 , absoluteBlock % 4),end="") + if status == self.OK: + status, block = self.read(absoluteBlock) + if status == self.ERR: + break + else: + for value in block: + print("{:02X} ".format(value),end="") + print(" ",end="") + for value in block: + if (value > 0x20) and (value < 0x7f): + print(chr(value),end="") + else: + print('.',end="") + print("") + else: + break + if status == self.ERR: + print("Authentication error") + return self.ERR + return self.OK \ No newline at end of file diff --git a/Client/ssd1306.py b/Client/ssd1306.py new file mode 100644 index 0000000..37ad682 --- /dev/null +++ b/Client/ssd1306.py @@ -0,0 +1,164 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces + +from micropython import const +import framebuf + + +# register definitions +SET_CONTRAST = const(0x81) +SET_ENTIRE_ON = const(0xA4) +SET_NORM_INV = const(0xA6) +SET_DISP = const(0xAE) +SET_MEM_ADDR = const(0x20) +SET_COL_ADDR = const(0x21) +SET_PAGE_ADDR = const(0x22) +SET_DISP_START_LINE = const(0x40) +SET_SEG_REMAP = const(0xA0) +SET_MUX_RATIO = const(0xA8) +SET_IREF_SELECT = const(0xAD) +SET_COM_OUT_DIR = const(0xC0) +SET_DISP_OFFSET = const(0xD3) +SET_COM_PIN_CFG = const(0xDA) +SET_DISP_CLK_DIV = const(0xD5) +SET_PRECHARGE = const(0xD9) +SET_VCOM_DESEL = const(0xDB) +SET_CHARGE_PUMP = const(0x8D) + + +# Subclassing FrameBuffer provides support for graphics primitives +# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html +class SSD1306(framebuf.FrameBuffer): + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + self.buffer = bytearray(self.pages * self.width) + super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP, # display off + # address setting + SET_MEM_ADDR, + 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE, # start at line 0 + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 + SET_MUX_RATIO, + self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, + 0x00, + SET_COM_PIN_CFG, + 0x02 if self.width > 2 * self.height else 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, + 0x80, + SET_PRECHARGE, + 0x22 if self.external_vcc else 0xF1, + SET_VCOM_DESEL, + 0x30, # 0.83*Vcc + # display + SET_CONTRAST, + 0xFF, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + SET_IREF_SELECT, + 0x30, # enable internal IREF during display on + # charge pump + SET_CHARGE_PUMP, + 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01, # display on + ): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP) + + def poweron(self): + self.write_cmd(SET_DISP | 0x01) + + def contrast(self, contrast): + self.write_cmd(SET_CONTRAST) + self.write_cmd(contrast) + + def invert(self, invert): + self.write_cmd(SET_NORM_INV | (invert & 1)) + + def rotate(self, rotate): + self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3)) + self.write_cmd(SET_SEG_REMAP | (rotate & 1)) + + def show(self): + x0 = 0 + x1 = self.width - 1 + if self.width != 128: + # narrow displays use centred columns + col_offset = (128 - self.width) // 2 + x0 += col_offset + x1 += col_offset + self.write_cmd(SET_COL_ADDR) + self.write_cmd(x0) + self.write_cmd(x1) + self.write_cmd(SET_PAGE_ADDR) + self.write_cmd(0) + self.write_cmd(self.pages - 1) + self.write_data(self.buffer) + + +class SSD1306_I2C(SSD1306): + def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): + self.i2c = i2c + self.addr = addr + self.temp = bytearray(2) + self.write_list = [b"\x40", None] # Co=0, D/C#=1 + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.temp[0] = 0x80 # Co=1, D/C#=0 + self.temp[1] = cmd + self.i2c.writeto(self.addr, self.temp) + + def write_data(self, buf): + self.write_list[1] = buf + self.i2c.writevto(self.addr, self.write_list) + + +class SSD1306_SPI(SSD1306): + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + self.rate = 10 * 1024 * 1024 + dc.init(dc.OUT, value=0) + res.init(res.OUT, value=0) + cs.init(cs.OUT, value=1) + self.spi = spi + self.dc = dc + self.res = res + self.cs = cs + import time + + self.res(1) + time.sleep_ms(1) + self.res(0) + time.sleep_ms(10) + self.res(1) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(0) + self.cs(0) + self.spi.write(bytearray([cmd])) + self.cs(1) + + def write_data(self, buf): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(1) + self.cs(0) + self.spi.write(buf) + self.cs(1) diff --git a/README.md b/README.md index 9ced707..37731cb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # RF-AD Simple RFID access control system linked to AD + diff --git a/Server/Dockerfile b/Server/Dockerfile new file mode 100644 index 0000000..4a80aab --- /dev/null +++ b/Server/Dockerfile @@ -0,0 +1,34 @@ +# Use an official Python runtime as a parent image +FROM python:3.9 + +# Set the working directory in the container +WORKDIR /Program +# Install the required system packages +RUN apt-get update && \ + apt-get install -y \ + gcc \ + libldap2-dev \ + libsasl2-dev \ + libssl-dev \ + build-essential \ + python3-flask \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +# Copy the current directory contents into the container at /Program +COPY ./Program/ /Program + +# Copy the entrypoint script into the container +COPY ./Program/entrypoint.sh /entrypoint.sh + +# Install any needed packages specified in requirements.txt +RUN pip install -r requirements.txt + +# Make the entrypoint script executable +RUN chmod +x /entrypoint.sh + +# Make port 5000 available to the world outside this container +EXPOSE 5000 + +# Set the entrypoint to the entrypoint script +ENTRYPOINT ["/entrypoint.sh"] + + diff --git a/Server/Program/.dockerignore b/Server/Program/.dockerignore new file mode 100644 index 0000000..db6a267 --- /dev/null +++ b/Server/Program/.dockerignore @@ -0,0 +1,12 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +env +venv +.idea +.vscode +.git +.gitignore +.DS_Store +*.db \ No newline at end of file diff --git a/Server/Program/WebAPI.py b/Server/Program/WebAPI.py new file mode 100644 index 0000000..d875193 --- /dev/null +++ b/Server/Program/WebAPI.py @@ -0,0 +1,65 @@ +from threading import Thread +from flask import Flask, request, jsonify +from env import * +import sqlite3 + +app = Flask(__name__) + +# Function to verify if the user is allowed to open the door +def check_access(rfid_uid, door_id): + try: + conn = sqlite3.connect(DBFILE) # Update with your database file path + cursor = conn.cursor() + + # Get the user's UPN and group memberships based on the RFID UID + cursor.execute("SELECT upn, MemberOf FROM Users WHERE rFIDUID = ?", (rfid_uid,)) + user_data = cursor.fetchone() + if user_data is None: + return False, None # User not found + + upn, user_groups = user_data + + # Get the group associated with the door + cursor.execute("SELECT GroupCn FROM Doors WHERE id = ?", (door_id,)) + door_group = cursor.fetchone() + if door_group is None: + return False, None # Door not found + + door_group = door_group[0] + + # Check if the user's group is allowed to open the door + if door_group in user_groups.split(','): + return True, upn # Access granted + else: + return False, None # Access denied + + except sqlite3.Error as e: + print(f"SQLite Error: {e}") + return False, None + +# Route to handle door access requests +@app.route('/access', methods=['POST']) +def door_access(): + data = request.get_json() + rfid_uid = data.get('rfid_uid') + door_id = data.get('door_id') + + if rfid_uid is None or door_id is None: + return jsonify({'error': 'RFID UID and door ID are required'}), 400 + + access_granted, upn = check_access(rfid_uid, door_id) + if access_granted: + return jsonify({'access_granted': True, 'upn': upn}), 200 + else: + return jsonify({'access_granted': False}), 200 + +def run_flask_app(): + app.run(debug=True, use_reloader=False, port=WebAPIPORT) +def run_webAPI_thread(): + print(f"STARTING API on port {WebAPIPORT}") + flask_thread = Thread(target=run_flask_app) + flask_thread.start() + flask_thread.join() + +if __name__ == '__main__': + app.run(debug=True) diff --git a/Server/Program/Webserver.py b/Server/Program/Webserver.py new file mode 100644 index 0000000..20117aa --- /dev/null +++ b/Server/Program/Webserver.py @@ -0,0 +1,103 @@ +from threading import Thread +from flask import Flask, render_template, send_file, Response, request, redirect, jsonify +import io +from ldapSync import sync_ldap_to_database +from database import * +from env import * + +app = Flask(__name__) + + + + + +# Route to the home +@app.route('/') +def add_door_form(): + existing_groups = get_existing_groups(DBFILE) # Update with your database file path + logs = get_latest_logs(DBFILE,5) + #print(logs[0]) + return render_template('./index.html', existing_groups=existing_groups, logs=logs) +# Route to display the fuser db +@app.route('/UserDB') +def index(): + users = get_users() + return render_template('userdb.html', users=users) +# Route to display the fuser db +@app.route('/LogsDB') +def logsdb(): + logs = get_logs() + return render_template('logsdb.html', logs=logs) + +@app.route('/export_logs') +def export_logs(): + logs = get_logs() + + # Create a file-like string to write logs + log_output = io.StringIO() + for log in logs: + log_line = f"{log[0]},{log[1]},{log[2]},{log[3]},{'Yes' if log[4] else 'No'},\n" + log_output.write(log_line) + + # Set the position to the beginning of the stream + log_output.seek(0) + + # Create a response with the file data + return Response( + log_output, + mimetype="text/plain", + headers={"Content-disposition": "attachment; filename=logs.csv"} + ) + +# Route to handle form submission and add the door to the database +@app.route('/add_door', methods=['POST']) +def add_door(): + Door_id = request.form["Door_id"] + group_cn = request.form["group_cn"] + + # Update with your database file path + exec = add_door_to_database(DBFILE, group_cn, Door_id) + if add_door_to_database(DBFILE, group_cn, Door_id): + return redirect('/') + else: + return f"Failed to add door to the database." + +# Route to handle sync button click +@app.route('/sync') +def sync(): + sync_ldap_to_database(DBFILE) + return render_template('./LDAP.html') + redirect('/') + + +# Route to handle door access requests +@app.route('/access', methods=['POST']) +def door_access(): + data = request.get_json() + rfid_uid = data.get('rfid_uid') + door_id = data.get('door_id') + + if rfid_uid is None or door_id is None: + return jsonify({'error': 'RFID UID and door ID are required'}), 400 + + access_granted, upn = check_access(rfid_uid, door_id) + if access_granted: + print('') + log_access_attempt(DBFILE,upn,rfid_uid,True,door_id) + return jsonify({'access_granted': True, 'upn': upn}), 200 + + else: + log_access_attempt(DBFILE,upn,rfid_uid,False,door_id) + return jsonify({'access_granted': False}), 403 + + +def run_flask_app(): + app.run(debug=True, use_reloader=False, port=5000, host="0.0.0.0") +def run_webServer_thread(): + print(f'STARTING WEB SERVER ON PORT {WebServerPORT}') + flask_thread = Thread(target=run_flask_app, daemon=True) + flask_thread.start() + # flask_thread.join() + +if __name__ == '__main__': + app.run(debug=True) diff --git a/Server/Program/data.db b/Server/Program/data.db new file mode 100644 index 0000000000000000000000000000000000000000..e9463e43faebff6662aafad37dd9b5f87d42e75c GIT binary patch literal 32768 zcmeI5Pi)&%9LMcAYyLIypc`KKv$@)(NmJ|n@$Juhf)zfqPS z3b|5CsC+AbSE*G#DKD2kH`{YDUnVJ$mnD@uL(Y?+e zxrmlGsIx^iRJJzNrInVu=r+|0&871T&CBYW?qzjht+l$e!nNn!l~z5X7;J5K`h)iN zuG(@hwbbawVZYOhYww*~I=i-X*54FWY<9c7^^h0Qt=D_)ok3?)J-xcR>@KW~Sv$Ad zbe9%ae2-`M2CAw}_ng~wSI)Q>)w6u?zB;?LS&d8~qVrsih}L)m7Y%oJQdD~riWBZ{ z3Sr(iU%hqZP)<=4@%B+aO1_8wVj>c$JgJ+l)NfN-w} zm_12kihf&vc4G|9XpHKvlvAds#aj)3azXE4nT<>@XdBDL(J0<}BXBjGl(8(TbJ+>y z^=VPq+S%;9ySHz&hl8&FdOfhSj)V1~_e3#(-}e3UBkeCT5%2agAl9Jg98ZlP$^Q}h z*M>b1v-&`9%GA*yC3lvkA`VE)wCvA>e|f!05_FY}5d-x1`Wf z00e*l5C8%|00;m9An?B=a73z$f00e*l5C8%|00=xi1PYQO`X~R{QH@HY8p-4(rQrX8 zfOr32l>Zd?0}2oT0zd!=00AHX1b_e#00KY&2mk>f@JtcNi<01fH2{0(|1Op5rGM6sb+HYR48<(Cu1 zR5NT!nVD?4_7WEhCkpv7+T3lVStc_v!ZxEsrgVw~b-<7kqYaz4yrR$3H~ zB#N2Psf{F19bS9c*gIw5Uz@ z)1`JezAxXG4UGp3V{5-}LynyQvK)$e4%;S4Lsv->^V#!=n|4~z%1L5uX%^Kj#8T2_ zGD(bVjWI&(v^}B}Bj&pUaTw8TUAI#$7Zb%u({; zZXf(gU~vEc0ssF0lYYB~@qqvk00KY&2mk>f00e*l5C8%|00;nqf0;l|l%_=fFGFYk E29irD^8f$< literal 0 HcmV?d00001 diff --git a/Server/Program/database.py b/Server/Program/database.py new file mode 100644 index 0000000..c2e28eb --- /dev/null +++ b/Server/Program/database.py @@ -0,0 +1,269 @@ +from datetime import datetime +import sqlite3 +from env import * + +# Function to check if a table exists in the database +def table_exists(cursor, table_name): + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)) + return cursor.fetchone() is not None + +# Function to create the Users table +def create_users_table(cursor): + cursor.execute('''CREATE TABLE Users ( + upn TEXT PRIMARY KEY, + rFIDUID TEXT, + MemberOf TEXT, + FOREIGN KEY (MemberOf) REFERENCES Groups(cn) + )''') + +# Function to create the Groups table +def create_groups_table(cursor): + cursor.execute('''CREATE TABLE Groups ( + cn TEXT PRIMARY KEY + )''') + +# Function to create the Doors table +def create_doors_table(cursor): + cursor.execute('''CREATE TABLE Doors ( + id INTEGER PRIMARY KEY, + GroupCn TEXT, + FOREIGN KEY (GroupCn) REFERENCES Groups(cn) + )''') +# Function to create the logs table +def create_logs_table(cursor): + """ + Create a log table with columns id, timestamp, user, and granted. + + :param db_file: The database file path. + """ + cursor.execute(''' + CREATE TABLE IF NOT EXISTS log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT , + user TEXT , + rFIDUID TEXT, + door_id INTEGER , + granted BOOLEAN , + FOREIGN KEY (door_id) REFERENCES Doors (id) + FOREIGN KEY (user) REFERENCES Users (upn) + FOREIGN KEY (rFIDUID) REFERENCES Users (rFIDUID) + ) + ''') +# Function to setup the database +def setup_database(db_file): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + # Check and create Users table + if not table_exists(cursor, "Users"): + create_users_table(cursor) + print(f"[{datetime.now()}] Users table created successfully.") + else: + print(f"[{datetime.now()}] Users table already exists.") + + # Check and create Groups table + if not table_exists(cursor, "Groups"): + create_groups_table(cursor) + print(f"[{datetime.now()}] Groups table created successfully.") + else: + print(f"[{datetime.now()}] Groups table already exists.") + + # Check and create Doors table + if not table_exists(cursor, "Doors"): + create_doors_table(cursor) + print(f"[{datetime.now()}] Doors table created successfully.") + else: + print(f"[{datetime.now()}] Doors table already exists.") + # Check and create Doors table + if not table_exists(cursor, "Log"): + create_logs_table(cursor) + print(f"[{datetime.now()}] Log table created successfully.") + else: + print(f"[{datetime.now()}] Log table already exists.") + # Commit changes and close connection + conn.commit() + conn.close() + +def log_access_attempt(db_file, user, rFIDUID, granted, doorID): + """ + Log an access attempt to the log table. + + :param db_file: The database file path. + :param user: The user attempting access. + :param rFIDUID: The user's tag uid + :param granted: Whether access was granted (True/False). + :param doorID: The door id + """ + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + + print(f'[{datetime.now()}] User {user} get granted : {granted} on door : {doorID}') + cursor.execute(''' + INSERT INTO log (timestamp, user, rFIDUID, granted, door_id) VALUES (?, ?, ?, ?, ?) + ''', (datetime.now(), user, rFIDUID, granted, doorID)) + + conn.commit() + conn.close() +def print_users_table(cursor): + cursor.execute("SELECT * FROM Users") + rows = cursor.fetchall() + print("Users:") + for row in rows: + print(row) + +# Function to print the content of the Groups table +def print_groups_table(cursor): + cursor.execute("SELECT * FROM Groups") + rows = cursor.fetchall() + print("Groups:") + for row in rows: + print(row) + +# Function to print the content of the Doors table +def print_doors_table(cursor): + cursor.execute("SELECT * FROM Doors") + rows = cursor.fetchall() + print("Doors:") + for row in rows: + print(row) +# Function to print the content of the Log table +def print_log_table(cursor): + cursor.execute("SELECT * FROM log") + rows = cursor.fetchall() + print("Logs:") + for row in rows: + print(row) + +# Function to print the content of the entire database +def print_database_content(db_file): + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + print_users_table(cursor) + print_groups_table(cursor) + print_doors_table(cursor) + print_log_table(cursor) + + conn.close() + +def get_logs(): + + """ + Fetch all logs from the log table in the database. + :return: List of log records. + """ + conn = sqlite3.connect(DBFILE) + cursor = conn.cursor() + + cursor.execute(''' + SELECT timestamp, user, rFIDUID, granted, door_id + FROM log + ORDER BY id DESC + ''') + + logs = cursor.fetchall() + + conn.close() + return logs + + +def get_latest_logs(db_file,limit=10): + """ + Fetch the latest logs from the database. + + :param limit: The number of latest logs to fetch. + :return: List of log entries. + """ + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + cursor.execute(''' + SELECT timestamp, user, rFIDUID, granted, door_id + FROM log + ORDER BY id DESC + LIMIT ? + ''', (limit,)) + + logs = cursor.fetchall() + conn.close() + return logs +# Function to fetch list of existing groups from the database +def get_existing_groups(db_file): + try: + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + cursor.execute("SELECT cn FROM Groups") + groups = cursor.fetchall() + conn.close() + return [group[0] for group in groups] + except sqlite3.Error as e: + print(f"SQLite Error: {e}") + return [] + +def get_users(): + """ + Fetch all users from the Users table in the database. + :return: List of user records. + """ + conn = sqlite3.connect(DBFILE) + cursor = conn.cursor() + + cursor.execute('SELECT upn, rFIDUID, MemberOf FROM Users') + users = cursor.fetchall() + + conn.close() + return users +# Function to add a door to the database +def add_door_to_database(db_file, group_cn, Door_id): + try: + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + cursor.execute("INSERT INTO Doors (id, GroupCn) VALUES (?,?)", (Door_id,group_cn,)) + conn.commit() + conn.close() + #print_database_content(DBFILE) + return True + except sqlite3.Error as e: + #print_database_content(DBFILE) + print(f"SQLite Error: {e}") + return (False, e) + +# Function to verify if the user is allowed to open the door +def check_access(rfid_uid_str, door_id): + try: + conn = sqlite3.connect(DBFILE) # Update with your database file path + cursor = conn.cursor() + + # Convert the received RFID UID string to bytes + rfid_uid_bytes = rfid_uid_str.encode('utf-8') + + # Get the user's UPN and group memberships based on the RFID UID + cursor.execute("SELECT upn, MemberOf FROM Users WHERE rFIDUID = ?", (rfid_uid_bytes,)) + user_data = cursor.fetchone() + if user_data is None: + return False, None # User not found + + upn_bytes, user_groups = user_data + + # Decode the UPN bytes to string + upn = upn_bytes.decode('utf-8') + + # Get the group associated with the door + cursor.execute("SELECT GroupCn FROM Doors WHERE id = ?", (door_id,)) + door_group = cursor.fetchone() + if door_group is None: + return False, None # Door not found + + door_group = door_group[0] + + # Check if the user's group is allowed to open the door + if door_group in user_groups.split(','): + return True, upn # Access granted + else: + return False, None # Access denied + + except sqlite3.Error as e: + print(f"SQLite Error: {e}") + return False, None \ No newline at end of file diff --git a/Server/Program/entrypoint.sh b/Server/Program/entrypoint.sh new file mode 100644 index 0000000..2f09463 --- /dev/null +++ b/Server/Program/entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/sh +ls /app/Program +echo patate +# Create env.py with environment variables +cat < /app/env.py +LDAPUSER = "${LDAPUSER}" +LDAPPASS = "${LDAPPASS}" +LDAP_SERVER = "${LDAP_SERVER}" +DOOR_ACCESS_GROUPS_DN = "${DOOR_ACCESS_GROUPS_DN}" +USERS_DN = "${USERS_DN}" +DBFILE = "${DBFILE}" +WebServerPORT = ${WebServerPORT} +EOT + + +# Run the main server script +exec python3 /Program/server.py diff --git a/Server/Program/env.py b/Server/Program/env.py new file mode 100644 index 0000000..7b2e71b --- /dev/null +++ b/Server/Program/env.py @@ -0,0 +1,10 @@ +LDAPUSER = "RO.doorAccess" +LDAPPASS = "@dmin.01I" +LDAP_SERVER = 'ldap://10.100.100.10' +DOOR_ACCESS_GROUPS_DN = 'ou=DoorAccess,ou=Groups,ou=BTS,dc=ad,dc=bts,dc=com' +USERS_DN = 'ou=Users,ou=BTS,dc=ad,dc=bts,dc=com' + + +DBFILE = "/db/data.db" + +WebServerPORT = 5000 diff --git a/Server/Program/ldapSync.py b/Server/Program/ldapSync.py new file mode 100644 index 0000000..fcdc4d6 --- /dev/null +++ b/Server/Program/ldapSync.py @@ -0,0 +1,164 @@ +from datetime import datetime +import ldap +import sqlite3 +import threading +import schedule +from env import * + +# Function to initialize LDAP connection +def initialize_ldap_connection(): + """ + ## Settings : + None + ## Behavior + Init the connection to the LDAP server. + Return LDAPobjet instance when connected + if it fail, return None and print error code + """ + try: + connect = ldap.initialize(LDAP_SERVER) + connect.set_option(ldap.OPT_REFERRALS, 0) + connect.simple_bind_s(LDAPUSER, LDAPPASS) + print(f"[{datetime.now()}] LDAP connection successful.") + return connect + except ldap.LDAPError as e: + print(f'[{datetime.now()}] LDAP Error: {e}') + return None + +# Function to retrieve users from LDAP +def retrieve_users_from_ldap(ldap_connection): + """ + ## Settings : + - ldap_connection : LDAPobjet instance + ## Behavior + retrieve the users in the specified OU + Return result when it success + if it fail, return empty list and print error code + """ + try: + result = ldap_connection.search_s(USERS_DN, ldap.SCOPE_SUBTREE, '(objectClass=user)') + return result + except ldap.LDAPError as e: + print(f'[{datetime.now()}] LDAP Error: {e}') + return [] + +# Function to retrieve groups from LDAP +def retrieve_groups_from_ldap(ldap_connection): + """ + ## Settings : + - ldap_connection : LDAPobjet instance + ## Behavior + retrieve the groups in the specified OU + Return result when it success + if it fail, return empty list and print error code + """ + try: + result = ldap_connection.search_s(DOOR_ACCESS_GROUPS_DN, ldap.SCOPE_SUBTREE, '(objectClass=group)') + return result + except ldap.LDAPError as e: + print(f'[{datetime.now()}]LDAP Error: {e}') + return [] + +# Function to add user to the database or update if already exists +def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): + try: + cursor.execute("SELECT * FROM Users WHERE upn=?", (upn,)) + existing_user = cursor.fetchone() + if existing_user: + # User already exists, check if data needs to be updated + if existing_user[1] != rfid_uid or existing_user[2] != member_of: + cursor.execute("UPDATE Users SET rFIDUID=?, MemberOf=? WHERE upn=?", (rfid_uid, member_of, upn)) + conn.commit() + print(f"[{datetime.now()}] User '{upn}' updated in the database.") + else: + print(f"[{datetime.now()}] User '{upn}' already exists in the database with the same data.") + else: + # User doesn't exist, insert new user + cursor.execute("INSERT INTO Users (upn, rFIDUID, MemberOf) VALUES (?, ?, ?)", (upn, rfid_uid, member_of)) + conn.commit() + print(f"[{datetime.now()}] User '{upn}' added to the database.") + except sqlite3.Error as e: + print(f'SQLite Error: {e}') + +# Function to add group to the database or update if already exists +def add_group_to_database(conn, cursor, cn): + try: + cursor.execute("SELECT * FROM Groups WHERE cn=?", (cn,)) + existing_group = cursor.fetchone() + if existing_group: + # Group already exists, no need to update + print(f"[{datetime.now()}] Group '{cn}' already exists in the database.") + else: + # Group doesn't exist, insert new group + cursor.execute("INSERT INTO Groups (cn) VALUES (?)", (cn,)) + conn.commit() + print(f"[{datetime.now()}] Group '{cn}' added to the database.") + except sqlite3.Error as e: + print(f'SQLite Error: {e}') + +# Function to sync LDAP users and groups to the database +def sync_ldap_to_database(db_file): + """ + Syncs LDAP users and groups to the SQLite database. + + Args: + db_file (str): The path to the SQLite database file. + + Returns: + None + + This function connects to the LDAP server, retrieves user and group information, + and synchronizes it with the SQLite database. It checks if users are disabled in + LDAP and removes them from the database if necessary. It also ensures that users + and groups are added or updated in the database according to the LDAP information. + + Note: + The LDAP connection must be properly configured and the LDAP server accessible + from the machine running this script. + """ + ldap_conn = initialize_ldap_connection() + if ldap_conn: + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + # Retrieve users from LDAP and add them to the database + users = retrieve_users_from_ldap(ldap_conn) + for dn, user_info in users: + upn = user_info.get('userPrincipalName', [''])[0] + rfid_uid = user_info.get('rFIDUID', [''])[0] + member_of = [group.decode('utf-8').split(',')[0].split('=')[1] for group in user_info.get('memberOf', [])] + + # Check if the user is disabled in LDAP + user_account_control = user_info.get('userAccountControl', [0])[0] + if user_account_control == b'514' or user_account_control == b'66050': # Check if the 9th bit is set (ADS_UF_ACCOUNTDISABLE flag) + # User is disabled, check if user exists in the database and remove if present + cursor.execute("SELECT * FROM Users WHERE upn=?", (upn,)) + existing_user = cursor.fetchone() + if existing_user: + cursor.execute("DELETE FROM Users WHERE upn=?", (upn,)) + conn.commit() + print(f"[{datetime.now()}] User '{upn}' disabled in LDAP and removed from the database.") + else: + print(f"[{datetime.now()}] User '{upn}' disabled in LDAP but not present in the database.") + continue # Skip adding the disabled user to the database + + # User is not disabled, add or update user in the database + add_user_to_database(conn, cursor, upn, rfid_uid, ', '.join(member_of)) + + # Retrieve groups from LDAP and add them to the database + groups = retrieve_groups_from_ldap(ldap_conn) + for dn, group_info in groups: + cn = group_info.get('cn', [''])[0].decode('utf-8') + add_group_to_database(conn, cursor, cn) + + # Close connections + conn.close() + ldap_conn.unbind() + +def run_sync_ldap_to_database_thread(db_file): + print(f"[{datetime.now()}] Running LDAP sync") + threading.Thread(target=sync_ldap_to_database, args=(db_file,), daemon=True).start() + +def schedule_sync_ldap_to_database(db_file): + run_sync_ldap_to_database_thread(db_file) # Run immediately + schedule.every(5).minutes.do(run_sync_ldap_to_database_thread, db_file) \ No newline at end of file diff --git a/Server/Program/requirements.txt b/Server/Program/requirements.txt new file mode 100644 index 0000000..8c123cb --- /dev/null +++ b/Server/Program/requirements.txt @@ -0,0 +1,4 @@ +Flask==2.0.2 +Werkzeug==2.0.3 +python-ldap==3.3.1 +schedule==1.2.1 \ No newline at end of file diff --git a/Server/Program/server.py b/Server/Program/server.py new file mode 100644 index 0000000..4deef08 --- /dev/null +++ b/Server/Program/server.py @@ -0,0 +1,15 @@ +from ldapSync import * +from database import * +from Webserver import run_webServer_thread +from env import * +import schedule + + + +setup_database(DBFILE) +print_database_content(DBFILE) +run_webServer_thread() +schedule_sync_ldap_to_database(DBFILE) + +while True : + schedule.run_pending() \ No newline at end of file diff --git a/Server/Program/static/style.css b/Server/Program/static/style.css new file mode 100644 index 0000000..4c88618 --- /dev/null +++ b/Server/Program/static/style.css @@ -0,0 +1,102 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; +} + +.container { + width: 80%; + margin: 0 auto; + padding: 20px; + background-color: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +h1 { + text-align: center; + color: #333; +} + +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; +} + +table, th, td { + border: 1px solid #ddd; +} + +th, td { + padding: 12px; + text-align: left; +} + +th { + background-color: #f2f2f2; +} + +tr:nth-child(even) { + background-color: #f9f9f9; +} + +tr:hover { + background-color: #f1f1f1; +} + +form { + width: 100%; + max-width: 600px; + margin: 0 auto 20px; + padding: 20px; + background-color: #fff; + border: 1px solid #ddd; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +form label { + display: block; + margin-bottom: 8px; + color: #333; +} + +form input[type="number"], +form input[type="text"], +form select { + width: 100%; + padding: 10px; + margin-bottom: 12px; + border: 1px solid #ddd; + border-radius: 4px; +} + +form input[type="submit"] { + width: 100%; + padding: 10px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +form input[type="submit"]:hover { + background-color: #45a049; +} +.navbar { + background-color: #45a049; + overflow: hidden; +} +.navbar a { + float: left; + display: block; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +.navbar a:hover { + background-color: #ddd; + color: black; +} \ No newline at end of file diff --git a/Server/Program/templates/LDAP.html b/Server/Program/templates/LDAP.html new file mode 100644 index 0000000..e1bf9e4 --- /dev/null +++ b/Server/Program/templates/LDAP.html @@ -0,0 +1,16 @@ + + + + + + LDAP Success + + + + + \ No newline at end of file diff --git a/Server/Program/templates/index.html b/Server/Program/templates/index.html new file mode 100644 index 0000000..c8a65e0 --- /dev/null +++ b/Server/Program/templates/index.html @@ -0,0 +1,65 @@ + + + + + + Access Logs + + + + +
+

Latest Access Logs

+ + + + + + + + + + + + {% for log in logs %} + + + + + + + + {% endfor %} + +
TimestampUserTag UIDDoor IDAccess Granted
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[4] }}{{ 'Yes' if log[3] == 1 else 'No' }}
+ +

Add Door

+
+ +

+ +

+ +
+ +

Force LDAP Synchronization

+
+ +
+
+ + + diff --git a/Server/Program/templates/logsdb.html b/Server/Program/templates/logsdb.html new file mode 100644 index 0000000..2e40eeb --- /dev/null +++ b/Server/Program/templates/logsdb.html @@ -0,0 +1,83 @@ + + + + + + Access Logs + + + + + + +
+

Access Logs

+
+ + + + +
+ + + + + + + + + + + + + + {% for log in logs %} + + + + + + + + {% endfor %} + +
TimestampUserRFID UIDDoor IDAccess Granted
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[4] }}{{ 'Yes' if log[3] else 'No' }}
+
+ + \ No newline at end of file diff --git a/Server/Program/templates/userdb.html b/Server/Program/templates/userdb.html new file mode 100644 index 0000000..4a65654 --- /dev/null +++ b/Server/Program/templates/userdb.html @@ -0,0 +1,39 @@ + + + + + + Users + + + + + +
+

Users Database

+ + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + +
UPNRFID UIDMember Of
{{ user[0] }}{{ user[1] }}{{ user[2] }}
+
+ + + diff --git a/Server/Program/templates/webgui.html b/Server/Program/templates/webgui.html new file mode 100644 index 0000000..37011c9 --- /dev/null +++ b/Server/Program/templates/webgui.html @@ -0,0 +1,27 @@ + + + + + + Add Door + + +

Add Door

+
+ +

+ +

+ +
+
+

Force LDAP Synchronization

+
+ +
+ + diff --git a/Server/docker-compose.yml b/Server/docker-compose.yml new file mode 100644 index 0000000..200ad9a --- /dev/null +++ b/Server/docker-compose.yml @@ -0,0 +1,17 @@ +services: + rf-ad: + build: ./ + ports: + - "5000:5000" + environment: + - LDAPUSER + - LDAPPASS + - LDAP_SERVER + - DOOR_ACCESS_GROUPS_DN + - USERS_DN + - DBFILE + - WebServerPORT + volumes: + - /opt/rf-ad/app:/app + - /opt/rf-ad/db:/db +