Signed-off-by: HeshamTB <hishaminv@gmail.com>
This commit is contained in:
HeshamTB 2022-06-08 18:30:09 +03:00
commit a034f17a63
6 changed files with 188 additions and 65 deletions

View File

@ -2,6 +2,7 @@
- [x] Issue requests from Lap to Pi or vica versa - [x] Issue requests from Lap to Pi or vica versa
- [x] Decide HTTP vs MQTT vs CoAP - [x] Decide HTTP vs MQTT vs CoAP
- [x] Hash passwords - [x] Hash passwords
- [x] Salt passwords
- [x] User registraion - [x] User registraion
- [x] User login in API - [x] User login in API
- [x] JWT token access - [x] JWT token access
@ -12,8 +13,8 @@
- [X] Expose door lock function - [X] Expose door lock function
- [X] Record access log - [X] Record access log
- [X] Expose data analysis - [X] Expose data analysis
- [ ] Load backend onto RPi - [X] Load backend onto RPi
- [ ] Test connections in lab network - [X] Test connections in lab network
- [ ] Define emrgancy triggers (manual and automatic) - [ ] Define emrgancy triggers (manual and automatic)
- [ ] Expose temporary control in case of emergancy - [ ] Expose temporary control in case of emergancy
- Triggers - Triggers
@ -26,6 +27,15 @@
- [X] Issue door open command - [X] Issue door open command
- [X] Make functions to gen a IotEntity token - [X] Make functions to gen a IotEntity token
- [ ] Write a small program/script to generate new Iot token and add new Iot Device into database - [ ] Write a small program/script to generate new Iot token and add new Iot Device into database
- [ ] Make inital database entries automatic for easy reset - [X] Make inital database entries automatic for easy reset
- [X] Expose access list endpoint for doors
- [X] Access list coutner for iot door
- [X] Force close in middle of timed open request
- [ ] Record user connections and time
- [ ] Record Iot dev connection and time
- [ ] Write unit tests
- [ ] Develop a program to visualize the data
- [ ] CLI frontend
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJibHVldG9vdGhfbWFjIjoic3RyaW5nIn0.ELl5AfBR1NdM4_OFhl_SCTm9EMPpqjiCKOSS0CrOJps eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJibHVldG9vdGhfbWFjIjoic3RyaW5nIn0.ELl5AfBR1NdM4_OFhl_SCTm9EMPpqjiCKOSS0CrOJps

View File

