From 46efe2b8ddf67cc8b6dad8f80e45a41db03dfadd Mon Sep 17 00:00:00 2001 From: Donald Cheng Hong Zou Date: Thu, 24 Mar 2022 02:10:52 -0400 Subject: [PATCH] Finished developing add config --- src/api.py | 95 ++++++++++++++- src/dashboard.py | 107 +++++++++-------- src/static/css/dashboard.css | 15 +++ src/static/js/index.js | 224 ++++++++++++++++++++++++++++------- src/templates/index.html | 70 +++++------ src/util.py | 5 +- 6 files changed, 381 insertions(+), 135 deletions(-) diff --git a/src/api.py b/src/api.py index 22696d7..ca15d70 100644 --- a/src/api.py +++ b/src/api.py @@ -1,2 +1,95 @@ -from dashboard import request, jsonify, app +import ipaddress, subprocess, datetime, os, util +from util import * +notEnoughParameter = {"status": False, "reason": "Please provide all required parameters."} +good = {"status": True, "reason": ""} +def togglePeerAccess(data, g): + checkUnlock = g.cur.execute(f"SELECT * FROM {data['config']} WHERE id='{data['peerID']}'").fetchone() + if checkUnlock: + moveUnlockToLock = g.cur.execute(f"INSERT INTO {data['config']}_restrict_access SELECT * FROM {data['config']} WHERE id = '{data['peerID']}'") + if g.cur.rowcount == 1: + print(g.cur.rowcount) + print(util.deletePeers(data['config'], [data['peerID']], g.cur, g.db)) + else: + moveLockToUnlock = g.cur.execute(f"SELECT * FROM {data['config']}_restrict_access WHERE id='{data['peerID']}'").fetchone() + try: + if len(moveLockToUnlock[-1]) == 0: + status = subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]}", + shell=True, stderr=subprocess.STDOUT) + else: + now = str(datetime.datetime.now().strftime("%m%d%Y%H%M%S")) + f_name = now + "_tmp_psk.txt" + f = open(f_name, "w+") + f.write(moveLockToUnlock[-1]) + f.close() + subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]} preshared-key {f_name}", + shell=True, stderr=subprocess.STDOUT) + os.remove(f_name) + status = subprocess.check_output(f"wg-quick save {data['config']}", shell=True, stderr=subprocess.STDOUT) + g.cur.execute(f"INSERT INTO {data['config']} SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'") + if g.cur.rowcount == 1: + g.cur.execute(f"DELETE FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'") + + except subprocess.CalledProcessError as exc: + return {"status": False, "reason": str(exc.output.strip())} + return good + + + +class addConfiguration: + def AddressCheck(data): + address = data['address'] + address = address.replace(" ", "") + address = address.split(',') + amount = 0 + for i in address: + try: + ips = ipaddress.ip_network(i, False) + amount += ips.num_addresses + except ValueError as e: + return {"status": False, "reason": str(e)} + if amount >= 1: + return {"status": True, "reason":"", "data":f"Total of {amount} IPs"} + else: + return {"status": True, "reason":"", "data":f"0 available IPs"} + + def PortCheck(data, configs): + port = data['port'] + if (not port.isdigit()) or int(port) < 1 or int(port) > 65535: + return {"status": False, "reason": f"Invalid port."} + for i in configs: + if i['port'] == port: + return {"status": False, "reason": f"{port} used by {i['conf']}."} + return good + + def NameCheck(data, configs): + name = data['name'] + name = name.replace(" ", "") + for i in configs: + if name == i['conf']: + return {"status": False, "reason":f"{name} already existed."} + return good + + def addConfiguration(data, configs, WG_CONF_PATH): + output = ["[Interface]", "SaveConfig = true"] + required = ['addConfigurationPrivateKey', 'addConfigurationListenPort', + 'addConfigurationAddress', 'addConfigurationPreUp', 'addConfigurationPreDown', + 'addConfigurationPostUp', 'addConfigurationPostDown'] + for i in required: + e = data[i] + if len(e) != 0: + key = i.replace("addConfiguration", "") + o = f"{key} = {e}" + output.append(o) + name = data['addConfigurationName'] + illegal_filename = [" ",".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3", + "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", + "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"] + for i in illegal_filename: + name = name.replace(i, "") + try: + newFile = open(f"{WG_CONF_PATH}/{name}.conf", "w+") + newFile.write("\n".join(output)) + except Exception as e: + return {"status": False, "reason":str(e)} + return {"status": True, "reason":"", "data": name} \ No newline at end of file diff --git a/src/dashboard.py b/src/dashboard.py index c934d0e..33671b9 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -31,7 +31,7 @@ from flask_socketio import SocketIO from util import * # Dashboard Version -DASHBOARD_VERSION = 'v3.0.5' +DASHBOARD_VERSION = 'v3.1' # WireGuard's configuration path WG_CONF_PATH = None @@ -58,6 +58,7 @@ socketio = SocketIO(app) # TODO: use class and object oriented programming + def connect_db(): """ Connect to the database @@ -521,7 +522,7 @@ def get_conf_list(): ) """ g.cur.execute(create_table) - temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)} + temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i), "port": get_conf_listen_port(i)} if temp['status'] == "running": temp['checked'] = 'checked' else: @@ -731,6 +732,7 @@ def auth(): if password.hexdigest() == config["Account"]["password"] \ and data['username'] == config["Account"]["username"]: session['username'] = data['username'] + session.permanent = True config.clear() return jsonify({"status": True, "msg": ""}) config.clear() @@ -1107,15 +1109,15 @@ def switch(config_name): check = subprocess.check_output("wg-quick down " + config_name, shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: - session["switch_msg"] = exc.output.strip().decode("utf-8") - return jsonify({"status": False, "reason":"Can't stop peer"}) + # session["switch_msg"] = exc.output.strip().decode("utf-8") + return jsonify({"status": False, "reason":"Can't stop peer", "message": str(exc.output.strip().decode("utf-8"))}) elif status == "stopped": try: subprocess.check_output("wg-quick up " + config_name, shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: - session["switch_msg"] = exc.output.strip().decode("utf-8") - return jsonify({"status": False, "reason":"Can't turn on peer"}) + # session["switch_msg"] = exc.output.strip().decode("utf-8") + return jsonify({"status": False, "reason":"Can't turn on peer", "message": str(exc.output.strip().decode("utf-8"))}) return jsonify({"status": True, "reason":""}) @app.route('/add_peer_bulk/', methods=['POST']) @@ -1534,45 +1536,18 @@ def switch_display_mode(mode): # APIs +import api + + @app.route('/api/togglePeerAccess', methods=['POST']) def togglePeerAccess(): data = request.get_json() returnData = {"status": True, "reason": ""} required = ['peerID', 'config'] if checkJSONAllParameter(required, data): - checkUnlock = g.cur.execute(f"SELECT * FROM {data['config']} WHERE id='{data['peerID']}'").fetchone() - if checkUnlock: - moveUnlockToLock = g.cur.execute(f"INSERT INTO {data['config']}_restrict_access SELECT * FROM {data['config']} WHERE id = '{data['peerID']}'") - if g.cur.rowcount == 1: - print(g.cur.rowcount) - print(deletePeers(data['config'], [data['peerID']], g.cur, g.db)) - else: - moveLockToUnlock = g.cur.execute(f"SELECT * FROM {data['config']}_restrict_access WHERE id='{data['peerID']}'").fetchone() - try: - if len(moveLockToUnlock[-1]) == 0: - status = subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]}", - shell=True, stderr=subprocess.STDOUT) - else: - now = str(datetime.now().strftime("%m%d%Y%H%M%S")) - f_name = now + "_tmp_psk.txt" - f = open(f_name, "w+") - f.write(moveLockToUnlock[-1]) - f.close() - subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]} preshared-key {f_name}", - shell=True, stderr=subprocess.STDOUT) - os.remove(f_name) - status = subprocess.check_output(f"wg-quick save {data['config']}", shell=True, stderr=subprocess.STDOUT) - g.cur.execute(f"INSERT INTO {data['config']} SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'") - if g.cur.rowcount == 1: - g.cur.execute(f"DELETE FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'") - - except subprocess.CalledProcessError as exc: - returnData["status"] = False - returnData["reason"] = exc.output.strip() + returnData = api.togglePeerAccess(data, g) else: - returnData["status"] = False - returnData["reason"] = "Please provide all required parameters." - + return jsonify(api.notEnoughParameter) return jsonify(returnData) @app.route('/api/addConfigurationAddressCheck', methods=['POST']) @@ -1581,20 +1556,52 @@ def addConfigurationAddressCheck(): returnData = {"status": True, "reason": ""} required = ['address'] if checkJSONAllParameter(required, data): - try: - ips = list(ipaddress.ip_network(data['address'], False).hosts()) - amount = len(ips) - 1 - if amount >= 1: - returnData = {"status": True, "reason":"", "data":f"Total of {amount} IPs"} - else: - returnData = {"status": True, "reason":"", "data":f"0 IP available for peers"} - - except ValueError as e: - returnData = {"status": False, "reason": str(e)} + returnData = api.addConfiguration.AddressCheck(data) else: - returnData = {"status": False, "reason": "Please provide all required parameters."} + return jsonify(api.notEnoughParameter) return jsonify(returnData) - + +@app.route('/api/addConfigurationPortCheck', methods=['POST']) +def addConfigurationPortCheck(): + data = request.get_json() + returnData = {"status": True, "reason": ""} + required = ['port'] + if checkJSONAllParameter(required, data): + returnData = api.addConfiguration.PortCheck(data, get_conf_list()) + else: + return jsonify(api.notEnoughParameter) + return jsonify(returnData) + +@app.route('/api/addConfigurationNameCheck', methods=['POST']) +def addConfigurationNameCheck(): + data = request.get_json() + returnData = {"status": True, "reason": ""} + required = ['name'] + if checkJSONAllParameter(required, data): + returnData = api.addConfiguration.NameCheck(data, get_conf_list()) + else: + return jsonify(api.notEnoughParameter) + return jsonify(returnData) + +@app.route('/api/addConfiguration', methods=["POST"]) +def addConfiguration(): + data = request.get_json() + returnData = {"status": True, "reason": ""} + required = ['addConfigurationPrivateKey', 'addConfigurationName', 'addConfigurationListenPort', + 'addConfigurationAddress', 'addConfigurationPreUp', 'addConfigurationPreDown', + 'addConfigurationPostUp', 'addConfigurationPostDown'] + needFilled = ['addConfigurationPrivateKey', 'addConfigurationName', 'addConfigurationListenPort', + 'addConfigurationAddress'] + if not checkJSONAllParameter(needFilled, data): + return jsonify(api.notEnoughParameter) + for i in required: + if i not in data.keys(): + return jsonify(api.notEnoughParameter) + + returnData = api.addConfiguration.addConfiguration(data, get_conf_list(), WG_CONF_PATH) + return jsonify(returnData) + + """ Dashboard Tools Related diff --git a/src/static/css/dashboard.css b/src/static/css/dashboard.css index 1db3560..39ce07c 100644 --- a/src/static/css/dashboard.css +++ b/src/static/css/dashboard.css @@ -772,4 +772,19 @@ pre.index-alert { .input-feedback{ display: none; +} + +#addConfigurationModal label{ + display: flex; + width: 100%; + align-items: center; +} + +#addConfigurationModal label a{ + margin-left: auto !important; +} + +#reGeneratePrivateKey{ + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; } \ No newline at end of file diff --git a/src/static/js/index.js b/src/static/js/index.js index 2f23bf7..6715193 100644 --- a/src/static/js/index.js +++ b/src/static/js/index.js @@ -1,9 +1,22 @@ let numberToast = 0; -let addConfigurationModal = new bootstrap.Modal(document.getElementById('addConfigurationModal'), { +let emptyInputFeedback = "Can't leave empty"; +$('[data-toggle="tooltip"]').tooltip() +let $add_configuration = $("#add_configuration"); + +let addConfigurationModal = $("#addConfigurationModal"); + +addConfigurationModal.modal({ keyboard: false, - backdrop: 'static' + backdrop: 'static', + show: false }); +addConfigurationModal.on("hidden.bs.modal", function(){ + $("#add_configuration_form").trigger("reset"); + $("#add_configuration_form input").removeClass("is-valid").removeClass("is-invalid"); + $(".addConfigurationAvailableIPs").text("N/A"); +}) + function showToast(msg){ $(".toastContainer").append( ` - - +
+ {%endfor%} @@ -84,53 +82,49 @@
-
+

+					
                         
- +
- +
-
-
-
-
- -
-
- +
- - + + +
- - + + +
- - -
- Please provide a valid city. -
+ + +
- +

N/A

@@ -139,28 +133,26 @@
- +
- +
- +
- +
- - -
diff --git a/src/util.py b/src/util.py index ae6f94a..c37edd9 100644 --- a/src/util.py +++ b/src/util.py @@ -104,9 +104,10 @@ def deletePeers(config_name, delete_keys, cur, db): def checkJSONAllParameter(required, data): if len(data) == 0: - print("length 0") return False for i in required: if i not in list(data.keys()): return False - return True \ No newline at end of file + return True + +