mirror of https://github.com/D4-project/d4-core
chg: [UI] add user management
parent
113159f820
commit
c8d2b8cb95
|
@ -7,12 +7,35 @@
|
|||
D4 core server is a complete server to handle clients (sensors) including the decapsulation of the [D4 protocol](https://github.com/D4-project/architecture/tree/master/format), control of
|
||||
sensor registrations, management of decoding protocols and dispatching to adequate decoders/analysers.
|
||||
|
||||
## Database map
|
||||
## Database map - Metadata
|
||||
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| | |
|
||||
| | | |
|
||||
```
|
||||
DB 0 - Stats + sensor configs
|
||||
DB 1 - Users
|
||||
DB 2 - Analyzer queue
|
||||
```
|
||||
|
||||
### DB 1
|
||||
|
||||
##### User Management:
|
||||
| Hset Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| user:all | **user id** | **password hash** |
|
||||
| | | |
|
||||
| user:tokens | **token** | **user id** |
|
||||
| | | |
|
||||
| user_metadata:**user id** | token | **token** |
|
||||
| | change_passwd | **boolean** |
|
||||
| | role | **role** |
|
||||
|
||||
| Set Key | Value |
|
||||
| ------ | ------ |
|
||||
| user_role:**role** | **user id** |
|
||||
|
||||
|
||||
| Zrank Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| ail:all_role | **role** | **int, role priority (1=admin)** |
|
||||
|
||||
### Server
|
||||
| Key | Value |
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import redis
|
||||
import bcrypt
|
||||
|
||||
from flask_login import UserMixin
|
||||
|
||||
class User(UserMixin):
|
||||
|
||||
def __init__(self, id):
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
self.r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
if self.r_serv_db.hexists('user:all', id):
|
||||
self.id = id
|
||||
else:
|
||||
self.id = "__anonymous__"
|
||||
|
||||
# return True or False
|
||||
#def is_authenticated():
|
||||
|
||||
# return True or False
|
||||
#def is_anonymous():
|
||||
|
||||
@classmethod
|
||||
def get(self_class, id):
|
||||
return self_class(id)
|
||||
|
||||
def user_is_anonymous(self):
|
||||
if self.id == "__anonymous__":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_password(self, password):
|
||||
if self.user_is_anonymous():
|
||||
return False
|
||||
|
||||
password = password.encode()
|
||||
hashed_password = self.r_serv_db.hget('user:all', self.id).encode()
|
||||
if bcrypt.checkpw(password, hashed_password):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def request_password_change(self):
|
||||
if self.r_serv_db.hget('user_metadata:{}'.format(self.id), 'change_passwd') == 'True':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_in_role(self, role):
|
||||
if self.r_serv_db.sismember('user_role:{}'.format(role), self.id):
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -1,6 +1,8 @@
|
|||
twisted[tls]
|
||||
redis
|
||||
flask
|
||||
flask-login
|
||||
bcrypt
|
||||
|
||||
#sudo python3 -m pip install --upgrade service_identity
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import redis
|
||||
import time
|
||||
import uuid
|
||||
import flask
|
||||
import redis
|
||||
import random
|
||||
import datetime
|
||||
import ipaddress
|
||||
import configparser
|
||||
|
@ -16,6 +18,17 @@ import configparser
|
|||
import subprocess
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
|
||||
|
||||
import bcrypt
|
||||
|
||||
# Import Role_Manager
|
||||
from Role_Manager import create_user_db, check_password_strength, check_user_role_integrity
|
||||
from Role_Manager import login_admin, login_analyst
|
||||
|
||||
sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib'))
|
||||
|
||||
from User import User
|
||||
|
||||
baseUrl = ''
|
||||
if baseUrl != '':
|
||||
|
@ -59,6 +72,12 @@ redis_server_metadata = redis.StrictRedis(
|
|||
db=0,
|
||||
decode_responses=True)
|
||||
|
||||
redis_users = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
redis_server_analyzer = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
|
@ -71,9 +90,31 @@ json_type_description = {}
|
|||
for type_info in json_type:
|
||||
json_type_description[type_info['type']] = type_info
|
||||
|
||||
Flask_dir = os.path.join(os.environ['D4_HOME'], 'web')
|
||||
|
||||
# ========= TLS =========#
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.load_cert_chain(certfile=os.path.join(Flask_dir, 'server.crt'), keyfile=os.path.join(Flask_dir, 'server.key'))
|
||||
#print(ssl_context.get_ciphers())
|
||||
# ========= =========#
|
||||
|
||||
app = Flask(__name__, static_url_path=baseUrl+'/static/')
|
||||
app.config['MAX_CONTENT_LENGTH'] = 900 * 1024 * 1024
|
||||
|
||||
# ========= session ========
|
||||
app.secret_key = str(random.getrandbits(256))
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'login'
|
||||
login_manager.init_app(app)
|
||||
# ========= =========#
|
||||
|
||||
# ========= LOGIN MANAGER ========
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.get(user_id)
|
||||
# ========= =========#
|
||||
|
||||
# ========== FUNCTIONS ============
|
||||
def is_valid_uuid_v4(header_uuid):
|
||||
try:
|
||||
|
@ -195,19 +236,137 @@ def get_uuid_disk_statistics(uuid_name, date_day='', type='', all_types_on_disk=
|
|||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
# API - JSON
|
||||
if request.path.startswith('/api/'):
|
||||
return Response(json.dumps({"status": "error", "reason": "404 Not Found"}, indent=2, sort_keys=True), mimetype='application/json'), 404
|
||||
# UI - HTML Template
|
||||
else:
|
||||
return render_template('404.html'), 404
|
||||
|
||||
@app.errorhandler(405)
|
||||
def _handle_client_error(e):
|
||||
if request.path.startswith('/api/'):
|
||||
res_dict = {"status": "error", "reason": "Method Not Allowed: The method is not allowed for the requested URL"}
|
||||
anchor_id = request.path[8:]
|
||||
anchor_id = anchor_id.replace('/', '_')
|
||||
api_doc_url = 'https://d4-project.org#{}'.format(anchor_id)
|
||||
res_dict['documentation'] = api_doc_url
|
||||
return Response(json.dumps(res_dict, indent=2, sort_keys=True), mimetype='application/json'), 405
|
||||
else:
|
||||
return
|
||||
|
||||
# ========== ROUTES ============
|
||||
@app.route('/login', methods=['POST', 'GET'])
|
||||
def login():
|
||||
|
||||
'''
|
||||
current_ip = request.remote_addr
|
||||
login_failed_ip = r_cache.get('failed_login_ip:{}'.format(current_ip))
|
||||
|
||||
# brute force by ip
|
||||
if login_failed_ip:
|
||||
login_failed_ip = int(login_failed_ip)
|
||||
if login_failed_ip >= 5:
|
||||
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip:{}'.format(current_ip)))
|
||||
return render_template("login.html", error=error)
|
||||
'''
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
#next_page = request.form.get('next_page')
|
||||
|
||||
if username is not None:
|
||||
user = User.get(username)
|
||||
'''
|
||||
login_failed_user_id = r_cache.get('failed_login_user_id:{}'.format(username))
|
||||
# brute force by user_id
|
||||
if login_failed_user_id:
|
||||
login_failed_user_id = int(login_failed_user_id)
|
||||
if login_failed_user_id >= 5:
|
||||
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_user_id:{}'.format(username)))
|
||||
return render_template("login.html", error=error)
|
||||
'''
|
||||
|
||||
if user and user.check_password(password):
|
||||
#if not check_user_role_integrity(user.get_id()):
|
||||
# error = 'Incorrect User ACL, Please contact your administrator'
|
||||
# return render_template("login.html", error=error)
|
||||
login_user(user) ## TODO: use remember me ?
|
||||
if user.request_password_change():
|
||||
return redirect(url_for('change_password'))
|
||||
else:
|
||||
return redirect(url_for('index'))
|
||||
# login failed
|
||||
else:
|
||||
# set brute force protection
|
||||
#logger.warning("Login failed, ip={}, username={}".format(current_ip, username))
|
||||
#r_cache.incr('failed_login_ip:{}'.format(current_ip))
|
||||
#r_cache.expire('failed_login_ip:{}'.format(current_ip), 300)
|
||||
#r_cache.incr('failed_login_user_id:{}'.format(username))
|
||||
#r_cache.expire('failed_login_user_id:{}'.format(username), 300)
|
||||
#
|
||||
|
||||
error = 'Password Incorrect'
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
return 'please provide a valid username'
|
||||
|
||||
else:
|
||||
#next_page = request.args.get('next')
|
||||
error = request.args.get('error')
|
||||
return render_template("login.html" , error=error)
|
||||
|
||||
@app.route('/change_password', methods=['POST', 'GET'])
|
||||
@login_required
|
||||
def change_password():
|
||||
password1 = request.form.get('password1')
|
||||
password2 = request.form.get('password2')
|
||||
error = request.args.get('error')
|
||||
|
||||
if error:
|
||||
return render_template("change_password.html", error=error)
|
||||
|
||||
if current_user.is_authenticated and password1!=None:
|
||||
if password1==password2:
|
||||
if check_password_strength(password1):
|
||||
user_id = current_user.get_id()
|
||||
create_user_db(user_id , password1, update=True)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
error = 'Incorrect password'
|
||||
return render_template("change_password.html", error=error)
|
||||
else:
|
||||
error = "Passwords don't match"
|
||||
return render_template("change_password.html", error=error)
|
||||
else:
|
||||
error = 'Please choose a new password'
|
||||
return render_template("change_password.html", error=error)
|
||||
|
||||
@app.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# role error template
|
||||
@app.route('/role', methods=['POST', 'GET'])
|
||||
@login_required
|
||||
def role():
|
||||
return render_template("error/403.html"), 403
|
||||
|
||||
@app.route('/test')
|
||||
def test():
|
||||
return 'test'
|
||||
|
||||
@app.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
date = datetime.datetime.now().strftime("%Y/%m/%d")
|
||||
return render_template("index.html", date=date)
|
||||
|
||||
@app.route('/_json_daily_uuid_stats')
|
||||
@login_required
|
||||
def _json_daily_uuid_stats():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
daily_uuid = redis_server_metadata.zrange('daily_uuid:{}'.format(date), 0, -1, withscores=True)
|
||||
|
@ -219,6 +378,7 @@ def _json_daily_uuid_stats():
|
|||
return jsonify(data_daily_uuid)
|
||||
|
||||
@app.route('/_json_daily_type_stats')
|
||||
@login_required
|
||||
def _json_daily_type_stats():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
daily_uuid = redis_server_metadata.zrange('daily_type:{}'.format(date), 0, -1, withscores=True)
|
||||
|
@ -235,6 +395,7 @@ def _json_daily_type_stats():
|
|||
return jsonify(data_daily_uuid)
|
||||
|
||||
@app.route('/sensors_status')
|
||||
@login_required
|
||||
def sensors_status():
|
||||
active_connection_filter = request.args.get('active_connection_filter')
|
||||
if active_connection_filter is None:
|
||||
|
@ -314,6 +475,7 @@ def sensors_status():
|
|||
active_connection_filter=active_connection_filter)
|
||||
|
||||
@app.route('/show_active_uuid')
|
||||
@login_required
|
||||
def show_active_uuid():
|
||||
#swap switch value
|
||||
active_connection_filter = request.args.get('show_active_connection')
|
||||
|
@ -328,6 +490,7 @@ def show_active_uuid():
|
|||
return redirect(url_for('sensors_status', active_connection_filter=active_connection_filter))
|
||||
|
||||
@app.route('/server_management')
|
||||
@login_required
|
||||
def server_management():
|
||||
blacklisted_ip = request.args.get('blacklisted_ip')
|
||||
unblacklisted_ip = request.args.get('unblacklisted_ip')
|
||||
|
@ -398,6 +561,7 @@ def server_management():
|
|||
blacklisted_uuid=blacklisted_uuid, unblacklisted_uuid=unblacklisted_uuid)
|
||||
|
||||
@app.route('/uuid_management')
|
||||
@login_required
|
||||
def uuid_management():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -470,6 +634,7 @@ def uuid_management():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklisted_ip')
|
||||
@login_required
|
||||
def blacklisted_ip():
|
||||
blacklisted_ip = request.args.get('blacklisted_ip')
|
||||
unblacklisted_ip = request.args.get('unblacklisted_ip')
|
||||
|
@ -495,6 +660,7 @@ def blacklisted_ip():
|
|||
unblacklisted_ip=unblacklisted_ip, blacklisted_ip=blacklisted_ip)
|
||||
|
||||
@app.route('/blacklisted_uuid')
|
||||
@login_required
|
||||
def blacklisted_uuid():
|
||||
blacklisted_uuid = request.args.get('blacklisted_uuid')
|
||||
unblacklisted_uuid = request.args.get('unblacklisted_uuid')
|
||||
|
@ -521,6 +687,7 @@ def blacklisted_uuid():
|
|||
|
||||
|
||||
@app.route('/uuid_change_stream_max_size')
|
||||
@login_required
|
||||
def uuid_change_stream_max_size():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -539,6 +706,7 @@ def uuid_change_stream_max_size():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/uuid_change_description')
|
||||
@login_required
|
||||
def uuid_change_description():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
description = request.args.get('description')
|
||||
|
@ -550,6 +718,7 @@ def uuid_change_description():
|
|||
|
||||
# # TODO: check analyser uuid dont exist
|
||||
@app.route('/add_new_analyzer')
|
||||
@login_required
|
||||
def add_new_analyzer():
|
||||
type = request.args.get('type')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -576,6 +745,7 @@ def add_new_analyzer():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/empty_analyzer_queue')
|
||||
@login_required
|
||||
def empty_analyzer_queue():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
type = request.args.get('type')
|
||||
|
@ -598,6 +768,7 @@ def empty_analyzer_queue():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/remove_analyzer')
|
||||
@login_required
|
||||
def remove_analyzer():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
type = request.args.get('type')
|
||||
|
@ -623,6 +794,7 @@ def remove_analyzer():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/analyzer_change_max_size')
|
||||
@login_required
|
||||
def analyzer_change_max_size():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -641,6 +813,7 @@ def analyzer_change_max_size():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/kick_uuid')
|
||||
@login_required
|
||||
def kick_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -650,6 +823,7 @@ def kick_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklist_uuid')
|
||||
@login_required
|
||||
def blacklist_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -670,6 +844,7 @@ def blacklist_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/unblacklist_uuid')
|
||||
@login_required
|
||||
def unblacklist_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -693,6 +868,7 @@ def unblacklist_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklist_ip')
|
||||
@login_required
|
||||
def blacklist_ip():
|
||||
ip = request.args.get('ip')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -718,6 +894,7 @@ def blacklist_ip():
|
|||
return 'Invalid ip'
|
||||
|
||||
@app.route('/unblacklist_ip')
|
||||
@login_required
|
||||
def unblacklist_ip():
|
||||
ip = request.args.get('ip')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -745,6 +922,7 @@ def unblacklist_ip():
|
|||
return 'Invalid ip'
|
||||
|
||||
@app.route('/blacklist_ip_by_uuid')
|
||||
@login_required
|
||||
def blacklist_ip_by_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -756,6 +934,7 @@ def blacklist_ip_by_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/unblacklist_ip_by_uuid')
|
||||
@login_required
|
||||
def unblacklist_ip_by_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -767,6 +946,7 @@ def unblacklist_ip_by_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/add_accepted_type')
|
||||
@login_required
|
||||
def add_accepted_type():
|
||||
type = request.args.get('type')
|
||||
extended_type_name = request.args.get('extended_type_name')
|
||||
|
@ -786,6 +966,7 @@ def add_accepted_type():
|
|||
return 'Invalid type'
|
||||
|
||||
@app.route('/remove_accepted_type')
|
||||
@login_required
|
||||
def remove_accepted_type():
|
||||
type = request.args.get('type')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -798,6 +979,7 @@ def remove_accepted_type():
|
|||
return 'Invalid type'
|
||||
|
||||
@app.route('/remove_accepted_extended_type')
|
||||
@login_required
|
||||
def remove_accepted_extended_type():
|
||||
type_name = request.args.get('type_name')
|
||||
redis_server_metadata.srem('server:accepted_extended_type', type_name)
|
||||
|
@ -805,6 +987,7 @@ def remove_accepted_extended_type():
|
|||
|
||||
# demo function
|
||||
@app.route('/delete_data')
|
||||
@login_required
|
||||
def delete_data():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
redis_server_metadata.delete('daily_type:{}'.format(date))
|
||||
|
@ -813,6 +996,7 @@ def delete_data():
|
|||
|
||||
# demo function
|
||||
@app.route('/set_uuid_hmac_key')
|
||||
@login_required
|
||||
def set_uuid_hmac_key():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -824,6 +1008,7 @@ def set_uuid_hmac_key():
|
|||
|
||||
# demo function
|
||||
@app.route('/whois_data')
|
||||
@login_required
|
||||
def whois_data():
|
||||
ip = request.args.get('ip')
|
||||
if is_valid_ip:
|
||||
|
@ -832,11 +1017,13 @@ def whois_data():
|
|||
return 'Invalid IP'
|
||||
|
||||
@app.route('/generate_uuid')
|
||||
@login_required
|
||||
def generate_uuid():
|
||||
new_uuid = uuid.uuid4()
|
||||
return jsonify({'uuid': new_uuid})
|
||||
|
||||
@app.route('/get_analyser_sample')
|
||||
@login_required
|
||||
def get_analyser_sample():
|
||||
type = request.args.get('type')
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
|
@ -864,6 +1051,7 @@ def get_analyser_sample():
|
|||
return jsonify('Incorrect UUID')
|
||||
|
||||
@app.route('/get_uuid_type_history_json')
|
||||
@login_required
|
||||
def get_uuid_type_history_json():
|
||||
uuid_sensor = request.args.get('uuid_sensor')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -894,6 +1082,7 @@ def get_uuid_type_history_json():
|
|||
return jsonify('Incorrect UUID')
|
||||
|
||||
@app.route('/get_uuid_stats_history_json')
|
||||
@login_required
|
||||
def get_uuid_stats_history_json():
|
||||
uuid_sensor = request.args.get('uuid_sensor')
|
||||
stats = request.args.get('stats')
|
||||
|
@ -925,4 +1114,4 @@ def get_uuid_stats_history_json():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host='0.0.0.0', port=7000, threaded=True)
|
||||
app.run(host='0.0.0.0', port=7000, threaded=True, ssl_context=ssl_context)
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import re
|
||||
import redis
|
||||
import bcrypt
|
||||
|
||||
from functools import wraps
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
|
||||
|
||||
from flask import request, current_app
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'role'
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
default_passwd_file = os.path.join(os.environ['D4_HOME'], 'DEFAULT_PASSWORD')
|
||||
|
||||
regex_password = r'^(?=(.*\d){2})(?=.*[a-z])(?=.*[A-Z]).{10,100}$'
|
||||
regex_password = re.compile(regex_password)
|
||||
|
||||
###############################################################
|
||||
############### CHECK ROLE ACCESS ##################
|
||||
###############################################################
|
||||
|
||||
def login_admin(func):
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return login_manager.unauthorized()
|
||||
elif (not current_user.is_in_role('admin')):
|
||||
return login_manager.unauthorized()
|
||||
return func(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
def login_analyst(func):
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return login_manager.unauthorized()
|
||||
elif (not current_user.is_in_role('analyst')):
|
||||
return login_manager.unauthorized()
|
||||
return func(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
|
||||
|
||||
###############################################################
|
||||
###############################################################
|
||||
###############################################################
|
||||
|
||||
def gen_password(length=30, charset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_!@#$%^&*()"):
|
||||
random_bytes = os.urandom(length)
|
||||
len_charset = len(charset)
|
||||
indices = [int(len_charset * (byte / 256.0)) for byte in random_bytes]
|
||||
return "".join([charset[index] for index in indices])
|
||||
|
||||
def gen_token(length=41, charset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"):
|
||||
random_bytes = os.urandom(length)
|
||||
len_charset = len(charset)
|
||||
indices = [int(len_charset * (byte / 256.0)) for byte in random_bytes]
|
||||
return "".join([charset[index] for index in indices])
|
||||
|
||||
def generate_new_token(user_id):
|
||||
# create user token
|
||||
current_token = r_serv_db.hget('user_metadata:{}'.format(user_id), 'token')
|
||||
if current_token:
|
||||
r_serv_db.hdel('user:tokens', current_token)
|
||||
token = gen_token(41)
|
||||
r_serv_db.hset('user:tokens', token, user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'token', token)
|
||||
|
||||
def get_default_admin_token():
|
||||
if r_serv_db.exists('user_metadata:admin@admin.test'):
|
||||
return r_serv_db.hget('user_metadata:admin@admin.test', 'token')
|
||||
else:
|
||||
return ''
|
||||
|
||||
def create_user_db(username_id , password, default=False, role=None, update=False):
|
||||
password = password.encode()
|
||||
password_hash = hashing_password(password)
|
||||
|
||||
# create user token
|
||||
generate_new_token(username_id)
|
||||
|
||||
if update:
|
||||
r_serv_db.hdel('user_metadata:{}'.format(username_id), 'change_passwd')
|
||||
# remove default user password file
|
||||
if username_id=='admin@admin.test':
|
||||
os.remove(default_passwd_file)
|
||||
else:
|
||||
if default:
|
||||
r_serv_db.hset('user_metadata:{}'.format(username_id), 'change_passwd', 'True')
|
||||
if role:
|
||||
if role in get_all_role():
|
||||
for role_to_add in get_all_user_role(role):
|
||||
r_serv_db.sadd('user_role:{}'.format(role_to_add), username_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(username_id), 'role', role)
|
||||
|
||||
r_serv_db.hset('user:all', username_id, password_hash)
|
||||
|
||||
def edit_user_db(user_id, role, password=None):
|
||||
if password:
|
||||
password_hash = hashing_password(password.encode())
|
||||
r_serv_db.hset('user:all', user_id, password_hash)
|
||||
|
||||
current_role = r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
if role != current_role:
|
||||
request_level = get_role_level(role)
|
||||
current_role = get_role_level(current_role)
|
||||
|
||||
if current_role < request_level:
|
||||
role_to_remove = get_user_role_by_range(current_role -1, request_level - 2)
|
||||
for role_id in role_to_remove:
|
||||
r_serv_db.srem('user_role:{}'.format(role_id), user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'role', role)
|
||||
else:
|
||||
role_to_add = get_user_role_by_range(request_level -1, current_role)
|
||||
for role_id in role_to_add:
|
||||
r_serv_db.sadd('user_role:{}'.format(role_id), user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'role', role)
|
||||
|
||||
def delete_user_db(user_id):
|
||||
if r_serv_db.exists('user_metadata:{}'.format(user_id)):
|
||||
role_to_remove =get_all_role()
|
||||
for role_id in role_to_remove:
|
||||
r_serv_db.srem('user_role:{}'.format(role_id), user_id)
|
||||
user_token = r_serv_db.hget('user_metadata:{}'.format(user_id), 'token')
|
||||
r_serv_db.hdel('user:tokens', user_token)
|
||||
r_serv_db.delete('user_metadata:{}'.format(user_id))
|
||||
r_serv_db.hdel('user:all', user_id)
|
||||
|
||||
def hashing_password(bytes_password):
|
||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||
return hashed
|
||||
|
||||
def check_password_strength(password):
|
||||
result = regex_password.match(password)
|
||||
if result:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_all_role():
|
||||
return r_serv_db.zrange('d4:all_role', 0, -1)
|
||||
|
||||
def get_role_level(role):
|
||||
return int(r_serv_db.zscore('d4:all_role', role))
|
||||
|
||||
def get_all_user_role(user_role):
|
||||
current_role_val = get_role_level(user_role)
|
||||
return r_serv_db.zrange('d4:all_role', current_role_val -1, -1)
|
||||
|
||||
def get_all_user_upper_role(user_role):
|
||||
current_role_val = get_role_level(user_role)
|
||||
# remove one rank
|
||||
if current_role_val > 1:
|
||||
return r_serv_db.zrange('d4:all_role', 0, current_role_val -2)
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_user_role_by_range(inf, sup):
|
||||
return r_serv_db.zrange('d4:all_role', inf, sup)
|
||||
|
||||
def get_user_role(user_id):
|
||||
return r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
|
||||
def check_user_role_integrity(user_id):
|
||||
user_role = get_user_role(user_id)
|
||||
all_user_role = get_all_user_role(user_role)
|
||||
res = True
|
||||
if user_role not in all_user_role:
|
||||
return False
|
||||
return res
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import sys
|
||||
import redis
|
||||
import configparser
|
||||
|
||||
sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib'))
|
||||
|
||||
from Role_Manager import create_user_db, edit_user_db, get_default_admin_token, gen_password
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_HOST', 6380))
|
||||
|
||||
r_serv = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# create role_list
|
||||
if not r_serv.exists('d4:all_role'):
|
||||
role_dict = {'admin': 1, 'user': 1, 'sensor_register': 20}
|
||||
r_serv.zadd('d4:all_role', role_dict)
|
||||
|
||||
username = 'admin@admin.test'
|
||||
password = gen_password()
|
||||
if r_serv.exists('user_metadata:admin@admin.test'):
|
||||
edit_user_db(username, password=password, role='admin')
|
||||
else:
|
||||
create_user_db(username, password, role='admin', default=True)
|
||||
token = get_default_admin_token()
|
||||
|
||||
default_passwd_file = os.path.join(os.environ['D4_HOME'], 'DEFAULT_PASSWORD')
|
||||
to_write_str = '# Password Generated by default\n# This file is deleted after the first login\n#\nemail=admin@admin.test\npassword='
|
||||
to_write_str = to_write_str + password + '\nAPI_Key=' + token
|
||||
with open(default_passwd_file, 'w') as f:
|
||||
f.write(to_write_str)
|
||||
|
||||
print('new user created: {}'.format(username))
|
||||
print('password: {}'.format(password))
|
||||
print('token: {}'.format(token))
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>403 - D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='image/d4-logo.png') }}">
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div>
|
||||
<br>
|
||||
<br>
|
||||
<h1 class="text-center">403 Forbidden</h1>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="d-flex justify-content-center">
|
||||
<pre>
|
||||
,d8 ,a8888a, ad888888b,
|
||||
,d888 ,8P"' `"Y8, d8" "88
|
||||
,d8" 88 ,8P Y8, a8P
|
||||
,d8" 88 88 88 aad8"
|
||||
,d8" 88 88 88 ""Y8,
|
||||
8888888888888 `8b d8' "8b
|
||||
88 `8ba, ,ad8' Y8, a88
|
||||
88 "Y8888P" "Y888888P'
|
||||
|
||||
88888888888 88 88 88 88
|
||||
88 88 "" 88 88
|
||||
88 88 88 88
|
||||
88aaaaa ,adPPYba, 8b,dPPYba, 88,dPPYba, 88 ,adPPYb,88 ,adPPYb,88 ,adPPYba, 8b,dPPYba,
|
||||
88""""" a8" "8a 88P' "Y8 88P' "8a 88 a8" `Y88 a8" `Y88 a8P_____88 88P' `"8a
|
||||
88 8b d8 88 88 d8 88 8b 88 8b 88 8PP""""""" 88 88
|
||||
88 "8a, ,a8" 88 88b, ,a8" 88 "8a, ,d88 "8a, ,d88 "8b, ,aa 88 88
|
||||
88 `"YbbdP"' 88 8Y"Ybbd8"' 88 `"8bbdP"Y8 `"8bbdP"Y8 `"Ybbd8"' 88 88
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{% include 'navfooter.html' %}
|
||||
|
||||
<body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,108 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="text-center">
|
||||
|
||||
|
||||
<form class="form-signin" action="{{ url_for('change_password')}}" autocomplete="off" method="post">
|
||||
<img class="mb-4" src="{{ url_for('static', filename='img/d4-logo.png')}}" width="300">
|
||||
<h1 class="h3 mb-3 text-secondary">Change Password</h1>
|
||||
<label for="inputPassword1" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword1" name="password1" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Password" autocomplete="new-password" required autofocus>
|
||||
<label for="inputPassword2" class="sr-only">Confirm Password</label>
|
||||
<input type="password" id="inputPassword2" name="password2" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Confirm Password" value="" autocomplete="new-password" required>
|
||||
{% if error %}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<h5 class="h3 mb-3 text-secondary">Password Requirements</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Minimal length
|
||||
<span class="badge badge-primary badge-pill">10</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Upper characters: A-Z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Lower characters: a-z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Digits: 0-9
|
||||
<span class="badge badge-primary badge-pill">2</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Maximum length
|
||||
<span class="badge badge-primary badge-pill">100</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
|
@ -0,0 +1,85 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="text-center">
|
||||
|
||||
|
||||
<form class="form-signin" action="{{ url_for('login')}}" method="post">
|
||||
<img class="mb-4" src="{{ url_for('static', filename='img/d4-logo.png')}}" width="300">
|
||||
<h1 class="h3 mb-3 text-secondary">Please sign in</h1>
|
||||
<label for="inputEmail" class="sr-only">Email address</label>
|
||||
<input type="email" id="inputEmail" name="username" class="form-control" placeholder="Email address" required autofocus>
|
||||
<label for="inputPassword" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword" name="password" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Password" required>
|
||||
{% if error %}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
Loading…
Reference in New Issue