Compare commits

...

15 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
16 changed files with 334 additions and 14 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
@ -65,4 +67,4 @@ def valid_monitor_token(token: str, db: Session):
mac_signed = payload.get("bluetooth_mac")
monitor = crud.get_monitor_bluetooth(db, mac_signed)
return monitor
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
@ -217,6 +220,15 @@ def record_room_sensor_data(db: Session, entry: schemas.MonitorUpdateReadings,
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)
@ -239,3 +251,15 @@ def update_user_status(db: Session, user: models.User, state: bool):
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,7 +57,7 @@ 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_monitor_bluetooth(db, iot_monitor.bluetooth_mac)
if monitor_exists: return
@ -66,6 +68,7 @@ def init_allowance():
def init_sensor_data():
monitor = crud.get_monitor(db, 1)
if monitor.sensor_history: return
for i in range(50):
room_data = \
schemas.\
@ -79,6 +82,7 @@ def init_sensor_data():
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,
@ -130,4 +134,4 @@ def init():
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,3 +1,6 @@
# 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
@ -6,7 +9,7 @@ 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
@ -382,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.MonitorUpdateReadings,
db: Session = Depends(get_db)):
device : schemas.Monitor = auth_helper.valid_monitor_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, 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'])
@ -437,4 +450,4 @@ def get_allowed_usernames(request: schemas.IotDoorPollingRequest,
def get(db: Session = Depends(get_db)):
mon = crud.get_monitor(db, "ff:ff:ff:ff")
return mon.door
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
@ -69,7 +72,6 @@ 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)
@ -87,4 +89,14 @@ class UserConnectionHistory(Base):
timestamp = Column(DateTime)
# TODO: add ip
# TODO: Add table to store active sessions. May periodically clear.
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
@ -104,6 +107,7 @@ class IotDoorPollingResponse(BaseModel):
acces_list_counter : int
time_seconds : int
force_close: bool
state: bool
class IotMonitorRoomInfo(BaseModel):
humidity : int

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

View File

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