From 13ea9141a439880f69e8474a35dfb408c437d3b9 Mon Sep 17 00:00:00 2001 From: Joshua Derner Date: Thu, 15 Aug 2019 08:58:18 -0400 Subject: [PATCH] Admin user table, admin logging, multi user admin --- dsiprouter.sh | 6 ++- gui/database/__init__.py | 15 +++++++ gui/dsiprouter.py | 76 ++++++++++++++++++++++++++++------- gui/settings.py | 5 ++- kamailio/admin/dsip_admin.sql | 7 ++++ 5 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 kamailio/admin/dsip_admin.sql diff --git a/dsiprouter.sh b/dsiprouter.sh index 5b9065cf..3e0746bf 100755 --- a/dsiprouter.sh +++ b/dsiprouter.sh @@ -72,6 +72,7 @@ export IPV6_ENABLED=0 export DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" export DSIP_KAMAILIO_CONFIG_DIR="${DSIP_PROJECT_DIR}/kamailio" export DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio51_dsiprouter.cfg" +export DSIP_ADMIN_DIR="${DSIP_KAMAILIO_CONFIG_DIR}/admin" export DSIP_DEFAULTS_DIR="${DSIP_KAMAILIO_CONFIG_DIR}/defaults" export DSIP_CONFIG_FILE="${DSIP_PROJECT_DIR}/gui/settings.py" export DSIP_RUN_DIR="/var/run/dsiprouter" @@ -209,7 +210,7 @@ ZXIKClRoYW5rcyB0byBvdXIgc3BvbnNvcjogU2t5ZXRlbCAoc2t5ZXRlbC5jb20pCg==" \ # Cleanup exported variables on exit function cleanupAndExit { - unset DSIP_PROJECT_DIR DSIP_INSTALL_DIR DSIP_KAMAILIO_CONFIG_DIR DSIP_KAMAILIO_CONFIG DSIP_DEFAULTS_DIR SYSTEM_KAMAILIO_CONFIG_DIR DSIP_CONFIG_FILE + unset DSIP_PROJECT_DIR DSIP_INSTALL_DIR DSIP_KAMAILIO_CONFIG_DIR DSIP_KAMAILIO_CONFIG DSIP_ADMIN_DIR DSIP_DEFAULTS_DIR SYSTEM_KAMAILIO_CONFIG_DIR DSIP_CONFIG_FILE unset REQ_PYTHON_MAJOR_VER DISTRO DISTRO_VER PYTHON_CMD AWS_ENABLED PATH_UPDATE_FILE SYSTEM_RTPENGINE_CONFIG_DIR SYSTEM_RTPENGINE_CONFIG_FILE SERVERNAT unset RTPENGINE_VER SRC_DIR DSIP_SYSTEM_CONFIG_DIR BACKUPS_DIR DSIP_RUN_DIR KAM_VERSION CLOUD_INSTALL_LOG DEBUG IPV6_ENABLED unset MYSQL_ROOT_PASSWORD MYSQL_ROOT_USERNAME MYSQL_ROOT_DATABASE MYSQL_KAM_PASSWORD MYSQL_KAM_USERNAME MYSQL_KAM_DATABASE @@ -521,6 +522,9 @@ function configureKamailio { mysql --user="$MYSQL_KAM_USERNAME" --password="$MYSQL_KAM_PASSWORD" $MYSQL_KAM_DATABASE < $sqlscript fi + # Install schema for admin users table + mysql -s -N --user="$MYSQL_ROOT_USERNAME" --password="$MYSQL_ROOT_PASSWORD" $MYSQL_KAM_DATABASE < ${DSIP_ADMIN_DIR}/dsip_admin.sql + # Install schema for custom LCR logic mysql -s -N --user="$MYSQL_ROOT_USERNAME" --password="$MYSQL_ROOT_PASSWORD" $MYSQL_KAM_DATABASE < ${DSIP_DEFAULTS_DIR}/lcr.sql diff --git a/gui/database/__init__.py b/gui/database/__init__.py index 5ecd9934..cd804ef4 100644 --- a/gui/database/__init__.py +++ b/gui/database/__init__.py @@ -90,6 +90,19 @@ def __init__(self, groupid, prefix, gwlist, notes=''): pass +class AdminUsers(object): + """ + Schema for admin_users\n + """ + + def __init__(self, admin_id, admin_username, admin_hash): + self.admin_id = admin_id + self.admin_username = admin_username + self.admin_hash = admin_hash + + pass + + class OutboundRoutes(object): """ Schema for dr_rules table\n @@ -404,6 +417,7 @@ def loadSession(): dr_gateways = Table('dr_gateways', metadata, autoload=True) address = Table('address', metadata, autoload=True) + admin_users = Table('admin_users', metadata, autoload=True) outboundroutes = Table('dr_rules', metadata, autoload=True) inboundmapping = Table('dr_rules', metadata, autoload=True) subscriber = Table('subscriber', metadata, autoload=True) @@ -431,6 +445,7 @@ def loadSession(): mapper(Gateways, dr_gateways) mapper(Address, address) + mapper(AdminUsers, admin_users) mapper(InboundMapping, inboundmapping) mapper(OutboundRoutes, outboundroutes) mapper(dSIPDomainMapping, dsip_domain_mapping) diff --git a/gui/dsiprouter.py b/gui/dsiprouter.py index 90092df4..42a17a8a 100755 --- a/gui/dsiprouter.py +++ b/gui/dsiprouter.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 -import os, re, json, subprocess, urllib.parse, glob,datetime, csv, logging, signal +import os, re, json, subprocess, urllib.parse, glob,datetime, csv, logging, signal, hashlib +import logging.handlers from copy import copy from flask import Flask, render_template, request, redirect, abort, flash, session, url_for, send_from_directory, g from flask_script import Manager, Server @@ -13,7 +14,7 @@ from shared import getInternalIP, getExternalIP, updateConfig, getCustomRoutes, debugException, debugEndpoint, \ stripDictVals, strFieldsToDict, dictToStrFields, allowed_file, showError, hostToIP, IO, status from database import loadSession, Gateways, Address, InboundMapping, OutboundRoutes, Subscribers, dSIPLCR, \ - UAC, GatewayGroups, Domain, DomainAttrs, dSIPDomainMapping, dSIPMultiDomainMapping, Dispatcher, dSIPMaintModes + UAC, GatewayGroups, AdminUsers, Domain, DomainAttrs, dSIPDomainMapping, dSIPMultiDomainMapping, Dispatcher, dSIPMaintModes from modules import flowroute from modules.domain.domain_routes import domains import globals @@ -27,7 +28,6 @@ db = loadSession() numbers_api = flowroute.Numbers() - # TODO: unit testing per component @app.before_first_request @@ -74,13 +74,11 @@ def index(): error = "server" return showError(type=error) - @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') - - + @app.route('/login', methods=['POST']) def login(): try: @@ -88,13 +86,26 @@ def login(): debugEndpoint() form = stripDictVals(request.form.to_dict()) + + txtUser = form['username'] + txtPassword = form['password'] + txtPasswordHash = hashlib.md5(txtPassword.encode('utf-8')).hexdigest() - if form['password'] == settings.PASSWORD and form['username'] == settings.USERNAME: + validAdmin = db.query(AdminUsers).filter( + AdminUsers.admin_username == txtUser, + AdminUsers.admin_hash == txtPasswordHash + ).count() + + logAdmin = initiateLogger() + if validAdmin == 1: session['logged_in'] = True - session['username'] = form['username'] + session['username'] = txtUser + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Login successful') else: - flash('wrong username or password!') + flash('There seems to be a problem. Contact technical support.') + logAdmin.debug(settings.LOG_TAG + txtUser + ' - Login failure') return render_template('index.html', version=settings.VERSION), 403 + return redirect(url_for('index')) @@ -113,6 +124,9 @@ def logout(): try: if (settings.DEBUG): debugEndpoint() + + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + ' logout ' + session['username']) session.pop('logged_in', None) session.pop('username', None) @@ -156,6 +170,9 @@ def displayCarrierGroups(gwgroup=None): GatewayGroups.id, GatewayGroups.gwlist, GatewayGroups.description, UAC.auth_username, UAC.auth_password, UAC.realm).all() db.close() + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Display Carriers') + return render_template('carriergroups.html', rows=res, API_URL=request.url_root) except sql_exceptions.SQLAlchemyError as ex: @@ -221,7 +238,7 @@ def addUpdateCarrierGroups(): Uacreg = UAC(gwgroup, auth_username, auth_password, auth_domain, auth_proxy, settings.EXTERNAL_IP_ADDR, auth_domain) #Add auth_domain(aka registration server) to the gateway list - Addr = Address(name + "-uac", hostToIP(auth_domain), 32, str(settings.FLT_CARRIER), gwgroup=str(gwgroup)) + Addr = Address(name + "-uac", hostToIP(auth_domain), 32, settings.FLT_CARRIER, gwgroup=gwgroup) db.add(Uacreg) db.add(Addr) @@ -1025,6 +1042,8 @@ def displayInboundMapping(): debugException(ex, log_ex=False, print_ex=True, showstack=False) return showError(type="http", code=ex.status_code, msg="Flowroute Credentials Not Valid") + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Display Inbound Mappings') return render_template('inboundmapping.html', rows=res, gwlist=gateways, imported_dids=dids) except sql_exceptions.SQLAlchemyError as ex: @@ -1089,7 +1108,7 @@ def addUpdateInboundMapping(): gateways.append(gw) gwlist = ','.join(gateways) - + logMsg = '' # Adding if not ruleid: # don't allow duplicate entries @@ -1097,13 +1116,16 @@ def addUpdateInboundMapping(): raise http_exceptions.BadRequest("Duplicate DID's are not allowed") IMap = InboundMapping(settings.FLT_INBOUND, prefix, gwlist, notes) db.add(IMap) - + logMsg = 'Added inbound rule: ' + str(prefix) # Updating else: db.query(InboundMapping).filter(InboundMapping.ruleid == ruleid).update( {'prefix': prefix, 'gwlist': gwlist, 'description': notes}, synchronize_session=False) - + logMsg = 'Updated inbound rule: ' + str(prefix) db.commit() + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - ' + logMsg) + globals.reload_required = True return displayInboundMapping() @@ -1154,6 +1176,9 @@ def deleteInboundMapping(): d = db.query(InboundMapping).filter(InboundMapping.ruleid == ruleid) d.delete(synchronize_session=False) db.commit() + + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Deleted rule id: ' + str(ruleid)) globals.reload_required = True return displayInboundMapping() @@ -1197,6 +1222,9 @@ def processInboundMappingImport(filename,groupid,pbxid,notes,db): IMap = InboundMapping(groupid, row[0], pbxid, notes) db.add(IMap) + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Imported DID\'s') + db.commit() @@ -1409,6 +1437,9 @@ def displayOutboundRoutes(): teleblock["media_ip"] = settings.TELEBLOCK_MEDIA_IP teleblock["media_port"] = settings.TELEBLOCK_MEDIA_PORT + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Display global outbound routes') + return render_template('outboundroutes.html', rows=rows, teleblock=teleblock, custom_routes='') except sql_exceptions.SQLAlchemyError as ex: @@ -1709,13 +1740,18 @@ def reloadkam(): ['kamcmd', 'cfg.seti', 'teleblock', 'media_port', str(settings.TELEBLOCK_MEDIA_PORT)]) session['last_page'] = request.headers['Referer'] - + + status_code = 100 if return_code == 0: status_code = 1 globals.reload_required = False else: status_code = 0 globals.reload_required = prev_reload_val + + logAdmin = initiateLogger() + logAdmin.debug(settings.LOG_TAG + session['username'] + ' - Reloaded Kamailio with return status of ' + str(status_code)) + return json.dumps({"status": status_code}) except http_exceptions.HTTPException as ex: @@ -1884,5 +1920,17 @@ def checkDatabase(): # If not, close DB connection so that the SQL engine can get another one from the pool db.close() +def initiateLogger(): + + logger = logging.getLogger('adminLog') + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + handler.setFormatter(formatter) + if not len(logger.handlers): + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) + return logger + if __name__ == "__main__": initApp(app) diff --git a/gui/settings.py b/gui/settings.py index 5be06e51..f5f913f4 100644 --- a/gui/settings.py +++ b/gui/settings.py @@ -4,8 +4,6 @@ DSIP_PROTO = 'http' DSIP_HOST = '0.0.0.0' DSIP_PORT = 5000 -USERNAME = 'admin' -PASSWORD = 'ZmJhM2Y1ODAwNDhl' DSIP_API_TOKEN = 'neggz1jEn5ei0ZICsNdWSHv9Obdq7VJJukaLp8smlOaZm49NCrHX72MabKCBixLx' DSIP_API_HOST = '' @@ -91,3 +89,6 @@ # AWS = Amazon Web Services, GCP = Google Cloud Platform, AZURE = Microsoft Azure, DO = Digital Ocean CLOUD_PLATFORM = '' + +# Log tagging +LOG_TAG = '[dsip gui]: ' \ No newline at end of file diff --git a/kamailio/admin/dsip_admin.sql b/kamailio/admin/dsip_admin.sql new file mode 100644 index 00000000..1d02221a --- /dev/null +++ b/kamailio/admin/dsip_admin.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS `admin_users`; +CREATE TABLE `admin_users` ( + `admin_id` int NOT NULL AUTO_INCREMENT, + `admin_username` varchar(32) NOT NULL, + `admin_hash` varchar(32) NOT NULL, + PRIMARY KEY (`admin_id`) +); \ No newline at end of file