mirror of https://github.com/CIRCL/AIL-framework
chg: [user_management] create default admin user (temp passwd save in AIL_HOME) + change password UI + logout UI + create random password
parent
99e35c51ec
commit
3fe9d14e9f
10
OVERVIEW.md
10
OVERVIEW.md
|
@ -53,21 +53,27 @@ Redis and ARDB overview
|
||||||
| ail:current_background_script_stat | **progress in % of the background script** |
|
| ail:current_background_script_stat | **progress in % of the background script** |
|
||||||
|
|
||||||
##### User Management:
|
##### User Management:
|
||||||
| Key | Field | Value |
|
| Hset Key | Field | Value |
|
||||||
| ------ | ------ | ------ |
|
| ------ | ------ | ------ |
|
||||||
| user:all | **user id** | **password hash** |
|
| user:all | **user id** | **password hash** |
|
||||||
| | | |
|
| | | |
|
||||||
| user:tokens | **token** | **user id** |
|
| user:tokens | **token** | **user id** |
|
||||||
| | | |
|
| | | |
|
||||||
| user_metadata:**user id** | **user token** | **token** |
|
| user_metadata:**user id** | **user token** | **token** |
|
||||||
|
| | change_passwd | **boolean** |
|
||||||
|
|
||||||
| Key | Value |
|
| Set Key | Value |
|
||||||
| ------ | ------ |
|
| ------ | ------ |
|
||||||
| user:request_password_change | **user id** |
|
| user:request_password_change | **user id** |
|
||||||
| user:admin | **user id** |
|
| user:admin | **user id** |
|
||||||
| | |
|
| | |
|
||||||
| user_role:**role** | **user id** |
|
| user_role:**role** | **user id** |
|
||||||
|
|
||||||
|
|
||||||
|
| Zrank Key | Field | Value |
|
||||||
|
| ------ | ------ | ------ |
|
||||||
|
| ail:all_role | **role** | **int, role priority (1=admin)** |
|
||||||
|
|
||||||
## DB2 - TermFreq:
|
## DB2 - TermFreq:
|
||||||
|
|
||||||
##### Set:
|
##### Set:
|
||||||
|
|
|
@ -53,7 +53,7 @@ class User(UserMixin):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def request_password_change(self):
|
def request_password_change(self):
|
||||||
if self.r_serv_db.sismember('user:request_password_change', self.id):
|
if self.r_serv_db.hget('user_metadata:{}'.format(self.id), 'change_passwd') == 'True':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -16,6 +16,7 @@ import bcrypt
|
||||||
import flask
|
import flask
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from os.path import join
|
from os.path import join
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(os.path.join(os.environ['AIL_BIN'], 'packages/'))
|
sys.path.append(os.path.join(os.environ['AIL_BIN'], 'packages/'))
|
||||||
|
@ -31,14 +32,14 @@ from pytaxonomies import Taxonomies
|
||||||
import Flask_config
|
import Flask_config
|
||||||
|
|
||||||
def flask_init():
|
def flask_init():
|
||||||
int_user_management()
|
|
||||||
|
|
||||||
def int_user_management():
|
|
||||||
# # TODO: check for admin account
|
|
||||||
# check if an account exists
|
# check if an account exists
|
||||||
if not r_serv_db.hexists('user:all'):
|
if not r_serv_db.exists('user:all'):
|
||||||
password = 'admin@admin.test'
|
password = secrets.token_urlsafe()
|
||||||
create_user_db('admin', password, default=True)
|
create_user_db('admin@admin.test', password, role='admin',default=True)
|
||||||
|
# add default roles
|
||||||
|
if not r_serv_db.exists('ail:all_role'):
|
||||||
|
r_serv_db.zadd('ail:all_role', 1, 'admin')
|
||||||
|
r_serv_db.zadd('ail:all_role', 2, 'analyst')
|
||||||
|
|
||||||
def hashing_password(bytes_password):
|
def hashing_password(bytes_password):
|
||||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||||
|
@ -51,19 +52,31 @@ def verify_password(id, bytes_password):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_user_db(username_id , password, default=False):
|
def check_password_strength(password):
|
||||||
## TODO: validate username
|
result = regex_password.match(password)
|
||||||
## TODO: validate password
|
if result:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
if username_id == '__anonymous__':
|
|
||||||
## TODO: return 500
|
|
||||||
return 'ERROR'
|
|
||||||
|
|
||||||
|
def create_user_db(username_id , password, default=False, role=None, update=False):
|
||||||
password = password.encode()
|
password = password.encode()
|
||||||
password_hash = hashing_password(password)
|
password_hash = hashing_password(password)
|
||||||
r_serv_db.hset('user:all', username_id, password_hash)
|
r_serv_db.hset('user:all', username_id, password_hash)
|
||||||
|
if update:
|
||||||
|
r_serv_db.hdel('user_metadata:{}'.format(username_id), 'change_passwd')
|
||||||
|
if username_id=='admin@admin.test':
|
||||||
|
os.remove(default_passwd_file)
|
||||||
|
else:
|
||||||
if default:
|
if default:
|
||||||
r_serv_db.set('user:request_password_change', username_id)
|
r_serv_db.hset('user_metadata:{}'.format(username_id), 'change_passwd', True)
|
||||||
|
if role:
|
||||||
|
if role in get_all_role():
|
||||||
|
r_serv_db.sadd('user_role:{}'.format(role), username_id)
|
||||||
|
|
||||||
|
def get_all_role():
|
||||||
|
return r_serv_db.zrange('ail:all_role', 0 , -1)
|
||||||
|
|
||||||
# CONFIG #
|
# CONFIG #
|
||||||
cfg = Flask_config.cfg
|
cfg = Flask_config.cfg
|
||||||
|
@ -72,6 +85,11 @@ baseUrl = baseUrl.replace('/', '')
|
||||||
if baseUrl != '':
|
if baseUrl != '':
|
||||||
baseUrl = '/'+baseUrl
|
baseUrl = '/'+baseUrl
|
||||||
|
|
||||||
|
default_passwd_file = os.path.join(os.environ['AIL_HOME'], 'DEFAULT_PASSWORD')
|
||||||
|
|
||||||
|
regex_password = r'^(?=(.*\d){2})(?=.*[a-z])(?=.*[A-Z]).{10,}$'
|
||||||
|
regex_password = re.compile(regex_password)
|
||||||
|
|
||||||
# ========= REDIS =========#
|
# ========= REDIS =========#
|
||||||
r_serv_db = redis.StrictRedis(
|
r_serv_db = redis.StrictRedis(
|
||||||
host=cfg.get("ARDB_DB", "host"),
|
host=cfg.get("ARDB_DB", "host"),
|
||||||
|
@ -163,6 +181,8 @@ modified_header = modified_header.replace('<!--insert here-->', '\n'.join(to_add
|
||||||
with open('templates/header.html', 'w') as f:
|
with open('templates/header.html', 'w') as f:
|
||||||
f.write(modified_header)
|
f.write(modified_header)
|
||||||
|
|
||||||
|
flask_init()
|
||||||
|
|
||||||
|
|
||||||
# ========= JINJA2 FUNCTIONS ========
|
# ========= JINJA2 FUNCTIONS ========
|
||||||
def list_len(s):
|
def list_len(s):
|
||||||
|
@ -187,20 +207,16 @@ def login():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.form.get('username')
|
username = request.form.get('username')
|
||||||
password = request.form.get('password')
|
password = request.form.get('password')
|
||||||
next_page = request.form.get('next_page')
|
#next_page = request.form.get('next_page')
|
||||||
|
|
||||||
print(username)
|
|
||||||
print(password)
|
|
||||||
|
|
||||||
if username is not None:
|
if username is not None:
|
||||||
user = User.get(username)
|
user = User.get(username)
|
||||||
#print(user.is_anonymous)
|
|
||||||
#print('auth') # TODO: overwrite
|
|
||||||
#print(user.is_authenticated)
|
|
||||||
if user and user.check_password(password):
|
if user and user.check_password(password):
|
||||||
login_user(user) ## TODO: use remember me ?
|
login_user(user) ## TODO: use remember me ?
|
||||||
#print(user.request_password_change())
|
|
||||||
print(user.is_active)
|
print(user.is_active)
|
||||||
|
if user.request_password_change():
|
||||||
|
return redirect(url_for('change_password'))
|
||||||
|
else:
|
||||||
return redirect(url_for('dashboard.index'))
|
return redirect(url_for('dashboard.index'))
|
||||||
else:
|
else:
|
||||||
return 'incorrect password'
|
return 'incorrect password'
|
||||||
|
@ -208,9 +224,26 @@ def login():
|
||||||
return 'none'
|
return 'none'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
next_page = request.args.get('next')
|
#next_page = request.args.get('next')
|
||||||
print(next_page)
|
return render_template("login.html")
|
||||||
return render_template("login.html", next_page=next_page)
|
|
||||||
|
@app.route('/change_password', methods=['POST', 'GET'])
|
||||||
|
@login_required
|
||||||
|
def change_password():
|
||||||
|
password1 = request.form.get('password1')
|
||||||
|
password2 = request.form.get('password2')
|
||||||
|
|
||||||
|
# # TODO: display errors message
|
||||||
|
|
||||||
|
if current_user.is_authenticated and password1!=None and 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('dashboard.index'))
|
||||||
|
else:
|
||||||
|
return render_template("change_password.html")
|
||||||
|
else:
|
||||||
|
return render_template("change_password.html")
|
||||||
|
|
||||||
@app.route('/role', methods=['POST', 'GET'])
|
@app.route('/role', methods=['POST', 'GET'])
|
||||||
def role():
|
def role():
|
||||||
|
|
|
@ -12,17 +12,25 @@ import secrets
|
||||||
# Import config
|
# Import config
|
||||||
sys.path.append('./modules/')
|
sys.path.append('./modules/')
|
||||||
|
|
||||||
|
def get_all_role():
|
||||||
|
return r_serv_db.zrange('ail:all_role', 0 , -1)
|
||||||
|
|
||||||
def hashing_password(bytes_password):
|
def hashing_password(bytes_password):
|
||||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||||
return hashed
|
return hashed
|
||||||
|
|
||||||
def create_user_db(username_id , password, default=False):
|
def create_user_db(username_id , password, default=False, role=None, update=False):
|
||||||
password = password.encode()
|
password = password.encode()
|
||||||
password_hash = hashing_password(password)
|
password_hash = hashing_password(password)
|
||||||
r_serv_db.hset('user:all', username_id, password_hash)
|
r_serv_db.hset('user:all', username_id, password_hash)
|
||||||
|
if update:
|
||||||
|
r_serv_db.hdel('user_metadata:{}'.format(username_id), 'change_passwd')
|
||||||
|
else:
|
||||||
if default:
|
if default:
|
||||||
r_serv_db.set('user:request_password_change', username_id)
|
r_serv_db.hset('user_metadata:{}'.format(username_id), 'change_passwd', True)
|
||||||
|
if role:
|
||||||
|
if role in get_all_role():
|
||||||
|
r_serv_db.sadd('user_role:{}'.format(role), username_id)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
configfile = os.path.join(os.environ['AIL_BIN'], 'packages/config.cfg')
|
configfile = os.path.join(os.environ['AIL_BIN'], 'packages/config.cfg')
|
||||||
|
@ -41,13 +49,18 @@ if __name__ == "__main__":
|
||||||
decode_responses=True)
|
decode_responses=True)
|
||||||
|
|
||||||
username = 'admin@admin.test'
|
username = 'admin@admin.test'
|
||||||
# # TODO: create random password
|
password = secrets.token_urlsafe()
|
||||||
password = 'admin'
|
create_user_db(username, password, role='admin', default=True)
|
||||||
create_user_db(username, password, default=True)
|
|
||||||
|
|
||||||
# create user token
|
# create user token
|
||||||
token = secrets.token_urlsafe(41)
|
token = secrets.token_urlsafe(41)
|
||||||
r_serv_db.hset('user:tokens', token, username)
|
r_serv_db.hset('user:tokens', token, username)
|
||||||
|
|
||||||
|
default_passwd_file = os.path.join(os.environ['AIL_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('new user created: {}'.format(username))
|
||||||
print('password: {}'.format(password))
|
print('password: {}'.format(password))
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>AIL-Framework</title>
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png')}}">
|
||||||
|
<!-- Core CSS -->
|
||||||
|
<link href="{{ url_for('static', filename='css/bootstrap4.min.css') }}" rel="stylesheet">
|
||||||
|
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- JS -->
|
||||||
|
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/bootstrap4.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='image/logo-small.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" 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" placeholder="Confirm Password" value="" autocomplete="new-password" required>
|
||||||
|
<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>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
|
@ -67,14 +67,13 @@
|
||||||
|
|
||||||
|
|
||||||
<form class="form-signin" action="{{ url_for('login')}}" method="post">
|
<form class="form-signin" action="{{ url_for('login')}}" method="post">
|
||||||
<img class="mb-4" src="{{ url_for('static', filename='image/AIL.png')}}" width="300">
|
<img class="mb-4" src="{{ url_for('static', filename='image/logo-small.png')}}" width="300">
|
||||||
<h1 class="h3 mb-3 text-secondary">Please sign in</h1>
|
<h1 class="h3 mb-3 text-secondary">Please sign in</h1>
|
||||||
<label for="inputEmail" class="sr-only">Email address</label>
|
<label for="inputEmail" class="sr-only">Email address</label>
|
||||||
<input type="email" id="inputEmail" name="username" class="form-control" placeholder="Email address" required autofocus>
|
<input type="email" id="inputEmail" name="username" class="form-control" placeholder="Email address" required autofocus>
|
||||||
<label for="inputPassword" class="sr-only">Password</label>
|
<label for="inputPassword" class="sr-only">Password</label>
|
||||||
<input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
|
<input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||||
<input type="text" name="next_page" hidden>{{next_page}}</input>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
<li class="nav-item mr-3">
|
<li class="nav-item mr-3">
|
||||||
<a class="nav-link" id="page-options" href="{{ url_for('settings.settings_page') }}" aria-disabled="true"><i class="fas fa-cog"></i> Server Management</a>
|
<a class="nav-link" id="page-options" href="{{ url_for('settings.settings_page') }}" aria-disabled="true"><i class="fas fa-cog"></i> Server Management</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mr-3">
|
||||||
|
<a class="nav-link" id="page-options" href="{{ url_for('logout') }}" aria-disabled="true"><i class="fas fa-sign-out-alt"></i> Log Out</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form class="form-inline my-2 my-lg-0 ml-auto justify-content-center">
|
<form class="form-inline my-2 my-lg-0 ml-auto justify-content-center">
|
||||||
|
|
Loading…
Reference in New Issue