chg: [website] admin user

main
David Cruciani 2024-02-29 10:15:16 +01:00
parent 7a31404c7b
commit 4426a51193
No known key found for this signature in database
GPG Key ID: 8690CDE1E3994B9B
14 changed files with 304 additions and 108 deletions

View File

@ -31,8 +31,18 @@ Edit `config.py`
- `MISP_MODULE`: url and port where misp-module is running
- `ADMIN_USER`: If True, config page will not be accessible
- `ADMIN_PASSWORD`: Password for Admin user if `ADMIN_USER` is True
## Launch
```bash
./launch.sh -l
```
## Admin user
If admin user is active, type `/login` in url to access a login page and type the password wrote in `config.py` in `ADMIN_PASSOWRD`.

View File

@ -3,6 +3,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
from flask_migrate import Migrate
from flask_session import Session
from flask_login import LoginManager
from config import config as Config
import os
@ -11,7 +12,8 @@ import os
db = SQLAlchemy()
csrf = CSRFProtect()
migrate = Migrate()
sess = Session()
session = Session()
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
@ -25,12 +27,16 @@ def create_app():
csrf.init_app(app)
migrate.init_app(app, db, render_as_batch=True)
app.config["SESSION_SQLALCHEMY"] = db
sess.init_app(app)
session.init_app(app)
login_manager.login_view = "account.login"
login_manager.init_app(app)
from .home import home_blueprint
from .history.history import history_blueprint
from .account.account import account_blueprint
app.register_blueprint(home_blueprint, url_prefix="/")
app.register_blueprint(history_blueprint, url_prefix="/")
app.register_blueprint(account_blueprint, url_prefix="/")
return app

View File

@ -0,0 +1,45 @@
from ..db_class.db import User
from flask import Blueprint, render_template, redirect, url_for, request, flash
from .form import LoginForm
from flask_login import (
login_required,
login_user,
logout_user,
current_user
)
from ..utils.utils import admin_password
from ..db_class.db import User
from .. import db
account_blueprint = Blueprint(
'account',
__name__,
template_folder='templates',
static_folder='static'
)
@account_blueprint.route('/login', methods=['GET', 'POST'])
def login():
"""Log in an existing user."""
form = LoginForm()
if form.validate_on_submit():
if form.password.data == str(admin_password()):
user = User(email="admin@admin.admin")
db.session.add(user)
db.session.commit()
login_user(user, form.remember_me.data)
flash('You are now logged in. Welcome back!', 'success')
return redirect(request.args.get('next') or "/")
else:
flash('Invalid password.', 'error')
return render_template('account/login.html', form=form)
@account_blueprint.route('/logout')
@login_required
def logout():
User.query.filter_by(id=current_user.id).delete()
logout_user()
flash('You have been logged out.', 'info')
return redirect(url_for('home.home'))

View File

@ -0,0 +1,13 @@
from flask_wtf import FlaskForm
from wtforms.fields import (
BooleanField,
PasswordField,
SubmitField
)
from wtforms.validators import InputRequired
class LoginForm(FlaskForm):
password = PasswordField('Password', validators=[InputRequired()])
remember_me = BooleanField('Keep me logged in')
submit = SubmitField('Log in')

View File

@ -1,5 +1,6 @@
import json
from .. import db
from .. import db, login_manager
from flask_login import UserMixin, AnonymousUserMixin
class Module(db.Model):
@ -76,3 +77,30 @@ class Module_Config(db.Model):
config_id = db.Column(db.Integer, index=True)
value = db.Column(db.String, index=True)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
first_name = db.Column(db.String(64), index=True)
last_name = db.Column(db.String(64), index=True)
email = db.Column(db.String(64), unique=True, index=True)
def to_json(self):
return {
"id": self.id,
"first_name": self.first_name,
"last_name": self.last_name,
"email": self.email
}
class AnonymousUser(AnonymousUserMixin):
def is_admin(self):
return False
def read_only(self):
return True
login_manager.anonymous_user = AnonymousUser
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

