added permissions api and websocket stuff

This commit is contained in:
2019-11-28 19:39:53 +01:00
parent c4b54357f7
commit a709dbcaef
14 changed files with 229 additions and 24 deletions

View File

@@ -31,6 +31,10 @@ ics = "*"
coloredlogs = "*" coloredlogs = "*"
pythonping = "*" pythonping = "*"
scapy = "*" scapy = "*"
python-socketioclient = "*"
python-socketio = {version = "*",extras = ["client"]}
socketio-client = "*"
websocket-client = "*"
[dev-packages] [dev-packages]

View File

@@ -11,6 +11,7 @@ from backend import app, db
from backend.models import room_model, recorder_model, RecorderCommand from backend.models import room_model, recorder_model, RecorderCommand
from backend.recorder_adapters import get_defined_recorder_adapters from backend.recorder_adapters import get_defined_recorder_adapters
from backend.tools.model_updater import update_recorder_models_database, create_default_recorders from backend.tools.model_updater import update_recorder_models_database, create_default_recorders
from backend.websocket.base import WebSocketBase
def main(): def main():
@@ -37,7 +38,11 @@ def main():
except Exception as e: except Exception as e:
logging.critical(e) logging.critical(e)
app.run(debug=True, host="0.0.0.0") 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)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -27,6 +27,7 @@ api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API',
api_user = Namespace('user', description="User management namespace", authorizations=api_authorizations) api_user = Namespace('user', description="User management namespace", authorizations=api_authorizations)
api_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations) api_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations)
api_permissions = Namespace('permissions', description="Permissions management namespace", authorizations=api_authorizations)
api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations) api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations)
api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations) api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations)
api_virtual_command = Namespace('virtual_command', description="Virtual command namespace", api_virtual_command = Namespace('virtual_command', description="Virtual command namespace",
@@ -38,6 +39,7 @@ api_control = Namespace('control', description="Control namespace",
api_v1.add_namespace(api_user) api_v1.add_namespace(api_user)
api_v1.add_namespace(api_group) api_v1.add_namespace(api_group)
api_v1.add_namespace(api_permissions)
api_v1.add_namespace(api_room) api_v1.add_namespace(api_room)
api_v1.add_namespace(api_recorder) api_v1.add_namespace(api_recorder)
api_v1.add_namespace(api_virtual_command) api_v1.add_namespace(api_virtual_command)
@@ -58,6 +60,7 @@ auth_api_v1.add_namespace(auth_api_register_ns)
from .example_api import * from .example_api import *
from .auth_api import * from .auth_api import *
from .user_api import * from .user_api import *
from .permission_api import *
from .group_api import * from .group_api import *
from .room_api import * from .room_api import *
from .recorder_api import * from .recorder_api import *

View File

@@ -20,7 +20,9 @@ user_model = api_user.model('User', {
fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})), fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})),
required=False, description='Group memberships.'), required=False, description='Group memberships.'),
'favorite_recorders': fields.List( 'favorite_recorders': fields.List(
fields.Nested(api_user.model('favorite_recorder', {'id': fields.Integer(), 'name': fields.String()})), fields.Nested(api_user.model('favorite_recorder',
{'id': fields.Integer(), 'name': fields.String(), 'offline': fields.Boolean(),
'created_at': fields.DateTime(), 'last_time_modified': fields.DateTime()})),
required=False, description='Favorite recorders.'), required=False, description='Favorite recorders.'),
}) })

View File

