chg: [user_management] create default admin user (temp passwd save in AIL_HOME) + change password UI + logout UI + create random password

pull/359/head
Terrtia 2019-06-06 21:27:13 +02:00
parent 99e35c51ec
commit 3fe9d14e9f
No known key found for this signature in database
GPG Key ID: 1E1B1F50D84613D0
7 changed files with 192 additions and 39 deletions

View File

@ -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:

View File

@ -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

View File

@ -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():

View File

@ -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))

View File

@ -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>

View File

@ -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>

View File

@ -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">