Compare commits

..

19 Commits

Author SHA1 Message Date
059d45b117
patch to require admin APIKey on admin endpoints
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-16 02:19:41 +03:00
39b7d25a57 WIP: enroll device
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-16 02:18:59 +03:00
fc9f1c5c05 file headers
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-16 02:18:43 +03:00
7f90e104d3 sql_app: emergency open time
For some reason, having 3600 set as the
	time for door to stay open in a case of
	fire does not open the door at all.
	Prob a limtiation in door implementation

Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 21:53:37 +03:00
2c3206fc06
verbose at emerg
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 19:56:00 +03:00
b704aee0fc
init_secrets: change file mode when making .env file
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 19:34:31 +03:00
99ac2acc0d sql_app: open door on emergency
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 19:30:14 +03:00
6a771c589b sql_app: init_db: do not recreate sensor_data and access_log
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 12:57:38 +03:00
2c60e14260 sql_app: enforce strict file permissions for .env
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-12 12:52:04 +03:00
21aef6ec6c sql_app: update monitor column
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-11 20:28:39 +03:00
20dfa6dcc4 sql_app: Respond to emergency events record db entry
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-11 18:05:52 +03:00
e1a7c4023b
sql_app: tools: Iot door emulator for testing in absence of door
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-11 17:39:18 +03:00
50a7d8251c
print monitor requests
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-10 21:45:44 +03:00
6ae5b811f4
sql_app: correct monitor mac
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-10 19:11:12 +03:00
21d72f17b2 sql_app: fixed error while setting door state
Old code was taking state from device itself,
	meaning it never changes. Now it is set from
	the incoming request.

Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-09 22:41:07 +03:00
dcd2ff5b89 sql_app: Split doors and monitors
A alot of interface and database boiler plate

Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-09 21:52:42 +03:00
a316374da6 sql_app: remove api key from some functions for testing
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-09 18:21:02 +03:00
a034f17a63 Merge branch 'master' of https://github.com/HeshamTB/ibs
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-08 18:30:09 +03:00
40030c91a1 sql_app: systemd unit file
Signed-off-by: HeshamTB <hishaminv@gmail.com>
2022-06-08 18:27:25 +03:00
22 changed files with 563 additions and 194 deletions

View File

