diff --git a/backend/__main__.py b/backend/__main__.py index 2997faa..7775e89 100644 --- a/backend/__main__.py +++ b/backend/__main__.py @@ -5,11 +5,12 @@ import logging import ssl import sys +import threading from jinja2.exceptions import TemplateNotFound from backend import app, db -from backend.cron import get_default_scheduler, add_default_jobs +from backend.cron import get_default_scheduler, add_default_jobs, async_permanent_cron_recorder_checker from backend.models import * from backend.models import room_model, recorder_model, RecorderCommand, Recorder from backend.recorder_adapters import get_defined_recorder_adapters @@ -17,13 +18,30 @@ from backend.tools.model_updater import update_recorder_models_database, create_ from backend.websocket.base import WebSocketBase +def _start_initial_recorder_state_update(run_in_thread=True): + if run_in_thread: + thread = threading.Thread(target=async_permanent_cron_recorder_checker.check_object_state, args=()) + thread.start() + else: + async_permanent_cron_recorder_checker.check_object_state() # initial check of all recorders + + + +def _create_and_start_default_scheduler(): + print("Starting Scheduler") + scheduler = get_default_scheduler() + add_default_jobs(scheduler) + scheduler.start() + return scheduler + + 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() - #Recorder() + # Recorder() room_model.pre_fill_table() update_recorder_models_database(drop=False) create_default_recorders() @@ -44,16 +62,14 @@ def main(): except Exception as e: logging.critical(e) - print("Starting Scheduler") - scheduler = get_default_scheduler() - add_default_jobs(scheduler) - scheduler.start() + scheduler = _create_and_start_default_scheduler() + #_start_initial_recorder_state_update(run_in_thread=False) wsb = WebSocketBase() print("running websocket...(replaces normal app.run()") wsb.start_websocket(debug=True) # print("running web app...") - #app.run(debug=True, host="0.0.0.0", threaded=True) + # app.run(debug=True, host="0.0.0.0", threaded=True) wsb.send_test_msg() while True: diff --git a/backend/api/auth_api.py b/backend/api/auth_api.py index 2645de7..5cc4a05 100644 --- a/backend/api/auth_api.py +++ b/backend/api/auth_api.py @@ -7,6 +7,7 @@ Login through API does not start a new session, but instead returns JWT. """ import base64 import json +import logging from pprint import pprint import flask @@ -29,6 +30,8 @@ from backend.api import auth_api_bp, auth_api_providers_ns, auth_api_register_ns from backend.auth import AUTH_PROVIDERS, oidc_auth from backend.models.user_model import User, Group, BlacklistToken +logger = logging.getLogger("lrc.api.auth") + @auth_api_bp.route('/providers', methods=('GET',)) def get_auth_providers(): @@ -92,6 +95,7 @@ def login(): } return jsonify(token), 200 + # Endpoint for revoking the current users access token @auth_api_bp.route('/logout', methods=['GET', 'DELETE']) @jwt_required @@ -135,7 +139,7 @@ def create_or_retrieve_user_from_userinfo(userinfo): user = User.get_by_identifier(email) if user is not None: - app.logger.info("user found -> update user") + logger.info("user found -> update user") pprint(user.to_dict()) user.first_name = userinfo.get("given_name", "") user.last_name = userinfo.get("family_name", "") @@ -148,7 +152,7 @@ def create_or_retrieve_user_from_userinfo(userinfo): last_name=userinfo.get("family_name", ""), external_user=True, groups=user_groups) - app.logger.info("creating new user") + logger.info("creating new user") db.session.add(user) db.session.commit() @@ -159,8 +163,11 @@ def create_or_retrieve_user_from_userinfo(userinfo): @auth_api_bp.route('/oidc/', methods=['GET']) @oidc_auth.oidc_auth() def oidc(redirect_url=None): + logger.debug("oidc auth endpoint:") + return "fuck!" user = create_or_retrieve_user_from_userinfo(flask.session['userinfo']) if user is None: + logger.error("Could not authenticate: could not find or create user.") return "Could not authenticate: could not find or create user.", 401 if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False): token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY']) @@ -169,13 +176,13 @@ def oidc(redirect_url=None): 'access_token': create_access_token(identity=user, fresh=True), 'refresh_token': create_refresh_token(identity=user) }) + logger.info("Token: {}".format(token)) if redirect_url is None: redirect_url = request.headers.get("Referer") if redirect_url is None: redirect_url = request.args.get('redirect_url') if redirect_url is None: redirect_url = "/" - app.logger.info("Token: {}".format(token)) response = make_response(redirect(redirect_url)) response.set_cookie('tokens', base64.b64encode(token.encode('utf-8'))) return response @@ -189,8 +196,7 @@ def refresh(): as we do not actually verify a password in this endpoint.""" jwt_identity = get_jwt_identity() user = User.get_by_identifier(jwt_identity) - app.logger.info("Refreshing token for " + str(user)) + logger.info("Refreshing token for " + str(user)) new_token = create_access_token(identity=user, fresh=False) ret = {'access_token': new_token} return jsonify(ret), 200 - diff --git a/backend/auth/oidc.py b/backend/auth/oidc.py index 943cf70..ca87038 100644 --- a/backend/auth/oidc.py +++ b/backend/auth/oidc.py @@ -15,7 +15,6 @@ from backend.models.user_model import User from . import auth_bp from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS - OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth OIDCAuthentication.oidc_logout_orig = OIDCAuthentication.oidc_logout @@ -46,6 +45,7 @@ def create_or_retrieve_user_from_userinfo(userinfo): if user is not None: app.logger.info("user found") + app.logger.debug(f"user found: {email}") user.last_seen = datetime.utcnow() # TODO: update user! db.session.commit() @@ -54,21 +54,24 @@ def create_or_retrieve_user_from_userinfo(userinfo): user = User(email=email, first_name=userinfo.get("given_name", ""), last_name=userinfo.get("family_name", "")) - app.logger.info("creating new user") + app.logger.info(f"creating new user: {email}") db.session.add(user) db.session.commit() return user - @auth_bp.route('/oidc', methods=['GET']) @oidc_auth.oidc_auth() def oidc(): user_session = UserSession(flask.session) app.logger.info(user_session.userinfo) user = create_or_retrieve_user_from_userinfo(user_session.userinfo) + if user is None: + return '' login_user(user) + app.logger.info(f"logged in user: {str(user)}") + app.logger.debug(f"id token: {str(user_session.id_token)}") return jsonify(id_token=user_session.id_token, access_token=flask.session['access_token'], userinfo=user_session.userinfo) @@ -78,3 +81,9 @@ def oidc(): def oidc_logout(): oidc_auth.oidc_logout() return redirect('/') + + +@oidc_auth.error_view +def error(error=None, error_description=None): + app.logger.error(f"Something wwent wrong with OIDC auth – error: {error}, message: {error_description}") + return jsonify({'error': error, 'message': error_description}) diff --git a/backend/config.py b/backend/config.py index fe58a30..37999f4 100644 Binary files a/backend/config.py and b/backend/config.py differ diff --git a/backend/cron/cron_state_checker.py b/backend/cron/cron_state_checker.py index 7f1ba56..0020dca 100644 --- a/backend/cron/cron_state_checker.py +++ b/backend/cron/cron_state_checker.py @@ -172,4 +172,4 @@ async_permanent_cron_recorder_checker = StateChecker( for r in Recorder.get_all(): async_permanent_cron_recorder_checker.add_object_to_state_check(r.id) -async_permanent_cron_recorder_checker.check_object_state() # initial check of all recorders + diff --git a/backend/tools/exception_decorator.py b/backend/tools/exception_decorator.py index 3d4fa08..b224325 100644 --- a/backend/tools/exception_decorator.py +++ b/backend/tools/exception_decorator.py @@ -1,4 +1,7 @@ from backend import LrcException +import logging + +logger = logging.getLogger("lrc.tools.exception_decorator") def exception_decorator(*exceptions): @@ -8,6 +11,9 @@ def exception_decorator(*exceptions): ret = func(*args, **kwargs) return ret except exceptions as e: + logger.error(str(e)) raise LrcException(e) + return new_func - return decorator \ No newline at end of file + + return decorator diff --git a/backend/tools/recorder_state_checker.py b/backend/tools/recorder_state_checker.py index bf44213..c403c57 100644 --- a/backend/tools/recorder_state_checker.py +++ b/backend/tools/recorder_state_checker.py @@ -11,6 +11,7 @@ from typing import Union import requests from requests.auth import HTTPBasicAuth +from requests.exceptions import ConnectionError from multiprocessing.pool import ThreadPool from multiprocessing.context import TimeoutError @@ -26,6 +27,8 @@ from backend.recorder_adapters.extron_smp import SMP35x from backend.tools.recorder_streams_sanity_checks import check_frame_is_valid, check_if_audio_is_valid from backend.tools.send_mail import send_error_mail, get_smtp_error_handler +from backend.tools.exception_decorator import exception_decorator + logger = logging.getLogger("lrc.tools.simple_state_checker") smtp_error_handler = get_smtp_error_handler(subject="Errors have been detected while checking recorder states!") @@ -50,6 +53,7 @@ agent_states_lock = threading.RLock() agent_states = {} +@exception_decorator(ConnectionError) def get_service_url(service_type: str): if service_type in config['service_urls']: return config['service_urls'][service_type] @@ -64,19 +68,23 @@ def get_service_url(service_type: str): return None +@exception_decorator(ConnectionError) def get_calender(rec_id): params = {'agentid': rec_id} url = get_service_url('org.opencastproject.scheduler') + "/calendars" res = session.get(url, params=params) if res.ok: return Calendar(res.text) + raise LrcException(res.text, res.status_code) +@exception_decorator(ConnectionError) def get_capture_agents(): url = get_service_url("org.opencastproject.capture.admin") + "/agents.json" res = session.get(url) if res.ok: return res.json()["agents"]["agent"] + raise LrcException(res.text, res.status_code) def get_recorder_details_old(): @@ -117,19 +125,25 @@ def get_recorder_adapter(recorder_info: Union[dict, Recorder]) -> RecorderAdapte type = recorder_info.get("type") except KeyError: type = RecorderModel.get_by_id(recorder_info.get('recorder_model_id')).model_name - if "SMP" in type: - rec = SMP35x(recorder_info.get('ip'), recorder_info.get('password')) - else: - rec = Epiphan(recorder_info.get('ip'), recorder_info.get("username"), recorder_info.get("password")) - return rec + try: + if "SMP" in type: + rec = SMP35x(recorder_info.get('ip'), recorder_info.get('password')) + else: + rec = Epiphan(recorder_info.get('ip'), recorder_info.get("username"), recorder_info.get("password")) + return rec + except LrcException: + raise def check_stream_sanity(recorder_agent: Union[Recorder, dict], recorder_adapter: RecorderAdapter = None): - if recorder_adapter is None: - recorder_info = get_recorder_by_name(recorder_agent.get('name')) - recorder_adapter = get_recorder_adapter(recorder_info) - if not recorder_adapter.is_recording(): - return True, "not recording, so there is no stream!", recorder_agent.get('name') + try: + if recorder_adapter is None: + recorder_info = get_recorder_by_name(recorder_agent.get('name')) + recorder_adapter = get_recorder_adapter(recorder_info) + if not recorder_adapter.is_recording(): + return True, "not recording, so there is no stream!", recorder_agent.get('name') + except LrcException: + return False, "Could not determine if recorder is recording!", recorder_agent.get('name') if recorder_agent.get('archive_stream1') is None and recorder_agent.get( 'archive_stream2') is None: # fall back to default names and rtsp archive_stream_1_url = "rtsp://{}/{}".format(recorder_adapter.address, Config.DEFAULT_ARCHIVE_STREAM_1_NAME) @@ -186,7 +200,12 @@ def check_capture_agent_state(recorder_agent: Union[Recorder, dict]): return True, "Recorder is in offline / maintenance mode", recorder_agent.get('name') agent_state_error_msg = None logger.debug("Checking Agent {}".format(recorder_agent.get('name'))) - c = get_calender(recorder_agent.get('name')) + try: + c = get_calender(recorder_agent.get('name')) + except LrcException: + error_msg = "Could not get calender of recorder agent: {}!".format(recorder_agent.get('name')) + logger.fatal(error_msg) + return False, error_msg, recorder_agent.get('name') is_recording_in_calendar = len(list(c.timeline.now())) >= 1 if is_recording_in_calendar: logger.info("{} has entry in Calender and should therefore be recording... checking now!".format(