add all files

This commit is contained in:
jeanGaston 2024-05-22 12:32:30 +02:00
parent 3b7e9acd32
commit 646033244f
23 changed files with 1747 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Server/.env

158
Client/main.py Normal file
View File

@ -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()

380
Client/mfrc522.py Normal file
View File

@ -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

164
Client/ssd1306.py Normal file
View File

@ -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)

View File

@ -1,2 +1,3 @@
# RF-AD
Simple RFID access control system linked to AD

34
Server/Dockerfile Normal file
View File

@ -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"]

View File

@ -0,0 +1,12 @@
__pycache__
*.pyc
*.pyo
*.pyd
env
venv
.idea
.vscode
.git
.gitignore
.DS_Store
*.db

65
Server/Program/WebAPI.py Normal file
View File

@ -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)

103
Server/Program/Webserver.py Normal file
View File

@ -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)

BIN
Server/Program/data.db Normal file

Binary file not shown.

269
Server/Program/database.py Normal file
View File

@ -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

View File

@ -0,0 +1,17 @@
#!/bin/sh
ls /app/Program
echo patate
# Create env.py with environment variables
cat <<EOT > /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

10
Server/Program/env.py Normal file
View File

@ -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

164
Server/Program/ldapSync.py Normal file
View File

@ -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)

View File

@ -0,0 +1,4 @@
Flask==2.0.2
Werkzeug==2.0.3
python-ldap==3.3.1
schedule==1.2.1

15
Server/Program/server.py Normal file
View File

@ -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()

View File

@ -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;
}

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LDAP Success</title>
</head>
<body>
<script>
// Display popup message
alert("LDAP sync ended succesfully");
window.location.href = "/";
</script>
</body>
</html>

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Access Logs</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="navbar">
<a href="/">Home</a>
<a href="/UserDB">Users</a>
<a href="/LogsDB">Logs</a>
</div>
<div class="container">
<h1>Latest Access Logs</h1>
<table>
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Tag UID</th>
<th>Door ID</th>
<th>Access Granted</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log[0] }}</td>
<td>{{ log[1] }}</td>
<td>{{ log[2] }}</td>
<td>{{ log[4] }}</td>
<td>{{ 'Yes' if log[3] == 1 else 'No' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1>Add Door</h1>
<form action="/add_door" method="post">
<label for="Door_id">Door ID:</label>
<input type="number" id="Door_id" name="Door_id" required><br><br>
<label for="group_cn">Group CN:</label>
<select id="group_cn" name="group_cn" required>
{% for group in existing_groups %}
<option value="{{ group }}">{{ group }}</option>
{% endfor %}
</select><br><br>
<input type="submit" value="Submit">
</form>
<h1>Force LDAP Synchronization</h1>
<form action="/sync">
<input type="submit" value="Sync LDAP">
</form>
</div>
<script>
// Refresh the page every 5 seconds
setTimeout(function(){
location.reload();
}, 5000);
</script>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Access Logs</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<script>
function filterTable() {
var timestampInput = document.getElementById("timestampFilter").value.toLowerCase();
var userInput = document.getElementById("userFilter").value.toLowerCase();
var rfidUidInput = document.getElementById("rfidUidFilter").value.toLowerCase();
var doorIdInput = document.getElementById("doorIdFilter").value.toLowerCase();
var table = document.getElementById("logsTable");
var tr = table.getElementsByTagName("tr");
for (var i = 1; i < tr.length; i++) {
var tdTimestamp = tr[i].getElementsByTagName("td")[0];
var tdUser = tr[i].getElementsByTagName("td")[1];
var tdRfidUid = tr[i].getElementsByTagName("td")[2];
var tdDoorId = tr[i].getElementsByTagName("td")[3];
if (tdTimestamp && tdUser && tdRfidUid && tdDoorId) {
var timestampValue = tdTimestamp.textContent || tdTimestamp.innerText;
var userValue = tdUser.textContent || tdUser.innerText;
var rfidUidValue = tdRfidUid.textContent || tdRfidUid.innerText;
var doorIdValue = tdDoorId.textContent || tdDoorId.innerText;
if (timestampValue.toLowerCase().indexOf(timestampInput) > -1 &&
userValue.toLowerCase().indexOf(userInput) > -1 &&
rfidUidValue.toLowerCase().indexOf(rfidUidInput) > -1 &&
doorIdValue.toLowerCase().indexOf(doorIdInput) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
</head>
<body>
<div class="navbar">
<a href="/">Home</a>
<a href="/UserDB">Users</a>
<a href="/LogsDB">Logs</a>
</div>
<div class="container">
<h1>Access Logs</h1>
<div class="filter-container">
<input type="text" id="timestampFilter" onkeyup="filterTable()" placeholder="Filter by timestamp">
<input type="text" id="userFilter" onkeyup="filterTable()" placeholder="Filter by user">
<input type="text" id="rfidUidFilter" onkeyup="filterTable()" placeholder="Filter by RFID UID">
<input type="text" id="doorIdFilter" onkeyup="filterTable()" placeholder="Filter by door ID">
</div>
<button onclick="window.location.href='/export_logs'">Export Logs as csv</button>
<table id="logsTable">
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>RFID UID</th>
<th>Door ID</th>
<th>Access Granted</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log[0] }}</td>
<td>{{ log[1] }}</td>
<td>{{ log[2] }}</td>
<td>{{ log[4] }}</td>
<td>{{ 'Yes' if log[3] else 'No' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Users </title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="navbar">
<a href="/">Home</a>
<a href="/UserDB">Users</a>
<a href="/LogsDB">Logs</a>
</div>
<div class="container">
<h1>Users Database</h1>
<table>
<thead>
<tr>
<th>UPN</th>
<th>RFID UID</th>
<th>Member Of</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user[0] }}</td>
<td>{{ user[1] }}</td>
<td>{{ user[2] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Door</title>
</head>
<body>
<h1>Add Door</h1>
<form action="/add_door" method="post">
<label for="Door_id" name="Door_id">Door ID:</label>
<input type="integer" id="Door_id" name="Door_id" required><br><br>
<label for="group_cn">Group CN:</label>
<select id="group_cn" name="group_cn" required>
{% for group in existing_groups %}
<option value="{{ group }}">{{ group }}</option>
{% endfor %}
</select><br><br>
<input type="submit" value="Submit">
</form>
<br>
<h1>Force LDAP Synchronization</h1>
<form action="/sync">
<input type="submit" value="Sync LDAP">
</form>
</body>
</html>

17
Server/docker-compose.yml Normal file
View File

@ -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