@ -0,0 +1,130 @@
From b08a24bedfb247fd148c48e00ee5d9b544991dfe Mon Sep 17 00:00:00 2001
From: HeshamTB <hishaminv@gmail.com>
Date: Thu, 14 Apr 2022 07:16:28 +0300
Subject: [PATCH] admin: All admin path functions require an APIKey
Signed-off-by: HeshamTB <hishaminv@gmail.com>
---
sql_app/auth_helper.py | 10 +++++++++-
sql_app/main.py | 19 ++++++++++---------
2 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/sql_app/auth_helper.py b/sql_app/auth_helper.py
index a9b866b..12aa271 100644
--- a/sql_app/auth_helper.py
+++ b/sql_app/auth_helper.py
@@ -3,18 +3,22 @@ from typing import Optional
from decouple import config
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
-from fastapi import Depends
+from fastapi import Depends, Security, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from fastapi.security.api_key import APIKey, APIKeyHeader
from . import crud, crypto, schemas
import jwt
import time
JWT_SECRET = config('jwt_secret')
JWT_ALGO = config('jwt_algorithm')
+__API_KEY = config('API_KEY')
+__API_KEY_NAME = config('API_KEY_NAME')
+api_key_header = APIKeyHeader(name=__API_KEY_NAME)
def create_access_token(data : dict, expires_delta : Optional[timedelta] = None):
# TODO: Consider making non-expiring token
@@ -33,3 +37,7 @@ def authenticate_user(db: Session, username : str, password : str):
return False
return crypto.verify_key(password, user.passwd_salt, user.hashed_password)
+def valid_api_key(key = Security(api_key_header)):
+ if not __API_KEY == key:
+ raise HTTPException(401, detail="invalid key")
+ return
diff --git a/sql_app/main.py b/sql_app/main.py
index 413db35..9a9434e 100644
--- a/sql_app/main.py
+++ b/sql_app/main.py
@@ -1,5 +1,6 @@
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from fastapi.security.api_key import APIKey
from sqlalchemy.orm import Session
from . import crud, models, schemas, auth_helper
@@ -65,31 +66,31 @@ def get_user_details(current_user: schemas.User = Depends(get_current_active_use
return current_user
@app.get("/admin/users/", response_model=List[schemas.User], tags=['Admin'])
-def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/admin/iotentities/", response_model=List[schemas.IotEntity], tags=['Admin'])
-def read_iot_entities(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+def read_iot_entities(skip: int = 0, limit: int = 100, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
iot_entities = crud.get_iot_entities(db, skip=skip, limit=limit)
return iot_entities
# TODO: Can duplicate
@app.post("/admin/iotentities/create", response_model=schemas.IotEntity, tags=['Admin'])
-def create_iot_entities(iot_entity: schemas.IotEntityCreate, db: Session = Depends(get_db)):
+def create_iot_entities(iot_entity: schemas.IotEntityCreate, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
iot_entities = crud.create_iot_entity(db, iot_entity)
return iot_entities
@app.get("/admin/users/{user_id}", response_model=schemas.User, tags=['Admin'])
-def read_user(user_id: int, db: Session = Depends(get_db)):
+def read_user(user_id: int, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
# TODO: Can duplicate
@app.post("/admin/users/allowdevice/id", tags=['Admin'])
-def allow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityRequestByID, db: Session = Depends(get_db)):
+def allow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityRequestByID, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
user = crud.get_user(db, request.user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
@@ -105,7 +106,7 @@ def allow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityReques
return user
@app.post("/admin/users/disallowdevice/id", tags=['Admin'])
-def disallow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityRequestByID, db: Session = Depends(get_db)):
+def disallow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityRequestByID, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
user = crud.get_user(db, request.user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
@@ -122,7 +123,7 @@ def disallow_user_for_iot_entity_by_id(request: schemas.UserAllowForIotEntityReq
return
@app.post("/admin/users/allowdevice/name", tags=['Admin'])
-def allow_user_for_iot_entity_by_name(request: schemas.UserAllowForIotEntityRequestByUsername, db: Session = Depends(get_db)):
+def allow_user_for_iot_entity_by_name(request: schemas.UserAllowForIotEntityRequestByUsername, db: Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
user = crud.get_user_by_username(db, request.username)
if not user:
raise HTTPException(status_code=404, detail="User not found")
@@ -138,11 +139,11 @@ def allow_user_for_iot_entity_by_name(request: schemas.UserAllowForIotEntityRequ
return
@app.post("/admin/users/{user_id}/deactiveate", tags=['Admin'])
-def deactiveate_user(user_id: int, db:Session = Depends(get_db)):
+def deactiveate_user(user_id: int, db:Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
return
@app.post("/admin/users/{user_id}/activeate", tags=['Admin'])
-def deactiveate_user(user_id: int, db:Session = Depends(get_db)):
+def deactiveate_user(user_id: int, db:Session = Depends(get_db), api_key: APIKey = Depends(auth_helper.valid_api_key)):
return
@app.get("/users/acesslist/", response_model=List[schemas.IotEntity], tags=['Users'])
--
libgit2 1.4.3

10
run-tls
View File

@ -1,4 +1,14 @@
#!/bin/bash
source venv/bin/activate
cd sql_app/
./file_permissios.py
if [ $? == 1 ]
then
echo "enviorment file_permissions are incorrect"
exit 1
fi
cd ../
exec uvicorn sql_app.main:app --ssl-certfile server.crt --ssl-keyfile server.key --port 4040 --host 0.0.0.0 --no-server-header

View File

@ -15,7 +15,7 @@
- [X] Expose data analysis
- [X] Load backend onto RPi
- [X] Test connections in lab network
- [ ] Define emrgancy triggers (manual and automatic)
- [X] Define emrgancy triggers (manual and automatic)
- [ ] Expose temporary control in case of emergancy
- Triggers
- Acccess
@ -36,6 +36,10 @@
- [ ] Write unit tests
- [ ] Develop a program to visualize the data
- [ ] CLI frontend
- [X] Emergaency
- [X] Send state with accesslist
- [X] Split monitor into different class
- [ ] Make a script that adds types, and thier basic database ops?? (avoid writing boiler-plate)
- [ ] Make a script that emulates a door and monitor
- [ ] Check file premissions on .env file, if global reject
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJibHVldG9vdGhfbWFjIjoic3RyaW5nIn0.ELl5AfBR1NdM4_OFhl_SCTm9EMPpqjiCKOSS0CrOJps

View File

@ -1,3 +1,5 @@
# May 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from typing import Optional
from decouple import config
@ -56,3 +58,13 @@ def valid_iot_token(token : str, db: Session):
mac_signed = payload.get("bluetooth_mac")
device = crud.get_iot_entity_by_bluetooth_mac(db, mac_signed)
return device
def valid_monitor_token(token: str, db: Session):
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGO])
except jwt.DecodeError:
return None
mac_signed = payload.get("bluetooth_mac")
monitor = crud.get_monitor_bluetooth(db, mac_signed)
return monitor

View File

@ -1,3 +1,6 @@
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
# CRUD (Create, Read, Update, Delete) from db
from sqlalchemy import select, join
@ -17,7 +20,7 @@ from warnings import warn
def get_user(db: Session, user_id: int) -> models.User:
return db.query(models.User).get(user_id)
def get_iot_entity(db: Session, id: int):
def get_iot_entity(db: Session, id: int) -> models.IotEntity:
return db.query(models.IotEntity).get(id)
def get_iot_entity_by_description(db: Session, description: str):
@ -44,8 +47,12 @@ def get_access_log_for_door_by_door_mac(db: Session, iot_id: str):
def get_access_log_for_user_by_id(db: Session, id : str):
return db.query(models.DoorAccessLog).filter(models.DoorAccessLog.user_id == id).all()
def get_room_data_now(db: Session) -> models.RoomSensorData:
return db.query(models.RoomSensorData)[-1]
# def get_room_data_now(db: Session, door_id: int) -> models.RoomSensorData:
# door = get_iot_entity(db, door_id)
# monitor : models.Monitors = door.monitor
# if not monitor: return -1
# if len(monitor.sensor_history) == 0: return -2
# return monitor.sensor_history[-1]
def create_user(db: Session, user: schemas.UserCreate):
key = crypto.gen_new_key(user.password)
@ -73,6 +80,8 @@ def update_user_password(db: Session, user: models.User, request: schemas.UserUp
def get_iot_entities(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.IotEntity).offset(skip).limit(limit).all()
def get_monitors(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Monitors).offset(skip).limit(limit).all()
def create_iot_entity(db: Session, iot_entity: schemas.IotEntityCreate):
db_item = models.IotEntity(bluetooth_mac=iot_entity.bluetooth_mac,
@ -82,6 +91,37 @@ def create_iot_entity(db: Session, iot_entity: schemas.IotEntityCreate):
db.refresh(db_item)
return db_item
def create_monitor(db: Session, monitor: schemas.IotEntityBase):
db_item = models.Monitors(bluetooth_mac=monitor.bluetooth_mac,
description=monitor.description)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
def get_monitor(db: Session, id: int) -> models.Monitors:
return db.query(models.Monitors).get(id)
def get_monitor_bluetooth(db: Session, bluetooth_mac: str) -> models.Monitors:
return db.query(models.Monitors).filter(models.Monitors.bluetooth_mac == bluetooth_mac).first()
def update_monitor(db: Session, monitor: models.Monitors):
db.add(monitor)
db.commit()
db.refresh(monitor)
def update_monitor_readings(db: Session, monitor_upadte: schemas.MonitorUpdateReadings, bluetooth_mac: str):
monitor = get_monitor_bluetooth(db, bluetooth_mac)
monitor.humidity = monitor_upadte.humidity
monitor.people = monitor_upadte.people
monitor.smoke_sensor_reading = monitor_upadte.smoke_sensor_reading
monitor.temperature = monitor_upadte.temperature
db.add(monitor)
db.commit()
db.refresh(monitor)
def create_user_link_to_iot(db: Session, user_id: int, iot_dev_id: int):
# Ensure link is not already present and it does not allow duplicates
link = db.query(models.UserAuthToIoTDev).filter(models.UserAuthToIoTDev.user_id == user_id).filter(models.UserAuthToIoTDev.iot_id == iot_dev_id).first()
@ -168,16 +208,27 @@ def record_door_access_log(db: Session, entry: schemas.DoorAccessLog):
db.commit()
db.refresh(db_item)
def record_room_sensor_data(db: Session, entry: schemas.IotMonitorRoomInfo):
def record_room_sensor_data(db: Session, entry: schemas.MonitorUpdateReadings,
monitor :models.Monitors):
db_item = models.RoomSensorData(humidity=entry.humidity,
people=entry.people,
temperature=entry.temperature,
smoke_sensor_reading=entry.smoke_sensor_reading,
timestamp=datetime.now())
timestamp=datetime.now(),
monitor_id=monitor.id)
db.add(db_item)
db.commit()
db.refresh(db_item)
monitor.humidity = entry.humidity
monitor.temperature = entry.temperature
monitor.people = entry.people
monitor.smoke_sensor_reading = entry.smoke_sensor_reading
db.add(monitor)
db.commit()
db.refresh(monitor)
def increment_door_access_list_counter(db: Session, iot_entity: models.IotEntity):
iot_entity.acces_list_counter = iot_entity.acces_list_counter + 1
db.add(iot_entity)
@ -190,12 +241,25 @@ def record_user_connection(db: Session, user: models.User, time: datetime):
db.commit()
db.refresh(entry)
def get_sensor_data_for_room(db: Session, skip: int = 0, limit: int = 100):
data = db.query(models.RoomSensorData).offset(skip).limit(limit).all()
return data
# def get_sensor_data_for_room(db: Session, monitor_id: int, count_last: int):
# data = db.query(models.RoomSensorData).all()
# if not data or len(data) == 0: return -1
# return data[-count_last]
def update_user_status(db: Session, user: models.User, state: bool):
user.is_active = state
db.add(user)
db.commit()
db.refresh(user)
def record_emergancy_entry(db: Session, monitor_data: schemas.MonitorUpdateReadings, monitor_id: int):
new_entry : models.EmergancyNotice = models.EmergancyNotice(
monitor_id=monitor_id,
people=monitor_data.people,
temperature=monitor_data.temperature,
smoke_sensor_reading=monitor_data.smoke_sensor_reading,
timestamp=datetime.now()
)
db.add(new_entry)
db.commit()
db.refresh(new_entry)

View File

@ -1,3 +1,5 @@
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
import os
from hashlib import pbkdf2_hmac

View File

@ -1,3 +1,6 @@
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

45
sql_app/enroll_door.py Normal file
View File

@ -0,0 +1,45 @@
# Quick enroll new device
# Hesham T. Banafa
# Jun 12th, 2022
from decouple import config
import requests
# idk if this stays in memory...
headers = {
"accept": "application/json",
"Content-type": "application/json"
}
def main():
if len(sys.argv) != 4:
print_help()
exit(1)
device_type = sys.argv[1]
bluetooth_mac = sys.argv[2]
description = sys.argv[3]
if device_type == 'DOOR':
mkdoor(bluetooth_mac, description)
elif device_type == 'MONITOR':
mkmonitor(bluetooth_mac, description)
else:
print('Device type not DOOR or MONITOR', file=sys.stderr)
exit(1)
# gen print token of bluetooth_mac
print(create_iot_dev_token(bluetooth_mac))
def mkdoor(bluetooth_mac: str, description: str):
data = {
"bluetooth_mac": bluetooth_mac,
"description": description
}
#response = requests.post("")
def mkmonitor(bluetooth_mac: str, description: str):
pass
def print_help():
msg = 'usgae: enroll_iotdevice <DOOR|MONITOR> <bluetooth_mac> <description>'
print(msg)

18
sql_app/file_permissios.py Executable file
View File

@ -0,0 +1,18 @@
#!/bin/python
# Hesham T. Banafa
# Jun 12th, 2022
# Check enviorment file permissions and return -1 if fails or 0
import os
import stat
ENV_FILE='.env'
st = os.stat(ENV_FILE)
if st.st_mode & stat.S_IROTH or \
st.st_mode & stat.S_IWOTH or \
st.st_mode & stat.S_IXOTH:
exit(1)
exit(0)

View File

@ -1,3 +1,5 @@
# June 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from . import crud, main, schemas, auth_helper
from decouple import config
@ -55,17 +57,18 @@ def init_door():
crud.create_iot_entity(db, iot_door)
def init_monitor():
iot_monitor = schemas.IotEntityCreate(bluetooth_mac="ff:ff:00:ff",
iot_monitor = schemas.IotEntityCreate(bluetooth_mac="ff:ff:ff",
description="Iot Lab Monitor")
monitor_exists = crud.get_iot_entity_by_bluetooth_mac(db, iot_monitor.bluetooth_mac)
monitor_exists = crud.get_monitor_bluetooth(db, iot_monitor.bluetooth_mac)
if monitor_exists: return
crud.create_iot_entity(db, iot_monitor)
crud.create_monitor(db, iot_monitor)
def init_allowance():
crud.create_user_link_to_iot(db, 1, 1)
def init_sensor_data():
monitor = crud.get_monitor(db, 1)
if monitor.sensor_history: return
for i in range(50):
room_data = \
schemas.\
@ -75,10 +78,11 @@ def init_sensor_data():
temperature=randint(18, 27),
smoke_sensor_reading=randint(150, 700),
token='dummy')
crud.record_room_sensor_data(db, room_data)
crud.record_room_sensor_data(db, room_data, monitor)
def init_open_close_requests():
user = crud.get_user_by_email(db, "hisham@banafa.com.sa")
if user.access_log: return
crud.set_open_door_request(db, 1, 10)
log_entry = schemas.DoorAccessLog(user_id=user.id,
iot_id=1,
@ -115,6 +119,11 @@ def init_user_connections():
crud.record_user_connection(db, users[i], datetime.now())
crud.record_user_connection(db, users[i], datetime.now())
def init_link_room_monitor():
monitor = crud.get_monitor(db, 1)
door = crud.get_iot_entity(db, 1)
monitor.door = door
crud.update_monitor(db, monitor)
def init():
init_user()
@ -124,4 +133,5 @@ def init():
init_sensor_data()
init_open_close_requests()
init_user_connections()
init_link_room_monitor()

View File

@ -16,3 +16,5 @@ then
exit 255
fi
echo "first_user_pass=$firstpass" >> .env
chmod 600 .env

View File

@ -1,14 +1,15 @@
from fastapi import Depends, FastAPI, HTTPException, status, Request
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm, OAuth2AuthorizationCodeBearer
from fastapi.security.api_key import APIKey
from fastapi.responses import PlainTextResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from sqlalchemy.orm import Session
from . import crud, models, schemas, auth_helper, init_db
from .database import SessionLocal, engine
from .utils import get_db
from .utils import get_db, EMERG_SMOKE, EMERG_TEMP, EMERG_OPEN_TIME_SEC
from typing import List
from datetime import timedelta, datetime
@ -18,16 +19,11 @@ models.Base.metadata.create_all(bind=engine)
oauth = OAuth2PasswordBearer(tokenUrl="tkn")
app = FastAPI(title="IoT Building System")
app.mount("/sql_app/static", StaticFiles(directory="sql_app/static"), name="static")
templates = Jinja2Templates(directory="sql_app/templates")
# Split into endpoints modules
#app.include_router(users.router,prefix="/users", tags=["User"])
init_db.init()
@app.get("/")
def home(request: Request):
return templates.TemplateResponse("home.html", context={"request": request})
def get_current_user(token: str = Depends(oauth), db: Session = Depends(get_db)):
credentials_exception = HTTPException(
@ -142,22 +138,23 @@ def get_iot_access_list_for_user(db: Session = Depends(get_db), current_user: sc
user = crud.get_user_by_username(db, current_user.username)
access_list = list()
for device in user.authorized_devices:
dev_db : models.IotEntity = device
sensors = crud.get_room_data_now(db)
if not sensors: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
door : models.IotEntity = device
monitor : models.Monitors = door.monitor
if not monitor: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="No Room link")
entry : schemas.RoomOverview = schemas.RoomOverview(
id=dev_db.id,
description=dev_db.description,
bluetooth_mac=dev_db.bluetooth_mac,
open_request=dev_db.open_request,
time_seconds=dev_db.time_seconds,
acces_list_counter=dev_db.acces_list_counter,
humidity=sensors.humidity,
people=sensors.people,
temperature=sensors.temperature,
smoke_sensor_reading=sensors.smoke_sensor_reading,
force_close=dev_db.force_close
id=door.id,
description=door.description,
bluetooth_mac=door.bluetooth_mac,
open_request=door.open_request,
time_seconds=door.time_seconds,
acces_list_counter=door.acces_list_counter,
humidity=monitor.humidity,
people=monitor.people,
temperature=monitor.temperature,
smoke_sensor_reading=monitor.smoke_sensor_reading,
force_close=door.force_close,
state=door.state
)
access_list.append(entry)
#crud.record_user_connection(db, user, datetime.now())
@ -198,12 +195,23 @@ def read_iot_entities(skip: int = 0, limit: int = 100, db: Session = Depends(get
iot_entities = crud.get_iot_entities(db, skip=skip, limit=limit)
return iot_entities
@app.get("/admin/monitors/", response_model=List[schemas.Monitor], tags=['Admin'])
def read_iot_monitors(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
monitors = crud.get_monitors(db, skip=skip, limit=limit)
return monitors
# TODO: Can duplicate
@app.post("/admin/iotentities/create", response_model=schemas.IotEntity, tags=['Admin'])
def create_iot_entities(iot_entity: schemas.IotEntityCreate, db: Session = Depends(get_db)):
iot_entities = crud.create_iot_entity(db, iot_entity)
return iot_entities
@app.post("/admin/monitor/create", response_model=schemas.Monitor, tags=['Admin'])
def create_monitor(iot_entity: schemas.IotEntityBase,
db: Session = Depends(get_db)):
monitor = crud.create_monitor(db, iot_entity)
return monitor
@app.get("/admin/users/{user_id}", response_model=schemas.User, tags=['Admin'])
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
@ -282,13 +290,22 @@ def deactiveate_user(user_id: int, db:Session = Depends(get_db)):
crud.update_user_status(db, user, True)
@app.post("/admin/iotdevice/gentoken/", response_model=schemas.Token, tags=['Admin'])
def generate_token_for_iot_device(bluetooth_mac : schemas.IotBluetoothMac,
api_key: APIKey = Depends(auth_helper.valid_api_key)):
def generate_token_for_iot_device(bluetooth_mac : schemas.IotBluetoothMac):
# api_key: APIKey = Depends(auth_helper.valid_api_key)
# We get here after a valid admin key, so send back permenant token
data = {"bluetooth_mac": bluetooth_mac.bluetooth_mac}
tkn = auth_helper.create_iot_dev_token(data)
return {"access_token": tkn, "token_type": "bearer"}
@app.patch("/admin/link/monitor/{monitor_id}/door/{door_id}", tags=['Admin'])
def link_monitor_with_door(monitor_id: int, door_id: int,
db: Session = Depends(get_db)):
monitor = crud.get_monitor(db, monitor_id)
door = crud.get_iot_entity(db, door_id)
monitor.door = door
crud.update_monitor(db, monitor)
return monitor
@app.post("/admin/user/accesslog/email/", tags=['Admin'])
def get_access_log_history_for_user(request : schemas.UserAccessLogRequestEmail,
db : Session = Depends(get_db)):
@ -303,14 +320,49 @@ def get_access_log_history_for_user(request : schemas.UserAccessLogRequestUserna
if not user: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
return user.access_log
@app.get("/admin/roominfo/now/", tags=['Admin'])
def get_room_data(db: Session = Depends(get_db)):
return crud.get_room_data_now(db)
@app.get("/admin/roominfo/{door_id}/now", tags=['Admin'])
def get_room_data(door_id: int, db: Session = Depends(get_db)):
door = crud.get_iot_entity(db, door_id)
if not door:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Door not found")
monitor : models.Monitors = door.monitor
if not monitor:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="No Room link")
data = monitor.sensor_history
if not data or len(data) == 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="No Sensor data")
return data[-1]
@app.get("/admin/roominfo/history/sensors", tags=['Admin'])
def get_all_sensor_history(skip: int = 0, limit: int = 100,
@app.get("/admin/roominfo/{monitor_id}/now", tags=['Admin'])
def get_room_data(monitor_id: int, db: Session = Depends(get_db)):
monitor = crud.get_monitor(db, monitor_id)
if not monitor: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Monitor not found")
if not monitor.door_id:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Monitor not linked")
data = crud.get_room_data_now(db, monitor.door_id)
if data == -1: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="No Room link")
if data == -2: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="No Sensor data")
return data
@app.get("/admin/roominfo/{monitor_id}/last/{count}", tags=['Admin'])
def get_all_sensor_history(monitor_id: int, count: int,
db: Session = Depends(get_db)):
return crud.get_sensor_data_for_room(db, skip, limit)
monitor = crud.get_monitor(db, monitor_id)
if not monitor: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Monitor not found")
data = monitor.sensor_history
if not data or len(data) == 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="No Sensor data")
return data[-count:]
@app.post("/admin/roominfo/accesslog",response_model=List[schemas.DoorAccessLog], tags=['Admin'])
def get_access_log_for_door(request : schemas.AccessLogRequest,
@ -333,23 +385,33 @@ def polling_method_for_iot_entity(request: schemas.IotDoorPollingRequest,
open_command=device.open_request,
acces_list_counter=device.acces_list_counter,
time_seconds=device.time_seconds,
force_close=device.force_close)
force_close=device.force_close,
state=device.state)
# Reset open_request to False
crud.clear_open_door_request(db, device.id)
crud.clear_close_door_request(db, device.id)
crud.set_door_state(db, device, device.state)
crud.set_door_state(db, device, bool(request.state))
return response
@app.post("/iotdevice/monitor/status", tags=['Iot'])
def polling_method_for_room_monitor(request: schemas.IotMonitorRoomInfo,
def polling_method_for_room_monitor(request: schemas.MonitorUpdateReadings,
db: Session = Depends(get_db)):
device : schemas.IotEntity = auth_helper.valid_iot_token(request.token, db)
device : models.Monitors = auth_helper.valid_monitor_token(request.token, db)
if not device:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials")
crud.record_room_sensor_data(db, request)
crud.record_room_sensor_data(db, request, device)
if request.temperature >= EMERG_TEMP or request.smoke_sensor_reading >= EMERG_SMOKE:
print("********EMERGENCY AT %s********" % device.description)
door : models.IotEntity = device.door
print("********OPENING DOOR %s ID:%d********" % (door.description, door.id))
crud.set_open_door_request(db, door.id, EMERG_OPEN_TIME_SEC)
crud.record_emergancy_entry(db, request, device.id)
# Call into a hook to notify with room and people
print(request)
return request
@app.post("/iotdevice/door/users", response_class=PlainTextResponse, tags=['Iot'])
@ -383,3 +445,9 @@ def get_allowed_usernames(request: schemas.IotDoorPollingRequest,
tkns = tkns + db_user.last_token + '\n'
return tkns
@app.get("/test")
def get(db: Session = Depends(get_db)):
mon = crud.get_monitor(db, "ff:ff:ff:ff")
return mon.door

View File

@ -1,3 +1,6 @@
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime
from sqlalchemy.orm import relationship
@ -23,7 +26,7 @@ class IotEntity(Base):
__tablename__ = "iot_entities"
id = Column(Integer, primary_key=True, index=True)
bluetooth_mac = Column(String(512))
bluetooth_mac = Column(String(512), index=True, unique=True)
description = Column(String(512))
open_request = Column(Boolean, default=False)
time_seconds = Column(Integer, default=10)
@ -31,7 +34,22 @@ class IotEntity(Base):
force_close = Column(Boolean, default=False)
state = Column(Boolean, default=False) # True is open, False is closed
authorized_users = relationship("User", secondary="user_iot_link", back_populates="authorized_devices")
access_log = relationship("DoorAccessLog", back_populates="iot_device")
access_log = relationship("DoorAccessLog", back_populates="iot_device") # one-to-many
monitor = relationship("Monitors", back_populates="door", uselist=False) # one-to-one
class Monitors(Base):
__tablename__ = "monitors"
id = Column(Integer, primary_key=True)
bluetooth_mac = Column(String(512), index=True, unique=True)
description = Column(String(512))
humidity = Column(Integer, default=0)
people = Column(Integer, default=0)
temperature = Column(Integer, default=0)
smoke_sensor_reading = Column(Integer, default=0)
door_id = Column(Integer, ForeignKey("iot_entities.id"))
door = relationship("IotEntity", back_populates="monitor")
sensor_history = relationship("RoomSensorData", back_populates="monitor")
class UserAuthToIoTDev(Base):
__tablename__ = "user_iot_link"
@ -54,13 +72,14 @@ class DoorAccessLog(Base):
class RoomSensorData(Base):
__tablename__ = "room_sensor_data"
# Data is now not related to a room. We should have a construct for rooms
reading_id = Column(Integer, primary_key=True, index=True)
humidity = Column(Integer)
people = Column(Integer)
temperature = Column(Integer)
smoke_sensor_reading = Column(Integer)
timestamp = Column(DateTime)
monitor_id = Column(Integer, ForeignKey("monitors.id"), index=True)
monitor = relationship("Monitors", back_populates="sensor_history")
class UserConnectionHistory(Base):
__tablename__ = "user_connection_history"
@ -70,4 +89,14 @@ class UserConnectionHistory(Base):
timestamp = Column(DateTime)
# TODO: add ip
class EmergancyNotice(Base):
__tablename__ = "emergency_notice"
id = Column(Integer, primary_key=True)
monitor_id = Column(Integer, ForeignKey("monitors.id"), index=True)
people = Column(Integer)
temperature = Column(Integer)
smoke_sensor_reading = Column(Integer)
timestamp = Column(DateTime)
# TODO: Add table to store active sessions. May periodically clear.

View File

@ -1,3 +1,6 @@
# March 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from typing import Any, List, Optional
from pydantic import BaseModel
@ -28,12 +31,33 @@ class IotEntity(IotEntityBase):
time_seconds: int
force_close: bool
acces_list_counter: int
state: bool
class Config:
orm_mode = True
class IotBluetoothMac(BaseModel):
bluetooth_mac : str
class Monitor(IotEntityBase):
# bluetooth_mac: str
# description: str
id: int
bluetooth_mac: str
description: str
humidity: int
people: int
temperature: int
smoke_sensor_reading: int
class Config:
orm_mode = True
class MonitorUpdateReadings(BaseModel):
humidity : int
people : int
temperature : int
smoke_sensor_reading : int
token: str # Contains mac
class User(UserBase):
id: int
is_active: bool
@ -83,6 +107,7 @@ class IotDoorPollingResponse(BaseModel):
acces_list_counter : int
time_seconds : int
force_close: bool
state: bool
class IotMonitorRoomInfo(BaseModel):
humidity : int
@ -90,8 +115,8 @@ class IotMonitorRoomInfo(BaseModel):
temperature : int
smoke_sensor_reading : int
token: str
class Config:
orm_mode = True
# class Config:
# orm_mode = True
class IotMonitorRoomInfoTimestamped(IotMonitorRoomInfo):
time: datetime

View File

@ -1,20 +0,0 @@
/* custom css */
html {
position: relative;
min-height: 100%;
}
body {
padding-top: 2rem;
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
}

View File

@ -1,36 +0,0 @@
(function () {
console.log("Sanity Check!");
})()
var token = null;
var logged_in = false;
function handleLogInClick() {
var username = document.getElementById("username_box").value;
var password = document.getElementById("password_box").value;
console.log("Username ", username);
fetch('/users/tkn', {
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded',
'accept': 'application/json'
},
body: `grant_type=&username=${username}&password=${password}&scope=&client_id=&client_secret=`
})
.then(response => {
if (!response) {
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(json => {
console.log(json);
token = json['access_token'];
console.log(token);
if (token) { logged_in = true; }
})
}

View File

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IoT Building System</title>
<!-- meta -->
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- styles -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<link href="{{url_for('static', path='/main.css')}}" rel="stylesheet" media="screen">
{% block css %}{% endblock %}
</head>
<body>
<div class="container">
<!-- child template -->
{% block content %}{% endblock %}
</div>
{% include 'footer.html' %}
<!-- scripts -->
<script src="{{url_for('static', path='/main.js')}}" type="text/javascript"></script>
{% block js %}{% endblock %}
</body>
</html>

View File

@ -1,6 +0,0 @@
<footer class="footer">
<div class="container">
<small><span class="text-muted">© <a href="https://testdriven.io">TestDriven.io</a></span></small>
</div>
</footer>

View File

@ -1,37 +0,0 @@
{% extends "_base.html" %}
{% block content %}
<div class="starter-template">
<h1>IoT Building System</h1>
<hr><br>
<div>
<h3>Task</h3>
<p>Login</p>
<div class="login_form" role="group" aria-label="Basic example">
<form>
<input type="text" id="username_box" placeholder="Username">
<input type="password" id="password_box" placeholder="Password">
<input type="button"value="Login" class="btn btn-primary" onclick="handleLogInClick()">
</form>
</div>
</div>
<br><br>
<div>
<h3>Task Status</h3>
<br>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Result</th>
</tr>
</thead>
<tbody id="tasks">
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,40 @@
# EE495
# Hesham T. Banafa
# Jun 11th, 2022
from time import sleep
import requests
def poll(poll_url: str, data: dict, headers: dict) -> dict:
res : requests.Response = \
requests.post(poll_url, json=data, headers=headers)
#print('sent ', data)
print(res.text, res, res.reason)
if res.status_code != 200: return None
return res.json()
def emulate(poll_url, token_in: str):
mac = "94:b9:7e:fb:57:1a"
polling_interval_secons = 1
polling_headers = {
'accept' : 'application/json',
'Content-Type': 'application/json'
}
stop = False
state = False
while (not stop):
sleep(polling_interval_secons)
data = {
'bluetooth_mac': mac,
'state': state,
'token': token_in
}
data_dict = poll(poll_url, data, polling_headers)
if not data_dict: continue
if data_dict['open_command']: state = True
if __name__ == '__main__':
emulate("https://ibs.cronos.typedef.cf:4040/iotdevice/door/status",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJibHVldG9vdGhfbWFjIjoiOTQ6Yjk6N2U6ZmI6NTc6MWEifQ.oRbL0U70g8HGkKIOnwkesDiB40VWTPmwIWiysvP-hXA")

31
sql_app/tools/ibs.service Normal file
View File

@ -0,0 +1,31 @@
# This is templte to use for python venv applications
# from https://broplanner.com/wp-content/webpc-passthru.php?src=https://broplanner.com/wp-content/uploads/2022/01/Screenshot-2022-01-25-224223-1536x237.png&nocache=1
[Unit]
After = network.target
[Service]
User=ibs
Group=ibs
WorkingDirectory=/srv/ibs/ibs
ExecStart=/srv/ibs/ibs/run-tls
Restart=on-failure
RestartSec=15
# Security
ReadWritePaths=/srv/ibs/ibs
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=yes
ProtectHostname=yes
[Install]
WantedBy=multi-user.target

View File

@ -1,3 +1,5 @@
# May 2022
# Hesham T. Banafa <hishaminv@gmail.com>
from .database import SessionLocal
@ -7,3 +9,8 @@ def get_db():
yield db
finally:
db.close()
EMERG_TEMP = 50
EMERG_SMOKE = 1000
EMERG_OPEN_TIME_SEC = 500