@@ -0,0 +1,91 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
from flask_jwt_extended import jwt_required
from flask_restplus import fields, Resource
from backend import db
from backend.api import api_permissions
from backend.models.user_model import Permission
permission_model = api_permissions.model('Permission', {
'id': fields.String(required=False, description='The permission\'s identifier'),
'name': fields.String(required=True, description='The permission\'s name'),
'description': fields.String(required=False, description='The permission\'s description'),
'groups': fields.List(fields.Nested(api_permissions.model('group_member',
{'id': fields.Integer(),
'name': fields.String(),
'description': fields.String()})),
required=False, description='Groups having the permission.'),
'access_control_entry': fields.Nested(api_permissions.model('group_member',
{'id': fields.Integer(),
'name': fields.String(),
'url': fields.String()}),
required=False, description="Access Control Entry"),
})
@api_permissions.route('/<int:id>')
@api_permissions.response(404, 'permission not found')
@api_permissions.param('id', 'The permission identifier')
class PermissionResource(Resource):
@jwt_required
@api_permissions.doc('get_permission')
@api_permissions.marshal_with(permission_model)
def get(self, id):
"""Fetch a user given its identifier"""
permission = Permission.get_by_id(id)
if permission is not None:
return permission
api_permissions.abort(404)
@jwt_required
@api_permissions.doc('delete_permission')
@api_permissions.response(204, 'permission deleted')
def delete(self, id):
"""Delete a permission given its identifier"""
permission = Permission.get_by_id(id)
if permission is not None:
permission.delete()
return '', 204
api_permissions.abort(404)
@jwt_required
@api_permissions.doc('update_permission')
@api_permissions.expect(permission_model)
@api_permissions.marshal_with(permission_model)
def put(self, id):
"""Update a task given its identifier"""
permission = Permission.get_by_id(id)
if permission is not None:
permission.name = api_permissions["name"]
db.session.commit()
return permission
api_permissions.abort(404)
@api_permissions.route('')
class PermissionList(Resource):
@jwt_required
@api_permissions.doc('permissions')
@api_permissions.marshal_list_with(permission_model)
def get(self):
"""
List all permissions
:return: permissions
"""
return Permission.get_all()
@jwt_required
@api_permissions.doc('create_permission')
@api_permissions.expect(permission_model)
@api_permissions.marshal_with(permission_model, code=201)
def post(self):
permission = Permission(**api_permissions.payload)
db.session.add(permission)
db.session.commit()
return permission

View File

@@ -70,7 +70,7 @@ class UserFavoriteRecorders(Resource):
args = generic_id_parser.parse_args() args = generic_id_parser.parse_args()
current_user_id = get_jwt_identity() current_user_id = get_jwt_identity()
user = User.get_by_identifier(current_user_id) user = User.get_by_identifier(current_user_id)
print(user) print(args)
recorder = Recorder.get_by_identifier(args["id"]) recorder = Recorder.get_by_identifier(args["id"])
print(recorder) print(recorder)
if recorder is None: if recorder is None:

Binary file not shown.

View File

@@ -1,7 +1,76 @@
import logging import logging
from multiprocessing.pool import ThreadPool
from threading import Lock
from typing import Union
cron_log_handler = logging.FileHandler(CRON_LOG_FILE) from backend import app, LrcException
from backend.models import Recorder
from backend.tools.simple_state_checker import check_capture_agent_state, ping_capture_agent
cron_log_handler = logging.FileHandler(app.config.get('CRON_LOG_FILE'))
cron_logger = logging.getLogger("mal.cron") cron_logger = logging.getLogger("mal.cron")
cron_logger.addHandler(cron_log_handler) cron_logger.addHandler(cron_log_handler)
logging.getLogger("apscheduler.scheduler").addHandler(cron_log_handler) logging.getLogger("apscheduler.scheduler").addHandler(cron_log_handler)
logging.getLogger("apscheduler.executors.default").addHandler(cron_log_handler) logging.getLogger("apscheduler.executors.default").addHandler(cron_log_handler)
recorder_jobs_lock = Lock()
recorder_jobs = set()
NUM_THREADS = 8
def add_recorder_to_state_check(recorder: Union[int, Recorder]):
if isinstance(recorder, int):
recorder = Recorder.get_by_identifier(recorder)
if recorder is None:
cron_logger.warning(
"Could not add recorder to state check, as specified id could not be found / recorder is None")
raise LrcException("Recorder is None / could not be found!")
recorder_jobs_lock.acquire()
recorder_jobs.add(recorder)
recorder_jobs_lock.release()
def remove_recorder_from_state_check(recorder: Union[int, Recorder]):
if isinstance(recorder, int):
recorder = Recorder.get_by_identifier(recorder)
if recorder is None:
cron_logger.warning(
"Could not remove recorder from state check, as specified id could not be found / recorder is None")
raise LrcException("Recorder is None / could not be found (and therefor not removed)!")
recorder_jobs_lock.acquire()
recorder_jobs.remove(recorder)
recorder_jobs_lock.release()
def check_recorder_state():
recorder_jobs_lock.acquire()
recorders = list(recorder_jobs)
recorder_jobs_lock.release()
recorder_states = {r['name']: {'state_ok': False, 'msg': 'unknown state!'} for r in recorders}
with ThreadPool(NUM_THREADS) as pool:
results = [pool.apply_async(check_capture_agent_state, (recorder,)) for recorder in recorders]
try:
state_results = [res.get(timeout=12) for res in results]
except TimeoutError as e:
cron_logger.error("Timeout while getting capture agent state! {}".format(e))
for r in state_results:
if r[0]: # ok :)
recorder_states[r[2]] = {'state_ok': True}
else:
recorder_states[r[2]]['msg'] = r[1]
with ThreadPool(NUM_THREADS) as pool:
results = [pool.apply_async(ping_capture_agent, (recorder,)) for recorder in recorders]
try:
ping_results = [res.get(timeout=12) for res in results]
except TimeoutError as e:
cron_logger.error("Timeout while pinging capture agent! {}".format(e))
for r in ping_results:
if not r[0]: # ok :)
recorder_states[r[2]]['msg'] = r[1]

