diff --git a/backend/__init__.py b/backend/__init__.py index 7e83474..055fdcb 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -15,6 +15,7 @@ from flask import Flask, jsonify from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth from flask_jwt_extended import JWTManager, decode_token from flask_login import LoginManager +from flask_restplus import abort from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS from backend.config import Config @@ -128,10 +129,16 @@ login_manager.init_app(app) # flask_jwt_extended: to be used usually by API jwt_extended = JWTManager(app) -# +# this is another library.... this is (probaby ->verify) used to check JWTs provided by external sources (KIT, etc.) jwt_auth = HTTPTokenAuth('Bearer') +@jwt_extended.invalid_token_loader +def unauthorized_jwt(token): + main_logger.info("Unauthorized access; invalid token provided: {}".format(token)) + abort(401) + + @jwt_auth.verify_token def verify_token(token): """This function (and HTTPTokenAuth('Bearer')) has been defined to be used together with MultiAuth. For API calls diff --git a/backend/__main__.py b/backend/__main__.py index 413d465..95e5a1e 100644 --- a/backend/__main__.py +++ b/backend/__main__.py @@ -8,16 +8,19 @@ import ssl from jinja2.exceptions import TemplateNotFound from backend import app, db -from backend.models import room_model, recorder_model +from backend.models import room_model, recorder_model, RecorderCommand from backend.recorder_adapters import get_defined_recorder_adapters -from backend.tools.model_updater import update_recorder_models_database +from backend.tools.model_updater import update_recorder_models_database, create_default_recorders def main(): logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') - db.drop_all() - db.create_all() + #db.drop_all() + #db.create_all() + room_model.pre_fill_table() + update_recorder_models_database(drop=False) + create_default_recorders() print(app.config.get("SERVER_NAME", None)) server_name = app.config.get("SERVER_NAME", None) @@ -34,10 +37,6 @@ def main(): except Exception as e: logging.critical(e) - - room_model.pre_fill_table() - update_recorder_models_database() - app.run(debug=True, host="0.0.0.0") diff --git a/backend/api/models.py b/backend/api/models.py index e774d09..2e89c12 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -29,12 +29,21 @@ recorder_model = api_recorder.model('Recorder', { 'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), 'last_time_modified': fields.DateTime(required=False, description='Creation date of the recorder'), 'name': fields.String(min_length=3, required=True, description='The recorder\'s name'), + 'model_name': fields.String(min_length=3, required=True, + description='The recorder\'s model name (might slightly ' + 'differ from actual name of the model)'), + 'serial_number': fields.String(required=False, description='The recorder\'s serial number'), + 'firmware_version': fields.String(required=False, description='The recorder\'s firmware'), + 'description': fields.String(required=False, description='The recorder\'s description'), 'locked': fields.Boolean(required=False, description='Indicates whether the recorder settings can be altered'), 'lock_message': fields.String(required=False, description='Optional: message explaining lock state'), 'offline': fields.Boolean(required=False, description='Should be set when recorder is disconnected for maintenance, etc.'), + 'additional_camera_connected': fields.Boolean(required=False, + description='Indicates whether an additional camera is connected'), 'ip': fields.String(required=False, description='The recorder\'s IP address'), + 'mac': fields.String(required=False, description='The recorder\'s IP address'), 'network_name': fields.String(required=False, description='The recorder\'s network name'), 'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'), 'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'), @@ -91,4 +100,3 @@ recorder_model_model = api_recorder.model('Recorder Model', { description='Model of the recorder.'), 'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands") }) - diff --git a/backend/config.py b/backend/config.py index 6d3ed2b..1286c24 100644 Binary files a/backend/config.py and b/backend/config.py differ diff --git a/backend/models/initial_recorders.json b/backend/models/initial_recorders.json index b0a190f..62f8415 100644 Binary files a/backend/models/initial_recorders.json and b/backend/models/initial_recorders.json differ diff --git a/backend/models/recorder_model.py b/backend/models/recorder_model.py index 7c8cfdd..0a9b4cd 100644 --- a/backend/models/recorder_model.py +++ b/backend/models/recorder_model.py @@ -60,11 +60,11 @@ class RecorderModel(db.Model): return RecorderModel.query.filter(RecorderModel.checksum == md5_sum).first() @hybrid_property - def requires_user(self): + def requires_username(self): return self._requires_user > 0 - @requires_user.setter - def requires_user(self, val: bool): + @requires_username.setter + def requires_username(self, val: bool): self._requires_user = 1 if val else 0 @hybrid_property @@ -87,10 +87,9 @@ class Recorder(db.Model): name = db.Column(db.Unicode(63), unique=True, nullable=False) model_name = db.Column(db.Unicode(63), unique=False, nullable=False) serial_number = db.Column(db.Unicode(63), unique=True, nullable=True) - locked = db.Column(db.Boolean, default=False) + locked = db.Column(db.Boolean, default=False) # no modifications allowed lock_message = db.Column(db.String, nullable=True, default=None) - in_maintenance = db.Column(db.Boolean, default=False) - offline = db.Column(db.Boolean, default=False) + offline = db.Column(db.Boolean, default=False) # maintenance, etc. description = db.Column(db.Unicode(255), unique=False, nullable=True, default="") _mac = db.Column(db.String(17), unique=True, nullable=True) _ip = db.Column(db.String(15), unique=True, nullable=True, default=None) @@ -122,6 +121,13 @@ class Recorder(db.Model): def get_by_identifier(identifier): return Recorder.query.filter(Recorder.id == identifier).first() + @staticmethod + def get_by_mac(mac: str): + if mac is None or mac == '': + return None + mac = mac.replace('-', ':').lower() + return Recorder.query.filter(Recorder._mac == mac).first() + @staticmethod def get_all(): return Recorder.query.all() diff --git a/backend/models/room_model.py b/backend/models/room_model.py index 5d5864f..e074e0b 100644 --- a/backend/models/room_model.py +++ b/backend/models/room_model.py @@ -3,6 +3,7 @@ Models for lecture recorder """ import json +import logging from sqlalchemy import MetaData, CheckConstraint from sqlalchemy.exc import IntegrityError @@ -12,6 +13,8 @@ from datetime import datetime, timedelta from backend import db, app, login_manager from backend.tools.scrape_rooms import scrape_rooms +logger = logging.getLogger("lrc."+__name__) + metadata = MetaData() @@ -74,12 +77,16 @@ class Room(db.Model): def pre_fill_table(): rooms = scrape_rooms() - i_tunes_u_mappings = [Room(name=room['name'], number=room['room_number'], + logger.debug("tada") + logger.debug("got {} rooms".format(len(rooms))) + db_rooms = [Room(name=room['name'], number=room['room_number'], building_name=room['building_name'], building_number=room['building_number']) for room in rooms] try: - db.session.bulk_save_objects(i_tunes_u_mappings) + db.session.bulk_save_objects(db_rooms) db.session.commit() + logger.debug("rooms commited to DB!") except IntegrityError as e: + logger.error("Could not add rooms! ({})".format(e)) db.session.rollback() diff --git a/backend/tools/helpers.py b/backend/tools/helpers.py index e27da4f..0361d21 100644 --- a/backend/tools/helpers.py +++ b/backend/tools/helpers.py @@ -1,7 +1,6 @@ import hashlib - def calculate_md5_checksum(string_to_md5_sum: str): return hashlib.md5(string_to_md5_sum.encode('utf-8')).hexdigest() diff --git a/backend/tools/model_updater.py b/backend/tools/model_updater.py index e4d5b60..92c480f 100644 --- a/backend/tools/model_updater.py +++ b/backend/tools/model_updater.py @@ -21,21 +21,27 @@ from backend.models.recorder_model import RecorderModel, RecorderCommand, Record from backend.recorder_adapters import get_defined_recorder_adapters from backend.tools.helpers import calculate_md5_checksum +logger = logging.getLogger("lrc." + __name__) + KNOWN_RECORDERS = {re.compile(r'(?PSMP)[\s]*(?P[\d]+)[\s]*.?[\s]*(?P[\S]*)'): 'SMP', re.compile( r'(?PLectureRecorder X2|LectureRecorder|VGADVI Recorder|DVI Broadcaster DL|DVIRecorderDL)'): 'Epiphan'} def create_recorder_commands_for_recorder_adapter(command_definitions: dict, recorder_model: RecorderModel): + logger.debug("Received {} command definitions to create " + "for recoder model {}".format(len(command_definitions), + recorder_model.record_adapter_id)) existing_recorder_commands = RecorderCommand.query.filter( - and_(RecorderCommand.name.in_(command_definitions.keys())), + and_( + RecorderCommand.name.in_([recorder_model.record_adapter_id + ":" + k for k in command_definitions.keys()])), RecorderCommand.recorder_model == recorder_model) existing_commands = set() for existing_command in existing_recorder_commands: existing_commands.add(existing_command.name) args = command_definitions.get(existing_command.name) if dumps(existing_command.parameters) != dumps(args): - logging.warning( + logger.warning( "The function definition {} collides with an existing definition of the same name " "but different parameters!".format( existing_command.name)) @@ -44,15 +50,21 @@ def create_recorder_commands_for_recorder_adapter(command_definitions: dict, rec db.session.commit() for c_d in set(command_definitions.keys()) - existing_commands: - # print(c_d) args = command_definitions.get(c_d) + # print(args) # create new recorder command(s) - r_c = RecorderCommand(name=c_d, parameters=args, recorder_model=recorder_model) + r_c = RecorderCommand(name=recorder_model.record_adapter_id + ":" + c_d, parameters=args, + recorder_model=recorder_model) db.session.add(r_c) + db.session.flush() db.session.commit() -def update_recorder_models_database(): +def update_recorder_models_database(drop: bool = False): + if drop: + for r in RecorderModel.get_all(): + db.session.delete(r) + db.session.commit() r_as = get_defined_recorder_adapters() for r_a in r_as: try: @@ -64,35 +76,40 @@ def update_recorder_models_database(): db.session.add(r_m) db.session.flush() db.session.refresh(r_m) + logger.debug("Creating command definitions for rec mod adapter {}".format(r_m.record_adapter_id)) + create_recorder_commands_for_recorder_adapter(r_a["commands"], r_m) else: - if not r_m.model_name == r_a["name"]: + if r_m.model_name != r_a["name"]: r_m.model_name = r_a["name"] r_m.last_time_modified = datetime.utcnow() - if not model_checksum == r_m.checksum: + if model_checksum != r_m.checksum: r_m.last_time_modified = datetime.utcnow() r_m.checksum = model_checksum r_m.last_checksum_change = datetime.utcnow() + logger.debug("Updating command definitions for rec mod adapter {}".format(r_m.record_adapter_id)) create_recorder_commands_for_recorder_adapter(r_a["commands"], r_m) except IntegrityError as e: + logger.error(e) db.session.rollback() db.session.commit() def get_recorder_room(rec: dict) -> Room(): rooms = Room.get_by_building_number(rec.get('building', None)) - if rooms.count() <= 1: + if rooms.count() <= 0: + logger.warning("Building {} unknown! Can not find room for recorder {}.".format(rec['building'], rec['name'])) + return None + if rooms.count() == 1: return rooms.first() room_name = rec.get('room') + room_name = room_name.replace('–', ' ') for room in rooms: if all([r_n in room.name for r_n in room_name.split()]): return room + logger.warning("No room found for recorder {}".format(rec['name'])) def create_default_recorders(): - models = RecorderModel.get_all() - for m in models: - print(m) - f = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, 'models', 'initial_recorders.json')) with open(f, 'r') as json_file: recorders = json.load(json_file)['recorders'] @@ -112,39 +129,64 @@ def create_default_recorders(): model_number = match.groupdict().get('number', None) options = match.groupdict().get('options', None) + if options is not None and options != "": + options = options.split(',') if model_number is not None: - model_name = name + model_number[:-1] # just get prefix (remove last digit) + model_name = name + " " + model_number + model_search_name = name + model_number[:-1] # just get prefix (remove last digit); SMP 35x -> 35 else: - model_name = KNOWN_RECORDERS[k_r] - rec_model = RecorderModel.get_where_adapter_id_contains(model_name) - - rec = Recorder(name=room_rec_name + " Recorder", model_name=model_name, recorder_model=rec_model, - username=username, password=password, firmware_version=firmware_version) - rec.mac = mac + model_name = name + model_search_name = KNOWN_RECORDERS[k_r] + rec_model = RecorderModel.get_where_adapter_id_contains(model_search_name) + rec = Recorder.get_by_mac(mac) + if rec is None: + rec = Recorder.get_by_name(room_rec_name + " Recorder") + if rec is None: + rec = Recorder() + rec.mac = mac + db.session.add(rec) + rec.name = room_rec_name + " Recorder" + rec.model_name = model_name + rec.recorder_model = rec_model + rec.username = username + rec.password = password + rec.firmware_version = firmware_version rec.ip = ip4 rec.additional_camera_connected = additional_camera rec.additional_note = description + rec.configured_options = options rec.room = get_recorder_room(r) - print(rec) - - db.session.add(rec) db.session.flush() db.session.refresh(rec) - print(recorders) + db.session.commit() if __name__ == '__main__': + # for r in Room.get_all(): + # print(r) + # exit() + + commands = RecorderCommand.get_all() + for c in commands: + print(c) + print(c.recorder_model) + + for r_m in RecorderModel.get_all(): + print(r_m.recorder_commands) + + exit() recorders = Recorder.get_all() for r in recorders: - print("{}: {}".format(r, r.room)) + if r.room is None: + print("{}: {}".format(r, r.room)) # db.drop_all() # db.create_all() - # update_recorder_models_database() - # create_default_recorders() - # db.session.commit() + update_recorder_models_database() + create_default_recorders() + db.session.commit() # print(get_recorder_room({"room": "Grosser Hörsaal Bauingenieure", "building": "10.50"})) # print(get_recorder_room({"room": "Grosser Hörsaal Bauingenieure", "building": "30.95"}))