2019-11-14 17:05:58 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*-coding:UTF-8 -*
|
|
|
|
|
|
|
|
'''
|
|
|
|
Blueprint Flask: crawler splash endpoints: dashboard, onion crawler ...
|
|
|
|
'''
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import json
|
|
|
|
|
2019-11-19 09:02:25 +01:00
|
|
|
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response, abort
|
2019-11-14 17:05:58 +01:00
|
|
|
from flask_login import login_required, current_user, login_user, logout_user
|
|
|
|
|
|
|
|
sys.path.append('modules')
|
|
|
|
import Flask_config
|
|
|
|
|
|
|
|
# Import Role_Manager
|
2019-11-20 16:15:08 +01:00
|
|
|
from Role_Manager import login_admin, login_analyst, login_read_only
|
2019-11-14 17:05:58 +01:00
|
|
|
|
2022-08-19 16:53:31 +02:00
|
|
|
|
|
|
|
sys.path.append(os.environ['AIL_BIN'])
|
|
|
|
##################################
|
|
|
|
# Import Project packages
|
|
|
|
##################################
|
|
|
|
from lib.objects import ail_objects
|
2023-04-05 16:09:06 +02:00
|
|
|
from lib import Tag
|
2022-08-19 16:53:31 +02:00
|
|
|
|
2019-11-14 17:05:58 +01:00
|
|
|
bootstrap_label = Flask_config.bootstrap_label
|
2019-11-15 17:22:50 +01:00
|
|
|
vt_enabled = Flask_config.vt_enabled
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
# ============ BLUEPRINT ============
|
2023-04-05 16:09:06 +02:00
|
|
|
correlation = Blueprint('correlation', __name__,
|
|
|
|
template_folder=os.path.join(os.environ['AIL_FLASK'], 'templates/correlation'))
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
# ============ VARIABLES ============
|
|
|
|
|
|
|
|
# ============ FUNCTIONS ============
|
|
|
|
|
|
|
|
def sanitise_graph_mode(graph_mode):
|
|
|
|
if graph_mode not in ('inter', 'union'):
|
|
|
|
return 'union'
|
|
|
|
else:
|
|
|
|
return graph_mode
|
|
|
|
|
|
|
|
def sanitise_nb_max_nodes(nb_max_nodes):
|
|
|
|
try:
|
|
|
|
nb_max_nodes = int(nb_max_nodes)
|
2023-06-18 16:09:57 +02:00
|
|
|
if nb_max_nodes < 2 and nb_max_nodes != 0:
|
2019-11-14 17:05:58 +01:00
|
|
|
nb_max_nodes = 300
|
2023-04-05 16:09:06 +02:00
|
|
|
except (TypeError, ValueError):
|
2019-11-14 17:05:58 +01:00
|
|
|
nb_max_nodes = 300
|
|
|
|
return nb_max_nodes
|
|
|
|
|
2023-05-26 11:22:12 +02:00
|
|
|
def sanitise_level(level):
|
|
|
|
try:
|
|
|
|
level = int(level)
|
|
|
|
if level < 0:
|
|
|
|
level = 2
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
level = 2
|
|
|
|
return level
|
|
|
|
|
2023-07-07 16:29:32 +02:00
|
|
|
def sanitise_objs_hidden(objs_hidden):
|
|
|
|
if objs_hidden:
|
|
|
|
objs_hidden = set(objs_hidden.split(',')) # TODO sanitize objects
|
|
|
|
else:
|
|
|
|
objs_hidden = set()
|
|
|
|
return objs_hidden
|
|
|
|
|
2019-11-14 17:05:58 +01:00
|
|
|
# ============= ROUTES ==============
|
2023-04-05 16:09:06 +02:00
|
|
|
@correlation.route('/correlation/show', methods=['GET', 'POST'])
|
2019-11-14 17:05:58 +01:00
|
|
|
@login_required
|
2019-11-20 16:15:08 +01:00
|
|
|
@login_read_only
|
2019-11-15 17:22:50 +01:00
|
|
|
def show_correlation():
|
2019-11-14 17:05:58 +01:00
|
|
|
if request.method == 'POST':
|
2022-10-25 16:25:19 +02:00
|
|
|
object_type = request.form.get('obj_type')
|
|
|
|
subtype = request.form.get('subtype')
|
|
|
|
obj_id = request.form.get('obj_id')
|
2019-11-14 17:05:58 +01:00
|
|
|
max_nodes = request.form.get('max_nb_nodes_in')
|
|
|
|
mode = request.form.get('mode')
|
|
|
|
if mode:
|
|
|
|
mode = 'inter'
|
|
|
|
else:
|
|
|
|
mode = 'union'
|
2023-05-26 11:22:12 +02:00
|
|
|
level = sanitise_level(request.form.get('level'))
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
## get all selected correlations
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types = []
|
2023-06-16 15:39:13 +02:00
|
|
|
correl_option = request.form.get('CookieNameCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('cookie-name')
|
2023-07-06 11:26:32 +02:00
|
|
|
correl_option = request.form.get('EtagCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('etag')
|
2024-02-21 14:18:09 +01:00
|
|
|
correl_option = request.form.get('FaviconCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('favicon')
|
2022-10-25 16:25:19 +02:00
|
|
|
correl_option = request.form.get('CveCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('cve')
|
2019-11-14 17:05:58 +01:00
|
|
|
correl_option = request.form.get('CryptocurrencyCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('cryptocurrency')
|
2023-07-17 15:47:17 +02:00
|
|
|
correl_option = request.form.get('HHHashCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('hhhash')
|
2019-11-14 17:05:58 +01:00
|
|
|
correl_option = request.form.get('PgpCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('pgp')
|
2020-05-11 18:11:38 +02:00
|
|
|
correl_option = request.form.get('UsernameCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('username')
|
2019-11-14 17:05:58 +01:00
|
|
|
correl_option = request.form.get('DecodedCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('decoded')
|
2019-12-17 15:13:36 +01:00
|
|
|
correl_option = request.form.get('ScreenshotCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('screenshot')
|
2019-11-14 17:05:58 +01:00
|
|
|
# correlation_objects
|
|
|
|
correl_option = request.form.get('DomainCheck')
|
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('domain')
|
|
|
|
correl_option = request.form.get('ItemCheck')
|
2019-11-14 17:05:58 +01:00
|
|
|
if correl_option:
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types.append('item')
|
2023-12-13 11:51:53 +01:00
|
|
|
correl_option = request.form.get('chatCheck')
|
2023-05-26 10:47:58 +02:00
|
|
|
if correl_option:
|
2023-12-13 11:51:53 +01:00
|
|
|
filter_types.append('chat')
|
|
|
|
correl_option = request.form.get('subchannelCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('chat-subchannel')
|
|
|
|
correl_option = request.form.get('threadCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('chat-thread')
|
|
|
|
correl_option = request.form.get('messageCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('message')
|
|
|
|
correl_option = request.form.get('imageCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('image')
|
|
|
|
correl_option = request.form.get('user_accountCheck')
|
|
|
|
if correl_option:
|
|
|
|
filter_types.append('user-account')
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
# list as params
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types = ",".join(filter_types)
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
# redirect to keep history and bookmark
|
2022-10-25 16:25:19 +02:00
|
|
|
return redirect(url_for('correlation.show_correlation', type=object_type, subtype=subtype, id=obj_id, mode=mode,
|
2023-05-26 11:22:12 +02:00
|
|
|
max_nodes=max_nodes, level=level, filter=filter_types))
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
# request.method == 'GET'
|
|
|
|
else:
|
2022-10-25 16:25:19 +02:00
|
|
|
obj_type = request.args.get('type')
|
|
|
|
subtype = request.args.get('subtype', '')
|
|
|
|
obj_id = request.args.get('id')
|
2019-11-14 17:05:58 +01:00
|
|
|
max_nodes = sanitise_nb_max_nodes(request.args.get('max_nodes'))
|
|
|
|
mode = sanitise_graph_mode(request.args.get('mode'))
|
2023-05-26 11:22:12 +02:00
|
|
|
level = sanitise_level(request.args.get('level'))
|
2023-07-07 16:29:32 +02:00
|
|
|
objs_hidden = sanitise_objs_hidden(request.args.get('hidden'))
|
|
|
|
obj_to_hide = request.args.get('hide')
|
|
|
|
if obj_to_hide:
|
|
|
|
objs_hidden.add(obj_to_hide)
|
2019-11-14 17:05:58 +01:00
|
|
|
|
2023-04-06 15:22:04 +02:00
|
|
|
related_btc = bool(request.args.get('related_btc', False))
|
2019-11-22 15:30:11 +01:00
|
|
|
|
2024-03-11 15:33:30 +01:00
|
|
|
filter_types = ail_objects.sanitize_objs_types(request.args.get('filter', '').split(','), default=True)
|
2019-11-14 17:05:58 +01:00
|
|
|
|
2022-10-25 16:25:19 +02:00
|
|
|
# check if obj_id exist
|
|
|
|
if not ail_objects.exists_obj(obj_type, subtype, obj_id):
|
2023-04-05 16:09:06 +02:00
|
|
|
return abort(404)
|
2022-10-25 16:25:19 +02:00
|
|
|
# object exist
|
2023-07-07 16:29:32 +02:00
|
|
|
else: # TODO remove old dict key
|
|
|
|
dict_object = {"type": obj_type,
|
|
|
|
"id": obj_id,
|
|
|
|
"object_type": obj_type,
|
2023-05-26 11:22:12 +02:00
|
|
|
"max_nodes": max_nodes, "mode": mode, "level": level,
|
2022-10-25 16:25:19 +02:00
|
|
|
"filter": filter_types, "filter_str": ",".join(filter_types),
|
2023-07-07 16:29:32 +02:00
|
|
|
"hidden": objs_hidden, "hidden_str": ",".join(objs_hidden),
|
|
|
|
|
|
|
|
"correlation_id": obj_id,
|
2023-04-06 16:18:06 +02:00
|
|
|
"metadata": ail_objects.get_object_meta(obj_type, subtype, obj_id,
|
2023-05-25 16:00:27 +02:00
|
|
|
options={'tags'}, flask_context=True),
|
|
|
|
"nb_correl": ail_objects.get_obj_nb_correlations(obj_type, subtype, obj_id)
|
2022-10-25 16:25:19 +02:00
|
|
|
}
|
|
|
|
if subtype:
|
2023-07-07 16:29:32 +02:00
|
|
|
dict_object["subtype"] = subtype
|
2022-10-25 16:25:19 +02:00
|
|
|
dict_object["metadata"]['type_id'] = subtype
|
2023-07-07 16:29:32 +02:00
|
|
|
else:
|
|
|
|
dict_object["subtype"] = ''
|
2022-10-25 16:25:19 +02:00
|
|
|
dict_object["metadata_card"] = ail_objects.get_object_card_meta(obj_type, subtype, obj_id, related_btc=related_btc)
|
2024-03-18 12:00:41 +01:00
|
|
|
dict_object["metadata_card"]['tags_safe'] = True
|
2023-04-05 16:09:06 +02:00
|
|
|
return render_template("show_correlation.html", dict_object=dict_object, bootstrap_label=bootstrap_label,
|
2024-03-18 12:00:41 +01:00
|
|
|
tags_selector_data=Tag.get_tags_selector_data(),
|
|
|
|
meta=dict_object["metadata_card"],
|
|
|
|
ail_tags=dict_object["metadata_card"]["add_tags_modal"])
|
2019-11-19 09:02:25 +01:00
|
|
|
|
2019-12-09 16:56:07 +01:00
|
|
|
@correlation.route('/correlation/get/description')
|
|
|
|
@login_required
|
|
|
|
@login_read_only
|
|
|
|
def get_description():
|
|
|
|
object_id = request.args.get('object_id')
|
2023-07-17 15:47:17 +02:00
|
|
|
obj_type, subtype, obj_id = ail_objects.get_obj_type_subtype_id_from_global_id(object_id)
|
2019-12-09 16:56:07 +01:00
|
|
|
|
2023-07-17 15:47:17 +02:00
|
|
|
# check if obj exist
|
2019-12-09 16:56:07 +01:00
|
|
|
# # TODO: return error json
|
2023-07-17 15:47:17 +02:00
|
|
|
if not ail_objects.exists_obj(obj_type, subtype, obj_id):
|
2019-12-10 15:41:47 +01:00
|
|
|
return Response(json.dumps({"status": "error", "reason": "404 Not Found"}, indent=2, sort_keys=True), mimetype='application/json'), 404
|
2023-02-28 11:01:27 +01:00
|
|
|
# object exist
|
2019-12-09 16:56:07 +01:00
|
|
|
else:
|
2024-03-11 15:33:30 +01:00
|
|
|
options = {'icon', 'tags', 'tags_safe'}
|
|
|
|
if obj_type == 'message':
|
|
|
|
options.add('content')
|
|
|
|
res = ail_objects.get_object_meta(obj_type, subtype, obj_id, options=options,
|
2023-04-21 15:38:48 +02:00
|
|
|
flask_context=True)
|
2023-04-06 16:18:06 +02:00
|
|
|
if 'tags' in res:
|
|
|
|
res['tags'] = list(res['tags'])
|
2019-12-09 16:56:07 +01:00
|
|
|
return jsonify(res)
|
2019-11-14 17:05:58 +01:00
|
|
|
|
|
|
|
@correlation.route('/correlation/graph_node_json')
|
|
|
|
@login_required
|
2019-11-20 16:15:08 +01:00
|
|
|
@login_read_only
|
2019-12-09 16:56:07 +01:00
|
|
|
def graph_node_json():
|
2022-10-25 16:25:19 +02:00
|
|
|
obj_id = request.args.get('id')
|
|
|
|
subtype = request.args.get('subtype')
|
|
|
|
obj_type = request.args.get('type')
|
2019-11-14 17:05:58 +01:00
|
|
|
max_nodes = sanitise_nb_max_nodes(request.args.get('max_nodes'))
|
2023-05-26 11:22:12 +02:00
|
|
|
level = sanitise_level(request.args.get('level'))
|
2019-11-14 17:05:58 +01:00
|
|
|
|
2023-07-07 16:29:32 +02:00
|
|
|
hidden = request.args.get('hidden')
|
|
|
|
if hidden:
|
|
|
|
hidden = set(hidden.split(','))
|
|
|
|
else:
|
|
|
|
hidden = set()
|
|
|
|
|
2022-10-25 16:25:19 +02:00
|
|
|
filter_types = ail_objects.sanitize_objs_types(request.args.get('filter', '').split(','))
|
2019-11-14 17:05:58 +01:00
|
|
|
|
2023-07-07 16:29:32 +02:00
|
|
|
json_graph = ail_objects.get_correlations_graph_node(obj_type, subtype, obj_id, filter_types=filter_types, max_nodes=max_nodes, level=level, objs_hidden=hidden, flask_context=True)
|
2022-08-19 16:53:31 +02:00
|
|
|
#json_graph = Correlate_object.get_graph_node_object_correlation(obj_type, obj_id, 'union', correlation_names, correlation_objects, requested_correl_type=subtype, max_nodes=max_nodes)
|
|
|
|
return jsonify(json_graph)
|
2022-04-28 15:57:00 +02:00
|
|
|
|
2023-04-06 15:13:27 +02:00
|
|
|
@correlation.route('/correlation/delete', methods=['GET'])
|
|
|
|
@login_required
|
|
|
|
@login_admin
|
|
|
|
def correlation_delete():
|
|
|
|
obj_type = request.args.get('type')
|
|
|
|
subtype = request.args.get('subtype', '')
|
|
|
|
obj_id = request.args.get('id')
|
|
|
|
|
|
|
|
if not ail_objects.exists_obj(obj_type, subtype, obj_id):
|
|
|
|
return abort(404)
|
|
|
|
|
|
|
|
ail_objects.delete_obj_correlations(obj_type, subtype, obj_id)
|
|
|
|
return redirect(url_for('correlation.show_correlation', type=obj_type, subtype=subtype, id=obj_id))
|
|
|
|
|
2023-04-05 16:09:06 +02:00
|
|
|
@correlation.route('/correlation/tags/add', methods=['POST'])
|
|
|
|
@login_required
|
2023-04-06 15:13:27 +02:00
|
|
|
@login_analyst
|
2023-04-05 16:09:06 +02:00
|
|
|
def correlation_tags_add():
|
|
|
|
obj_id = request.form.get('tag_obj_id')
|
|
|
|
subtype = request.form.get('tag_subtype', '')
|
|
|
|
obj_type = request.form.get('tag_obj_type')
|
|
|
|
nb_max = sanitise_nb_max_nodes(request.form.get('tag_nb_max'))
|
2023-05-26 11:22:12 +02:00
|
|
|
level = sanitise_level(request.form.get('tag_level'))
|
2023-04-05 16:09:06 +02:00
|
|
|
filter_types = ail_objects.sanitize_objs_types(request.form.get('tag_filter', '').split(','))
|
2023-07-07 16:29:32 +02:00
|
|
|
hidden = sanitise_objs_hidden(request.form.get('tag_hidden'))
|
2023-04-05 16:09:06 +02:00
|
|
|
|
|
|
|
if not ail_objects.exists_obj(obj_type, subtype, obj_id):
|
|
|
|
return abort(404)
|
|
|
|
|
|
|
|
# tags
|
|
|
|
taxonomies_tags = request.form.get('taxonomies_tags')
|
|
|
|
if taxonomies_tags:
|
|
|
|
try:
|
|
|
|
taxonomies_tags = json.loads(taxonomies_tags)
|
|
|
|
except Exception:
|
|
|
|
taxonomies_tags = []
|
|
|
|
else:
|
|
|
|
taxonomies_tags = []
|
|
|
|
galaxies_tags = request.form.get('galaxies_tags')
|
|
|
|
if galaxies_tags:
|
|
|
|
try:
|
|
|
|
galaxies_tags = json.loads(galaxies_tags)
|
|
|
|
except Exception:
|
|
|
|
galaxies_tags = []
|
|
|
|
if taxonomies_tags or galaxies_tags:
|
|
|
|
if not Tag.is_valid_tags_taxonomies_galaxy(taxonomies_tags, galaxies_tags):
|
|
|
|
return {'error': 'Invalid tag(s)'}, 400
|
|
|
|
tags = taxonomies_tags + galaxies_tags
|
|
|
|
else:
|
|
|
|
tags = []
|
|
|
|
|
|
|
|
if tags:
|
2023-05-26 11:22:12 +02:00
|
|
|
ail_objects.obj_correlations_objs_add_tags(obj_type, subtype, obj_id, tags, filter_types=filter_types,
|
2023-07-07 16:29:32 +02:00
|
|
|
objs_hidden=hidden,
|
2023-05-26 11:22:12 +02:00
|
|
|
lvl=level + 1, nb_max=nb_max)
|
2023-04-05 16:09:06 +02:00
|
|
|
return redirect(url_for('correlation.show_correlation',
|
|
|
|
type=obj_type, subtype=subtype, id=obj_id,
|
2023-05-26 11:22:12 +02:00
|
|
|
level=level,
|
2023-06-02 11:50:21 +02:00
|
|
|
max_nodes=nb_max,
|
2023-07-07 16:29:32 +02:00
|
|
|
hidden=hidden, hidden_str=",".join(hidden),
|
2023-04-05 16:09:06 +02:00
|
|
|
filter=",".join(filter_types)))
|
2024-01-26 15:42:46 +01:00
|
|
|
|
|
|
|
#####################################################################################
|
|
|
|
|
|
|
|
@correlation.route('/relationships/graph_node_json')
|
|
|
|
@login_required
|
|
|
|
@login_read_only
|
|
|
|
def relationships_graph_node_json():
|
|
|
|
obj_id = request.args.get('id')
|
|
|
|
subtype = request.args.get('subtype')
|
|
|
|
obj_type = request.args.get('type')
|
|
|
|
max_nodes = sanitise_nb_max_nodes(request.args.get('max_nodes'))
|
|
|
|
level = sanitise_level(request.args.get('level'))
|
|
|
|
|
|
|
|
json_graph = ail_objects.get_relationships_graph_node(obj_type, subtype, obj_id, max_nodes=max_nodes, level=level, flask_context=True)
|
|
|
|
return jsonify(json_graph)
|
|
|
|
|
|
|
|
|
|
|
|
@correlation.route('/relationship/show', methods=['GET', 'POST'])
|
|
|
|
@login_required
|
|
|
|
@login_read_only
|
|
|
|
def show_relationship():
|
|
|
|
if request.method == 'POST':
|
|
|
|
object_type = request.form.get('obj_type')
|
|
|
|
subtype = request.form.get('subtype')
|
|
|
|
obj_id = request.form.get('obj_id')
|
|
|
|
max_nodes = request.form.get('max_nb_nodes_in')
|
|
|
|
level = sanitise_level(request.form.get('level'))
|
|
|
|
|
|
|
|
# redirect to keep history and bookmark
|
|
|
|
return redirect(url_for('correlation.show_relationship', type=object_type, subtype=subtype, id=obj_id,
|
|
|
|
max_nodes=max_nodes, level=level))
|
|
|
|
|
|
|
|
# request.method == 'GET'
|
|
|
|
else:
|
|
|
|
obj_type = request.args.get('type')
|
|
|
|
subtype = request.args.get('subtype', '')
|
|
|
|
obj_id = request.args.get('id')
|
|
|
|
max_nodes = sanitise_nb_max_nodes(request.args.get('max_nodes'))
|
|
|
|
level = sanitise_level(request.args.get('level'))
|
|
|
|
|
|
|
|
# check if obj_id exist
|
|
|
|
if not ail_objects.exists_obj(obj_type, subtype, obj_id):
|
|
|
|
return abort(404)
|
|
|
|
# object exist
|
|
|
|
else: # TODO remove old dict key
|
|
|
|
dict_object = {"type": obj_type,
|
|
|
|
"id": obj_id,
|
|
|
|
"object_type": obj_type,
|
|
|
|
"max_nodes": max_nodes, "level": level,
|
|
|
|
"correlation_id": obj_id,
|
|
|
|
"metadata": ail_objects.get_object_meta(obj_type, subtype, obj_id, options={'tags', 'info', 'icon', 'username'}, flask_context=True),
|
|
|
|
"nb_relation": ail_objects.get_obj_nb_relationships(obj_type, subtype, obj_id)
|
|
|
|
}
|
|
|
|
if subtype:
|
|
|
|
dict_object["subtype"] = subtype
|
|
|
|
dict_object["metadata"]['type_id'] = subtype
|
|
|
|
else:
|
|
|
|
dict_object["subtype"] = ''
|
2024-04-03 17:39:45 +02:00
|
|
|
dict_object["metadata_card"] = ail_objects.get_object_card_meta(obj_type, subtype, obj_id)
|
|
|
|
dict_object["metadata_card"]['tags_safe'] = True
|
2024-01-26 15:42:46 +01:00
|
|
|
return render_template("show_relationship.html", dict_object=dict_object, bootstrap_label=bootstrap_label,
|
2024-04-03 17:39:45 +02:00
|
|
|
tags_selector_data=Tag.get_tags_selector_data(),
|
|
|
|
meta=dict_object["metadata_card"],
|
|
|
|
ail_tags=dict_object["metadata_card"]["add_tags_modal"])
|
|
|
|
|