View File

@@ -486,6 +486,22 @@ class Permission(db.Model):
back_populates='permissions') back_populates='permissions')
access_control_entry = db.relationship('AccessControlEntry', back_populates='required_permission') access_control_entry = db.relationship('AccessControlEntry', back_populates='required_permission')
@staticmethod
def get_by_name(name):
"""
Find permission by name
:param name:
:return:
"""
return Permission.query.filter(Permission.name == name).first()
@staticmethod
def get_all():
"""
Return all permissions
:return:
"""
return Permission.query.all()
@event.listens_for(User.__table__, 'after_create') @event.listens_for(User.__table__, 'after_create')
def insert_initial_users(*args, **kwargs): def insert_initial_users(*args, **kwargs):

View File

@@ -106,6 +106,7 @@ def get_recorder_adapter(recorder_info: dict) -> RecorderAdapter:
def check_capture_agent_state(a: dict): def check_capture_agent_state(a: dict):
agent_state_error_msg = None
logger.debug("Checking Agent {}".format(a['name'])) logger.debug("Checking Agent {}".format(a['name']))
c = get_calender(a['name']) c = get_calender(a['name'])
is_recording_in_calendar = len(list(c.timeline.now())) >= 1 is_recording_in_calendar = len(list(c.timeline.now())) >= 1
@@ -122,6 +123,7 @@ def check_capture_agent_state(a: dict):
else: else:
logger.info(rec.get_recording_status()) logger.info(rec.get_recording_status())
logger.error("FATAL - recorder {} must be recording but is not!!!!".format(a['name'])) logger.error("FATAL - recorder {} must be recording but is not!!!!".format(a['name']))
agent_state_error_msg = "FATAL - recorder must be recording but is not!"
with agent_states_lock: with agent_states_lock:
agent_states[a['name']] = 'FATAL - recorder is NOT recording, but should!' agent_states[a['name']] = 'FATAL - recorder is NOT recording, but should!'
except LrcException as e: except LrcException as e:
@@ -129,12 +131,14 @@ def check_capture_agent_state(a: dict):
logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip'])) logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip']))
else: else:
logger.error("FATAL: {} is not in capturing state...but should be!!".format(a['name'])) logger.error("FATAL: {} is not in capturing state...but should be!!".format(a['name']))
agent_state_error_msg = "FATAL - is not in capturing state...but should be!"
else: else:
recorder_info = get_recorder_by_name(a['name']) recorder_info = get_recorder_by_name(a['name'])
try: try:
rec = get_recorder_adapter(recorder_info) rec = get_recorder_adapter(recorder_info)
if rec.is_recording(): if rec.is_recording():
logger.error("FATAL - recorder must not be recording!!!!") logger.error("FATAL - recorder must not be recording!!!!")
agent_state_error_msg = "FATAL - is not in capturing state...but should be!"
with agent_states_lock: with agent_states_lock:
agent_states[a['name']] = 'FATAL - recorder IS recording, but should NOT!' agent_states[a['name']] = 'FATAL - recorder IS recording, but should NOT!'
else: else:
@@ -144,6 +148,11 @@ def check_capture_agent_state(a: dict):
except LrcException as e: except LrcException as e:
logger.fatal("Exception occurred: {}".format(str(e))) logger.fatal("Exception occurred: {}".format(str(e)))
logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip'])) logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip']))
agent_state_error_msg = "FATAL - Could not check state of recorder! Address: {}".format(recorder_info['ip'])
if agent_state_error_msg is None:
return True, "", a['name']
return False, agent_state_error_msg, a['name']
def ping_capture_agent(a: dict): def ping_capture_agent(a: dict):
@@ -157,8 +166,10 @@ def ping_capture_agent(a: dict):
universal_newlines=True # return string not bytes universal_newlines=True # return string not bytes
) )
logger.info("Successfully pinged {} ({}). :-)".format(a['name'], recorder_ip)) logger.info("Successfully pinged {} ({}). :-)".format(a['name'], recorder_ip))
return True, "", a['name']
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
logger.error("Can not ping {} ({})!!".format(a['name'], recorder_ip)) logger.error("Can not ping {} ({})!!".format(a['name'], recorder_ip))
return False, "Unable to ping", a['name']
agents = get_capture_agents() agents = get_capture_agents()

