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** |
|
||||
|
||||
##### User Management:
|
||||
| Key | Field | Value |
|
||||
| Hset Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| user:all | **user id** | **password hash** |
|
||||
| | | |
|
||||
| user:tokens | **token** | **user id** |
|
||||
| | | |
|
||||
| user_metadata:**user id** | **user token** | **token** |
|
||||
| | change_passwd | **boolean** |
|
||||
|
||||
| Key | Value |
|
||||
| Set Key | Value |
|
||||
| ------ | ------ |
|
||||
| user:request_password_change | **user id** |
|
||||
| user:admin | **user id** |
|
||||
| | |
|
||||
| user_role:**role** | **user id** |
|
||||
|
||||
|
||||
| Zrank Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| ail:all_role | **role** | **int, role priority (1=admin)** |
|
||||
|
||||
## DB2 - TermFreq:
|
||||
|
||||
##### Set:
|
||||
|
|
|
@ -53,7 +53,7 @@ class User(UserMixin):
|
|||
return False
|
||||
|
||||
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
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -16,6 +16,7 @@ import bcrypt
|
|||
import flask
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
from os.path import join
|
||||
import sys
|
||||
sys.path.append(os.path.join(os.environ['AIL_BIN'], 'packages/'))
|
||||
|
@ -31,14 +32,14 @@ from pytaxonomies import Taxonomies
|
|||
import Flask_config
|
||||
|
||||
def flask_init():
|
||||
int_user_management()
|
||||
|
||||
def int_user_management():
|
||||
# # TODO: check for admin account
|
||||
# check if an account exists
|
||||
if not r_serv_db.hexists('user:all'):
|
||||
password = 'admin@admin.test'
|
||||
create_user_db('admin', password, default=True)
|
||||
if not r_serv_db.exists('user:all'):
|
||||
password = secrets.token_urlsafe()
|
||||
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):
|
||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||
|
@ -51,19 +52,31 @@ def verify_password(id, bytes_password):
|
|||
else:
|
||||
return False
|
||||
|
||||
def create_user_db(username_id , password, default=False):
|
||||
## TODO: validate username
|
||||
## TODO: validate password
|
||||
def check_password_strength(password):
|
||||
result = regex_password.match(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_hash = hashing_password(password)
|
||||
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:
|
||||
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 #
|
||||
cfg = Flask_config.cfg
|
||||
|
@ -72,6 +85,11 @@ baseUrl = baseUrl.replace('/', '')
|
|||
if 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 =========#
|
||||
r_serv_db = redis.StrictRedis(
|
||||
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:
|
||||
f.write(modified_header)
|
||||
|
||||
flask_init()
|
||||
|
||||
|
||||
# ========= JINJA2 FUNCTIONS ========
|
||||
def list_len(s):
|
||||
|
@ -187,20 +207,16 @@ def login():
|
|||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
next_page = request.form.get('next_page')
|
||||
|
||||
print(username)
|
||||
print(password)
|
||||
#next_page = request.form.get('next_page')
|
||||
|
||||
if username is not None:
|
||||
user = User.get(username)
|
||||
#print(user.is_anonymous)
|
||||
#print('auth') # TODO: overwrite
|
||||
#print(user.is_authenticated)
|
||||
if user and user.check_password(password):
|
||||
login_user(user) ## TODO: use remember me ?
|
||||
#print(user.request_password_change())
|
||||
print(user.is_active)
|
||||
if user.request_password_change():
|
||||
return redirect(url_for('change_password'))
|
||||
else:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
else:
|
||||
return 'incorrect password'
|
||||
|
@ -208,9 +224,26 @@ def login():
|
|||
return 'none'
|
||||
|
||||
else:
|
||||
next_page = request.args.get('next')
|
||||
print(next_page)
|
||||
return render_template("login.html", next_page=next_page)
|
||||
#next_page = request.args.get('next')
|
||||
return render_template("login.html")
|
||||
|
||||
@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'])
|
||||
def role():
|
||||
|
|
|
@ -12,17 +12,25 @@ import secrets
|
|||
# Import config
|
||||
sys.path.append('./modules/')
|
||||
|
||||
def get_all_role():
|
||||
return r_serv_db.zrange('ail:all_role', 0 , -1)
|
||||
|
||||
def hashing_password(bytes_password):
|
||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||
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_hash = hashing_password(password)
|
||||
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:
|
||||
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__":
|
||||
configfile = os.path.join(os.environ['AIL_BIN'], 'packages/config.cfg')
|
||||
|
@ -41,13 +49,18 @@ if __name__ == "__main__":
|
|||
decode_responses=True)
|
||||
|
||||
username = 'admin@admin.test'
|
||||
# # TODO: create random password
|
||||
password = 'admin'
|
||||
create_user_db(username, password, default=True)
|
||||
password = secrets.token_urlsafe()
|
||||
create_user_db(username, password, role='admin', default=True)
|
||||
|
||||
# create user token
|
||||
token = secrets.token_urlsafe(41)
|
||||
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('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">
|
||||
<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>
|
||||
<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" placeholder="Password" required>
|
||||
<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>
|
||||
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
<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>
|
||||
</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>
|
||||
|
||||
<form class="form-inline my-2 my-lg-0 ml-auto justify-content-center">
|
||||
|
|
Loading…
Reference in New Issue