@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
from . import models, schemas, crypto, auth_helper from . import models, schemas, crypto, auth_helper
from datetime import datetime from datetime import datetime
from warnings import warn
# TODO: Data we can collect or log # TODO: Data we can collect or log
# - Last user connection (link to user) # - Last user connection (link to user)
@ -34,8 +35,11 @@ def get_user_by_username(db: Session, username: str) -> models.User:
def get_users(db: Session, skip: int = 0, limit: int = 100): def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all() return db.query(models.User).offset(skip).limit(limit).all()
def get_access_log_for_door_by_door_mac(db: Session, bluetooth_mac : str): def get_access_log_for_door_by_door_mac(db: Session, iot_id: str):
return db.query(models.DoorAccessLog).filter(models.DoorAccessLog.iot_dev_bluetooth_mac == bluetooth_mac).all() warn("Manual access log read is deprecated. Use device.access_log",
DeprecationWarning, stacklevel=2)
return db.query(models.DoorAccessLog)\
.filter(models.DoorAccessLog.iot_id == iot_id).all()
def get_access_log_for_user_by_id(db: Session, 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() return db.query(models.DoorAccessLog).filter(models.DoorAccessLog.user_id == id).all()
@ -56,6 +60,16 @@ def create_user(db: Session, user: schemas.UserCreate):
db.refresh(db_user) db.refresh(db_user)
return db_user return db_user
def update_user_password(db: Session, user: models.User, request: schemas.UserUpdatePassword):
key = crypto.gen_new_key(request.password)
salt = key[1]
hashed_pass = key[0]
user.passwd_salt = salt
user.hashed_password = hashed_pass
db.add(user)
db.commit()
db.refresh(user)
def get_iot_entities(db: Session, skip: int = 0, limit: int = 100): def get_iot_entities(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.IotEntity).offset(skip).limit(limit).all() return db.query(models.IotEntity).offset(skip).limit(limit).all()
@ -175,3 +189,13 @@ def record_user_connection(db: Session, user: models.User, time: datetime):
db.add(entry) db.add(entry)
db.commit() db.commit()
db.refresh(entry) 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 update_user_status(db: Session, user: models.User, state: bool):
user.is_active = state
db.add(user)
db.commit()
db.refresh(user)

View File

@ -2,7 +2,8 @@
from . import crud, main, schemas, auth_helper from . import crud, main, schemas, auth_helper
from decouple import config from decouple import config
from .database import SessionLocal from .database import SessionLocal
from datetime import timedelta from datetime import timedelta, datetime
from random import randint
db = SessionLocal() db = SessionLocal()
@ -63,9 +64,64 @@ def init_monitor():
def init_allowance(): def init_allowance():
crud.create_user_link_to_iot(db, 1, 1) crud.create_user_link_to_iot(db, 1, 1)
def init_sensor_data():
for i in range(50):
room_data = \
schemas.\
IotMonitorRoomInfo\
(humidity=randint(20, 80),
people=randint(0, 10),
temperature=randint(18, 27),
smoke_sensor_reading=randint(150, 700),
token='dummy')
crud.record_room_sensor_data(db, room_data)
def init_open_close_requests():
user = crud.get_user_by_email(db, "hisham@banafa.com.sa")
crud.set_open_door_request(db, 1, 10)
log_entry = schemas.DoorAccessLog(user_id=user.id,
iot_id=1,
command="OPEN",
timestamp=datetime.now())
crud.record_door_access_log(db, log_entry)
log_entry = schemas.DoorAccessLog(user_id=user.id,
iot_id=1,
command="OPEN",
timestamp=datetime.now())
crud.record_door_access_log(db, log_entry)
log_entry = schemas.DoorAccessLog(user_id=user.id,
iot_id=1,
command="OPEN",
timestamp=datetime.now())
crud.record_door_access_log(db, log_entry)
log_entry = schemas.DoorAccessLog(user_id=user.id,
iot_id=1,
command="CLOSE",
timestamp=datetime.now())
crud.record_door_access_log(db, log_entry)
def init_user_connections():
users = [ crud.get_user(db, 1),
crud.get_user(db, 2),
crud.get_user(db, 3)]
for i in range(3):
crud.record_user_connection(db, users[i], datetime.now())
crud.record_user_connection(db, users[i], datetime.now())
crud.record_user_connection(db, users[i], datetime.now())
def init(): def init():
init_user() init_user()
init_door() init_door()
init_monitor() init_monitor()
init_allowance() init_allowance()
init_sensor_data()
init_open_close_requests()
init_user_connections()

View File

@ -130,6 +130,39 @@ def issue_close_door_command(command: schemas.CloseDoorRequest,
return device return device
@app.get("/users/acesslist/", response_model=List[schemas.RoomOverview], tags=['Users'])
def get_iot_access_list_for_user(db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_active_user)):
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,
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
)
access_list.append(entry)
#crud.record_user_connection(db, user, datetime.now())
return access_list
@app.patch("/users/updatepassword", tags=['Users'])
def change_user_password(request: schemas.UserUpdatePassword,
current_user: models.User = Depends(get_current_active_user),
db: Session = Depends(get_db)):
crud.update_user_password(db, current_user, request)
return
@app.post("/users/tkn", response_model=schemas.Token, tags=['Users']) @app.post("/users/tkn", response_model=schemas.Token, tags=['Users'])
@app.post("/tkn", response_model=schemas.Token, tags=['Users']) @app.post("/tkn", response_model=schemas.Token, tags=['Users'])
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
@ -148,30 +181,6 @@ def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db:
#crud.record_user_connection(db, user, datetime.now()) #crud.record_user_connection(db, user, datetime.now())
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/acesslist/", response_model=List[schemas.RoomOverview], tags=['Users'])
def get_iot_access_list_for_user(db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_active_user)):
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=500, 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
)
access_list.append(entry)
#crud.record_user_connection(db, user, datetime.now())
return access_list
@app.get("/admin/users/", response_model=List[schemas.User], tags=['Admin']) @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)):
users = crud.get_users(db, skip=skip, limit=limit) users = crud.get_users(db, skip=skip, limit=limit)
@ -192,66 +201,78 @@ def create_iot_entities(iot_entity: schemas.IotEntityCreate, db: Session = Depen
def read_user(user_id: int, db: Session = Depends(get_db)): def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id) db_user = crud.get_user(db, user_id=user_id)
if db_user is None: if db_user is None:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="User not found")
return db_user return db_user
@app.post("/admin/users/allowdevice/id", tags=['Admin']) @app.patch("/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)):
user = crud.get_user(db, request.user_id) user = crud.get_user(db, request.user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="User not found")
iot_entity = crud.get_iot_entity(db, request.iot_entity_id) iot_entity = crud.get_iot_entity(db, request.iot_entity_id)
if not iot_entity: if not iot_entity:
raise HTTPException(status_code=404, detail="Iot Entity not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="Iot Entity not found")
res = crud.create_user_link_to_iot(db, request.user_id, request.iot_entity_id) res = crud.create_user_link_to_iot(db, request.user_id, request.iot_entity_id)
if not res: if not res:
raise HTTPException(status_code=500, detail="Could not complete operation") raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not complete operation")
crud.increment_door_access_list_counter(db, iot_entity) crud.increment_door_access_list_counter(db, iot_entity)
return return
@app.post("/admin/users/disallowdevice/id", tags=['Admin']) @app.patch("/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)):
user = crud.get_user(db, request.user_id) user = crud.get_user(db, request.user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="User not found")
iot_entity = crud.get_iot_entity(db, request.iot_entity_id) iot_entity = crud.get_iot_entity(db, request.iot_entity_id)
if not iot_entity: if not iot_entity:
raise HTTPException(status_code=404, detail="Iot Entity not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="Iot Entity not found")
res = crud.remove_user_link_to_iot(db, request.user_id, request.iot_entity_id) res = crud.remove_user_link_to_iot(db, request.user_id, request.iot_entity_id)
if not res: if not res:
raise HTTPException(status_code=500, detail="Could not complete operation") raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not complete operation")
crud.increment_door_access_list_counter(db, iot_entity) crud.increment_door_access_list_counter(db, iot_entity)
return return
@app.post("/admin/users/allowdevice/name", tags=['Admin']) @app.patch("/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)):
user = crud.get_user_by_username(db, request.username) user = crud.get_user_by_username(db, request.username)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="User not found")
iot_entity = crud.get_iot_entity_by_description(db, request.description) iot_entity = crud.get_iot_entity_by_description(db, request.description)
if not iot_entity: if not iot_entity:
raise HTTPException(status_code=404, detail="Iot Entity not found") raise HTTPException(status.HTTP_404_NOT_FOUND,
detail="Iot Entity not found")
res = crud.create_user_link_to_iot(db, user.id, iot_entity.id) res = crud.create_user_link_to_iot(db, user.id, iot_entity.id)
if not res: if not res:
raise HTTPException(status_code=500, detail="Could not complete operation") raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not complete operation")
return return
@app.post("/admin/users/{user_id}/deactiveate", tags=['Admin']) @app.patch("/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)):
return user = crud.get_user(db, user_id)
crud.update_user_status(db, user, False)
@app.post("/admin/users/{user_id}/activeate", tags=['Admin']) @app.patch("/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)):
return user = crud.get_user(db, user_id)
crud.update_user_status(db, user, True)
@app.post("/admin/iotdevice/gentoken/", response_model=schemas.Token, tags=['Admin']) @app.post("/admin/iotdevice/gentoken/", response_model=schemas.Token, tags=['Admin'])
def generate_token_for_iot_device(bluetooth_mac : schemas.IotBluetoothMac, def generate_token_for_iot_device(bluetooth_mac : schemas.IotBluetoothMac,
@ -261,31 +282,38 @@ def generate_token_for_iot_device(bluetooth_mac : schemas.IotBluetoothMac,
tkn = auth_helper.create_iot_dev_token(data) tkn = auth_helper.create_iot_dev_token(data)
return {"access_token": tkn, "token_type": "bearer"} return {"access_token": tkn, "token_type": "bearer"}
@app.post("/admin/iotdevice/accesslog/", tags=['Admin'])
def get_access_log_for_door(request : schemas.AccessLogRequest,
db : Session = Depends(get_db)):
device = crud.get_iot_entity_by_bluetooth_mac(db, request.bluetooth_mac)
if not device: raise HTTPException(status_code=404, detail="Iot Entity not found")
return crud.get_access_log_for_door_by_door_mac(db, request.bluetooth_mac)
@app.post("/admin/user/accesslog/email/", tags=['Admin']) @app.post("/admin/user/accesslog/email/", tags=['Admin'])
def get_access_log_history_for_user(request : schemas.UserAccessLogRequestEmail, def get_access_log_history_for_user(request : schemas.UserAccessLogRequestEmail,
db : Session = Depends(get_db)): db : Session = Depends(get_db)):
user = crud.get_user_by_email(db, request.email) user = crud.get_user_by_email(db, request.email)
if not user: raise HTTPException(status_code=404, detail="User not found") if not user: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
return crud.get_access_log_for_user_by_id(db, user.id) return user.access_log
@app.post("/admin/user/accesslog/username/", tags=['Admin']) @app.post("/admin/user/accesslog/username/", tags=['Admin'])
def get_access_log_history_for_user(request : schemas.UserAccessLogRequestUsername, def get_access_log_history_for_user(request : schemas.UserAccessLogRequestUsername,
db : Session = Depends(get_db)): db : Session = Depends(get_db)):
user = crud.get_user_by_username(db, request.username) user = crud.get_user_by_username(db, request.username)
if not user: raise HTTPException(status_code=404, detail="User not found") if not user: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
return crud.get_access_log_for_user_by_id(db, user.id) return user.access_log
@app.get("/admin/roominfo/now/", tags=['Admin']) @app.get("/admin/roominfo/now/", tags=['Admin'])
def get_room_data(db: Session = Depends(get_db)): def get_room_data(db: Session = Depends(get_db)):
return crud.get_room_data_now(db) return crud.get_room_data_now(db)
@app.get("/admin/roominfo/history/sensors", tags=['Admin'])
def get_all_sensor_history(skip: int = 0, limit: int = 100,
api_key: APIKey = Depends(auth_helper.valid_api_key),
db: Session = Depends(get_db)):
return crud.get_sensor_data_for_room(db, skip, limit)
@app.post("/admin/roominfo/accesslog",response_model=List[schemas.DoorAccessLog], tags=['Admin'])
def get_access_log_for_door(request : schemas.AccessLogRequest,
api_key: APIKey = Depends(auth_helper.valid_api_key),
db : Session = Depends(get_db)):
device: models.IotEntity = crud.get_iot_entity(db, request.iot_id)
if not device: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Iot Entity not found")
return device.access_log
@app.post("/iotdevice/door/status", response_model=schemas.IotDoorPollingResponse, tags=['Iot']) @app.post("/iotdevice/door/status", response_model=schemas.IotDoorPollingResponse, tags=['Iot'])
def polling_method_for_iot_entity(request: schemas.IotDoorPollingRequest, def polling_method_for_iot_entity(request: schemas.IotDoorPollingRequest,
db: Session = Depends(get_db)): db: Session = Depends(get_db)):

View File

@ -16,7 +16,8 @@ class User(Base):
last_token = Column(String, nullable=True) last_token = Column(String, nullable=True)
connections = relationship("UserConnectionHistory") connections = relationship("UserConnectionHistory")
authorized_devices = relationship("IotEntity", secondary="user_iot_link", back_populates="authorized_users") authorized_devices = relationship("IotEntity", secondary="user_iot_link", back_populates="authorized_users")
connections = relationship("UserConnectionHistory")
access_log = relationship("DoorAccessLog", back_populates="user")
class IotEntity(Base): class IotEntity(Base):
__tablename__ = "iot_entities" __tablename__ = "iot_entities"
@ -30,6 +31,7 @@ class IotEntity(Base):
force_close = Column(Boolean, default=False) force_close = Column(Boolean, default=False)
state = Column(Boolean, default=False) # True is open, False is closed state = Column(Boolean, default=False) # True is open, False is closed
authorized_users = relationship("User", secondary="user_iot_link", back_populates="authorized_devices") authorized_users = relationship("User", secondary="user_iot_link", back_populates="authorized_devices")
access_log = relationship("DoorAccessLog", back_populates="iot_device")
class UserAuthToIoTDev(Base): class UserAuthToIoTDev(Base):
__tablename__ = "user_iot_link" __tablename__ = "user_iot_link"
@ -42,8 +44,10 @@ class DoorAccessLog(Base):
__tablename__ = "door_access_log" __tablename__ = "door_access_log"
entry_id = Column(Integer, primary_key=True, index=True) entry_id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey('user_accounts.id')) user_id = Column(Integer, ForeignKey('user_accounts.id'), index=True)
iot_id = Column(Integer, ForeignKey('iot_entities.id')) user = relationship("User", back_populates="access_log")
iot_id = Column(Integer, ForeignKey('iot_entities.id'), index=True)
iot_device = relationship("IotEntity", back_populates="access_log")
command = Column(String(16)) command = Column(String(16))
timestamp = Column(DateTime) timestamp = Column(DateTime)
@ -62,7 +66,7 @@ class UserConnectionHistory(Base):
__tablename__ = "user_connection_history" __tablename__ = "user_connection_history"
reading_id = Column(Integer, primary_key=True) reading_id = Column(Integer, primary_key=True)
user_id = Column(Integer,ForeignKey("user_accounts.id"), index=True) user_id = Column(Integer, ForeignKey("user_accounts.id"), index=True)
timestamp = Column(DateTime) timestamp = Column(DateTime)
# TODO: add ip # TODO: add ip

View File

@ -60,6 +60,9 @@ class UserAllowForIotEntityRequestByUsername(BaseModel):
username: str username: str
description: str description: str
class UserUpdatePassword(BaseModel):
password: str
class OpenDoorRequestBase(BaseModel): class OpenDoorRequestBase(BaseModel):
username: str username: str
bluetooth_mac: str bluetooth_mac: str
@ -74,8 +77,6 @@ class IotDoorPollingRequest(BaseModel):
bluetooth_mac : str bluetooth_mac : str
state: int state: int
token : str token : str
class Config:
orm_mode = True
class IotDoorPollingResponse(BaseModel): class IotDoorPollingResponse(BaseModel):
open_command : bool open_command : bool
@ -106,7 +107,7 @@ class DoorAccessLog(BaseModel):
orm_mode = True orm_mode = True
class AccessLogRequest(BaseModel): class AccessLogRequest(BaseModel):
bluetooth_mac : str iot_id : int
class UserAccessLogRequestUsername(BaseModel): class UserAccessLogRequestUsername(BaseModel):
username : str username : str