View File

@@ -1,6 +1,7 @@
import logging import logging
import threading import threading
from flask_jwt_extended import verify_jwt_in_request, get_current_user, jwt_required, get_jwt_claims, get_jwt_identity
from flask_login import current_user from flask_login import current_user
from flask_socketio import SocketIO, emit from flask_socketio import SocketIO, emit
@@ -35,17 +36,16 @@ class WebSocketBase:
@socketio.on('connect') @socketio.on('connect')
def connect_handler(): def connect_handler():
logger.debug("new connection...") logger.debug("new connection...")
print(current_user) try:
if current_user.is_authenticated: print(verify_jwt_in_request())
logger.debug("user is authenticated") print(get_jwt_identity())
print("allowed!") except:
emit('my response',
{'message': '{0} has joined'.format(current_user.name)},
broadcast=True)
else:
logger.info("user is not authenticated!") logger.info("user is not authenticated!")
print("not allowed!!") print("not allowed!!")
return False # not allowed here return False # not allowed here
logger.debug("user is authenticated")
print("allowed!")
return True
@socketio.on_error() @socketio.on_error()
def handle_error(self, error): def handle_error(self, error):
@@ -55,5 +55,5 @@ class WebSocketBase:
if __name__ == '__main__': if __name__ == '__main__':
wsb = WebSocketBase() wsb = WebSocketBase()
#wsb.start_websocket_in_thread(debug=True) # wsb.start_websocket_in_thread(debug=True)
wsb.start_websocket(debug=True) wsb.start_websocket(debug=True)

View File

View File

@@ -3,19 +3,23 @@
import logging import logging
import threading import threading
import time import time
from socketIO_client import SocketIO, LoggingNamespace
from flask import Flask
from flask_socketio import SocketIO, emit
from backend import app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
logging.basicConfig() logging.basicConfig()
token = "# replace with: JWT Access Token"
#print(token)
print("params")
#socketIO = SocketIO('127.0.0.1', 5443, params={'jwt': '{}'.format(token)})
print("headers")
#socketIO = SocketIO('127.0.0.1', 5443, headers={'Authorization': 'Bearer {}'.format(token)})
print("cookies")
socketIO = SocketIO('127.0.0.1', 5443, cookies={'access_token_cookie': '{}'.format(token)})
#socketio = SocketIO(message_queue="redis://") #socketio = SocketIO(message_queue="redis://")
socketio = SocketIO(app, port=5443, debug=True) socketio = SocketIO('127.0.0.1', 5443)
#socketio.run(app, host="localhost", port=5000) #socketio.run(app, host="localhost", port=5000)
#socketio.init_app(app, host="localhost", port=5000, cors_allowed_origins="*", ) #socketio.init_app(app, host="localhost", port=5000, cors_allowed_origins="*", )