mirror of https://github.com/MISP/misp-modules
chg: [website] admin user
parent
7a31404c7b
commit
4426a51193
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
|
@ -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')
|
|
@ -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))
|
|
@ -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"])
|
||||
|
|
|
@ -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"""
|
||||
|
||||
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"""
|
||||
|
||||
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"""
|
||||
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"""
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
{% 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>
|
||||
{% 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>
|
||||
|
|
|
@ -50,82 +50,10 @@ def get_object(obj_name):
|
|||
return False
|
||||
|
||||
|
||||
def admin_user_active():
|
||||
return Config.ADMIN_USER
|
||||
|
||||
# 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 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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,6 +4,7 @@ flask-session
|
|||
flask-sqlalchemy
|
||||
Flask-WTF
|
||||
Flask-Migrate
|
||||
Flask-Login
|
||||
WTForms
|
||||
Werkzeug==2.3.8
|
||||
flask-restx
|
||||
|
|
Loading…
Reference in New Issue