View File

@ -1,6 +1,7 @@
import json
from flask import Flask, Blueprint, render_template, request, jsonify
from flask import Flask, Blueprint, render_template, request, jsonify, session as sess
from . import history_core as HistoryModel
from ..utils.utils import admin_user_active
history_blueprint = Blueprint(
'history',
@ -13,6 +14,7 @@ history_blueprint = Blueprint(
@history_blueprint.route("/history", methods=["GET"])
def history():
"""View all history"""
sess["admin_user"] = admin_user_active()
return render_template("history.html")
@history_blueprint.route("/get_history", methods=["GET"])
@ -25,6 +27,7 @@ def get_history():
@history_blueprint.route("/history_session", methods=["GET"])
def history_session():
"""View all history"""
sess["admin_user"] = admin_user_active()
return render_template("history_session.html", tree_view=False)
@history_blueprint.route("/get_history_session", methods=["GET"])
@ -49,6 +52,7 @@ def save_history(sid):
@history_blueprint.route("/history_tree", methods=["GET"])
def history_tree():
"""View all history"""
sess["admin_user"] = admin_user_active()
return render_template("history_session.html", tree_view=True)
@history_blueprint.route("/get_history_tree", methods=["GET"])

View File

@ -1,7 +1,9 @@
import json
from flask import Flask, Blueprint, render_template, request, jsonify
from flask import Blueprint, render_template, request, jsonify, session as sess
from flask_login import current_user
from . import session_class as SessionModel
from . import home_core as HomeModel
from . import session as SessionModel
from .utils.utils import admin_user_active
home_blueprint = Blueprint(
'home',
@ -13,12 +15,14 @@ home_blueprint = Blueprint(
@home_blueprint.route("/")
def home():
sess["admin_user"] = admin_user_active()
if "query" in request.args:
return render_template("home.html", query=request.args.get("query"))
return render_template("home.html")
@home_blueprint.route("/home/<sid>", methods=["GET", "POST"])
def home_query(sid):
sess["admin_user"] = admin_user_active()
if "query" in request.args:
query = request.args.get("query")
return render_template("home.html", query=query, sid=sid)
@ -26,6 +30,7 @@ def home_query(sid):
@home_blueprint.route("/query/<sid>")
def query(sid):
sess["admin_user"] = admin_user_active()
session = HomeModel.get_session(sid)
flag=False
if session:
@ -159,38 +164,50 @@ def download(sid):
@home_blueprint.route("/modules_config")
def modules_config():
"""List all modules for configuration"""
return render_template("modules_config.html")
sess["admin_user"] = admin_user_active()
if sess.get("admin_user"):
if current_user.is_authenticated:
return render_template("modules_config.html")
return render_template("404.html")
@home_blueprint.route("/modules_config_data")
def modules_config_data():
"""List all modules for configuration"""
modules_config = HomeModel.get_modules_config()
return modules_config, 200
sess["admin_user"] = admin_user_active()
if sess.get("admin_user"):
if current_user.is_authenticated:
modules_config = HomeModel.get_modules_config()
return modules_config, 200
return {"message": "Permission denied"}, 403
@home_blueprint.route("/change_config", methods=["POST"])
def change_config():
"""Change configuation for a module"""
if "module_name" in request.json["result_dict"]:
res = HomeModel.change_config_core(request.json["result_dict"])
if res:
return {'message': 'Config changed', 'toast_class': "success-subtle"}, 200
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
return {'message': 'Need to pass "module_name"', 'toast_class': "warning-subtle"}, 400
sess["admin_user"] = admin_user_active()
if sess.get("admin_user"):
if current_user.is_authenticated:
if "module_name" in request.json["result_dict"]:
res = HomeModel.change_config_core(request.json["result_dict"])
if res:
return {'message': 'Config changed', 'toast_class': "success-subtle"}, 200
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
return {'message': 'Need to pass "module_name"', 'toast_class': "warning-subtle"}, 400
return {'message': 'Permission denied', 'toast_class': "danger-subtle"}, 403
@home_blueprint.route("/change_status", methods=["GET"])
def change_status():
"""Change the status of a module, active or unactive"""
if "module_id" in request.args:
res = HomeModel.change_status_core(request.args.get("module_id"))
if res:
return {'message': 'Module status changed', 'toast_class': "success-subtle"}, 200
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
sess["admin_user"] = admin_user_active()
if sess.get("admin_user"):
if current_user.is_authenticated:
if "module_id" in request.args:
res = HomeModel.change_status_core(request.args.get("module_id"))
if res:
return {'message': 'Module status changed', 'toast_class': "success-subtle"}, 200
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
return {'message': 'Permission denied', 'toast_class': "danger-subtle"}, 403

View File

@ -1,5 +1,5 @@
import json
from .utils.utils import query_get_module, isUUID
from .utils.utils import query_get_module
from . import db
from .db_class.db import History, Module, Config, Module_Config, Session_db, History_Tree
from flask import session as sess

View File

@ -0,0 +1,126 @@
<!--
Author: David Cruciani
-->
{% import 'macros/form_macros.html' as f %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Flowintel-cm</title>
<script src="{{ url_for('static',filename='js/popper.min.js') }}"></script>
<script src="{{ url_for('static',filename='bootstrap-5.3.0/js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/jquery.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/jquery-ui.js') }}"></script>
<script src="{{ url_for('static',filename='js/vue.global.js') }}"></script>
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='bootstrap-5.3.0/css/bootstrap.min.css') }}">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='fontawesome-6.3.0/css/fontawesome.css') }}">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='fontawesome-6.3.0/css/solid.css') }}">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/jquery-ui.css') }}">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
html, body {
height: 100%;
}
body {
display: flex;
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-floating:focus-within {
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>
<main class="form-signin">
{% include 'macros/_flashes.html' %}
{% set flashes = {
'error': get_flashed_messages(category_filter=['form-error']),
'warning': get_flashed_messages(category_filter=['form-check-email']),
'info': get_flashed_messages(category_filter=['form-info']),
'success': get_flashed_messages(category_filter=['form-success'])
} %}
<form action="" method="post">
{{ form.hidden_tag() }}
<div class="mb-3">
{{form.password.label}}:
{{form.password(class_="form-control")}}
</div>
<div class="checkbox mb-3">
{{form.remember_me.label}}:
{{form.remember_me}}
</div>
{{ f.form_message(flashes['error'], header='Something went wrong.', class='error') }}
{{ f.form_message(flashes['warning'], header='Check your email.', class='warning') }}
{{ f.form_message(flashes['info'], header='Information', class='info') }}
{{ f.form_message(flashes['success'], header='Success!', class='success') }}
{{form.submit(class='btn btn-primary')}}
</form>
</main>
<!--Main layout-->
{# Implement CSRF protection for site #}
{% if csrf_token()|safe %}
<div style="visibility: hidden; display: none">
<input type="hidden" id="csrf_token" name="csrf_token" value="{{ csrf_token()|safe }}">
</div>
{% endif %}
</body>
<script>
$('.message').each((i, el) => {
const $el = $(el);
const $xx = $el.find('.close');
const sec = $el.data('autohide');
const triggerRemove = () => clearTimeout($el.trigger('remove').T);
$el.one('remove', () => $el.remove());
$xx.one('click', triggerRemove);
if (sec) $el.T = setTimeout(triggerRemove, sec * 1000);
});
</script>
</html>

View File

@ -11,18 +11,34 @@
<a href="/" class="list-group-item list-group-item-action text-nowrap" aria-current="true">
<i class="fa-solid fa-house fa-fw me-3"></i><span>Home</span>
</a>
<a style="margin-top: 30px;" href="/history" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-clock-rotate-left fa-fw me-3"></i><span>History</span>
</a>
{% if session.admin_user %}
{% if current_user.is_authenticated %}
<a style="margin-top: 30px;" href="/history" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-clock-rotate-left fa-fw me-3"></i><span>History</span>
</a>
{%endif%}
{%else%}
<a style="margin-top: 30px;" href="/history" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-clock-rotate-left fa-fw me-3"></i><span>History</span>
</a>
{%endif%}
<a style="margin-top: 30px;" href="/history_session" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-clock fa-fw me-3"></i><span>History Session</span>
</a>
<a style="margin-top: 30px;" href="/history_tree" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-timeline fa-fw me-3"></i><span>History Tree</span>
</a>
<a style="margin-top: 30px;" href="/modules_config" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-gear fa-fw me-3"></i><span>Config</span>
</a>
{% if session.admin_user %}
{% if current_user.is_authenticated %}
<a style="margin-top: 30px;" href="/modules_config" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-gear fa-fw me-3"></i><span>Config</span>
</a>
{%endif%}
{%else%}
<a style="margin-top: 30px;" href="/modules_config" class="list-group-item list-group-item-action text-nowrap">
<i class="fa-solid fa-gear fa-fw me-3"></i><span>Config</span>
</a>
{%endif%}
</div>
</div>
</div>

View File

@ -48,84 +48,12 @@ def get_object(obj_name):
loc_json = json.load(read_json)
return loc_json
return False
# def form_to_dict(form):
# loc_dict = dict()
# for field in form._fields:
# if field == "files_upload":
# loc_dict[field] = dict()
# loc_dict[field]["data"] = form._fields[field].data
# loc_dict[field]["name"] = form._fields[field].name
# elif not field == "submit" and not field == "csrf_token":
# loc_dict[field] = form._fields[field].data
# return loc_dict
def admin_user_active():
return Config.ADMIN_USER
# def create_specific_dir(specific_dir):
# if not os.path.isdir(specific_dir):
# os.mkdir(specific_dir)
def admin_password():
return Config.ADMIN_PASSWORD
# caseSchema = {
# "type": "object",
# "properties": {
# "title": {"type": "string"},
# "description": {"type": "string"},
# "uuid": {"type": "string"},
# "deadline:": {"type": "string"},
# "recurring_date:": {"type": "string"},
# "recurring_type:": {"type": "string"},
# "tasks": {
# "type": "array",
# "items": {"type": "object"},
# },
# "tags":{
# "type": "array",
# "items": {"type": "string"},
# },
# "clusters":{
# "type": "array",
# "items": {"type": "string"},
# },
# },
# "required": ['title']
# }
# taskSchema = {
# "type": "object",
# "properties": {
# "title": {"type": "string"},
# "description": {"type": "string"},
# "uuid": {"type": "string"},
# "deadline:": {"type": "string"},
# "url:": {"type": "string"},
# "notes:": {"type": "string"},
# "tags":{
# "type": "array",
# "items": {"type": "string"}
# },
# "clusters":{
# "type": "array",
# "items": {"type": "string"},
# },
# },
# "required": ['title']
# }
# def validateCaseJson(json_data):
# try:
# jsonschema.validate(instance=json_data, schema=caseSchema)
# except jsonschema.exceptions.ValidationError as err:
# print(err)
# return False
# return True
# def validateTaskJson(json_data):
# try:
# jsonschema.validate(instance=json_data, schema=taskSchema)
# except jsonschema.exceptions.ValidationError as err:
# print(err)
# return False
# return True

View File

@ -4,6 +4,8 @@ class Config:
FLASK_URL = '127.0.0.1'
FLASK_PORT = 7008
MISP_MODULE = '127.0.0.1:6666'
ADMIN_USER = False
ADMIN_PASSWORD = Password1234
class DevelopmentConfig(Config):
DEBUG = True

View File

@ -4,6 +4,7 @@ flask-session
flask-sqlalchemy
Flask-WTF
Flask-Migrate
Flask-Login
WTForms
Werkzeug==2.3.8
flask-restx