Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api v2 delete note #734

Merged
merged 82 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
4cf9278
[CLEAN] Removed spurious blank line
c8y3 Feb 11, 2025
eaa07f7
Started implementation of endpoint POST /api/v2/cases/{case_identifie…
c8y3 Feb 11, 2025
0cda066
Added tests for fields note_title and note_content
c8y3 Feb 11, 2025
de3ee8f
Added tests to create a note within a directory and a parent directory
c8y3 Feb 11, 2025
fb1eb1b
POST /api/v2/cases/{case_identifier}/notes returns 404 when case with…
c8y3 Feb 11, 2025
e20c8f9
POST /api/v2/cases/{case_identifier}/notes returns 400 when directory…
c8y3 Feb 11, 2025
2225c77
POST /api/v2/cases/{case_identifier}/notes returns 400 when directory…
c8y3 Feb 11, 2025
01b87ab
Added test on the presence of field modification_history when creatin…
c8y3 Feb 11, 2025
2d77386
Deprecated POST /case/notes/add in favor of POST /api/v2/cases/{case_…
c8y3 Feb 11, 2025
7daa26a
Removed duplicated piece of code (probably happened during a merge)
c8y3 Feb 12, 2025
0ccf6b0
Moved up imports (ruff E402 warning)
c8y3 Feb 12, 2025
9ba8889
Fixed typo in method name
c8y3 Feb 12, 2025
66e9a93
Trying to move imports up, to honour Ruff E402
c8y3 Feb 12, 2025
b2d7b7c
One import per line
c8y3 Feb 12, 2025
d66e345
Renamed private method which was used outside of its module
c8y3 Feb 12, 2025
3cc3139
Moved method related to users into app.business.users
c8y3 Feb 12, 2025
87b91f2
Moved import up, trying to adhere to Ruff E402
c8y3 Feb 12, 2025
c1369ee
Split celery initiation in two (the part which depends on the app is …
c8y3 Feb 12, 2025
4131322
Split marshmallow creation from Flask app initialization
c8y3 Feb 12, 2025
7650674
Split Flask-bcrypt creation from Flask app initialization
c8y3 Feb 12, 2025
0b4c76f
Added GET /api/v2/cases/{case_identifier}/notes/{identifier} to get a…
c8y3 Feb 12, 2025
7af0c68
Removed unnecessary parameter case_identifier when retrieving a note
c8y3 Feb 12, 2025
a5a403f
Deprecated GET /case/notes/{identifier} in favor of GET /api/v2/cases…
c8y3 Feb 12, 2025
586bdb8
Split SQLAlchemy creation from Flask app initialization
c8y3 Feb 14, 2025
0f1735d
Moving configuration variable declaration out of the __init__ into co…
c8y3 Feb 14, 2025
f9f120b
Moving import up a little
c8y3 Feb 14, 2025
32347d3
Removed seemingly unused variable
c8y3 Feb 14, 2025
16e41d4
Inject application configuration in run_post_init, to make dependenci…
c8y3 Feb 14, 2025
694a419
Inject application configuration deeper
c8y3 Feb 14, 2025
e2767e1
Finished injecting application configuration entirely in post_init
c8y3 Feb 14, 2025
12629cf
One import per line
c8y3 Feb 14, 2025
0f8ba40
One import per line
c8y3 Feb 14, 2025
639fad1
Sorting imports by layers
c8y3 Feb 14, 2025
4f15ffa
Moved configuration of timezone up in __init__, otherwise we may be u…
c8y3 Feb 14, 2025
a2499c2
Using CeleryConfig directly
c8y3 Feb 14, 2025
b03cfb3
Made a class for PostInit, so as to inject everything it needs from t…
c8y3 Feb 14, 2025
01ae164
Using f-strings instead of .format
c8y3 Feb 14, 2025
3e06635
Fixed deepsource PYL-R1710 warning
c8y3 Feb 14, 2025
26f9523
Grouped definition of jinja filters
c8y3 Feb 14, 2025
e83a174
Removed unnecessary f-strings
c8y3 Feb 14, 2025
a517967
Fixed deepsource FLK-E128
c8y3 Feb 14, 2025
077146b
Fixed deepsource FLK-E231
c8y3 Feb 14, 2025
0a4f7fe
Activated ruff rule E231
c8y3 Feb 14, 2025
0560c59
Removed now obsolete unit test suites
c8y3 Feb 14, 2025
5d880f0
Removed unused imports
c8y3 Feb 14, 2025
231e4ba
Fixing some ruff E231 warnings
c8y3 Feb 14, 2025
f650235
Fixing some ruff E231 warnings
c8y3 Feb 14, 2025
bc72b59
Fixing some ruff E231 warnings
c8y3 Feb 14, 2025
6ddbf36
Fixing last ruff E231 warnings
c8y3 Feb 14, 2025
b10c88d
Really activated ruff E231 rule this time
c8y3 Feb 14, 2025
6dec351
Fixed and activated ruff rule E305
c8y3 Feb 14, 2025
bbe9321
Fixed and activated ruff rule W292
c8y3 Feb 14, 2025
c240c88
Fixed an occurence of ruff RET505
c8y3 Feb 14, 2025
89ebe62
Fixed open redirect.
c8y3 Feb 26, 2025
f01f9be
Fixed bug when the next_url at login is absent
c8y3 Feb 26, 2025
478581e
One import per line
c8y3 Feb 26, 2025
afdeb0b
Renamed tests so that it is more precise
c8y3 Feb 26, 2025
e1ae67b
Added test to check create asset accepts field analysis_status_id
c8y3 Feb 28, 2025
332024c
Started implementation of PUT /api/v2/cases/{case_identifier}/notes/{…
c8y3 Feb 28, 2025
e903960
Note update changes value of field note_title
c8y3 Feb 28, 2025
6ac65cf
Simple quotes
c8y3 Feb 28, 2025
4075a3d
Modified code to avoid getting the note twice unnecessarily when upda…
c8y3 Feb 28, 2025
dd106f1
Update note returns 400 when called with an integer note_title
c8y3 Feb 28, 2025
65e0e96
Added test to check that update note returns 400 when directory_id do…
c8y3 Feb 28, 2025
96530a2
Added tests to check that udpate note return 404 when identifiers do …
c8y3 Feb 28, 2025
a0545da
Update IOC should return 404 when case_identifier does not correspond…
c8y3 Feb 28, 2025
4f2a8cd
Code uniformity
c8y3 Feb 28, 2025
94ddb84
Deprecated POST /case/notes/update/{note_id} in favor of PUT /api/v2/…
c8y3 Feb 28, 2025
35ba148
Added test for the presence of close_date after closing a case
c8y3 Feb 28, 2025
ae72e1d
Fixed an occurence of ruff E302 rule
c8y3 Feb 28, 2025
6c6032a
Fixed all occurences of ruff E302 rule and activated the rule
c8y3 Feb 28, 2025
a84c603
Replaced double quotes with simple quotes
c8y3 Feb 28, 2025
0b1af92
Started implementation of delete note
c8y3 Feb 28, 2025
8998444
Fixed update to note should return 403 when user has no access to case
c8y3 Feb 28, 2025
83a1393
Added method to delete note in the business layer
c8y3 Feb 28, 2025
01b40ea
Test delete note returns 403 when user has no access to case
c8y3 Feb 28, 2025
dd13791
Test delete note returns 404 when note does not exist
c8y3 Feb 28, 2025
51c2ec1
Test delete note returns 404 when case does not exist
c8y3 Feb 28, 2025
7b8a0e4
Get note returns 404 after it has been deleted
c8y3 Feb 28, 2025
93147b2
Renamed variables
c8y3 Feb 28, 2025
d2ea27d
Deprecated POST /case/notes/delete/{note_id} in favor of DELETE /api/…
c8y3 Feb 28, 2025
f9a8d2a
Activated ruff rule E303 and fixed all warnings
c8y3 Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[tool.ruff.lint]
preview = true
select = ["E231", "E302", "E303", "E305", "E4", "E7", "E9", "F", "W292"]
ignore = ["E402", "E711", "E712", "E721", "E722", "F821", "F841"]

65 changes: 32 additions & 33 deletions source/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import json
import logging as logger
import os
import urllib.parse
from flask import Flask
from flask import session
from flask_bcrypt import Bcrypt
Expand All @@ -34,8 +33,11 @@
from werkzeug.middleware.proxy_fix import ProxyFix

from app.flask_dropzone import Dropzone
from app.configuration import Config
from app.iris_engine.tasker.celery import make_celery
from app.iris_engine.tasker.celery import set_celery_flask_context
from app.iris_engine.access_control.oidc_handler import get_oidc_client
from app.jinja_filters import register_jinja_filters


class ReverseProxied(object):
Expand Down Expand Up @@ -63,6 +65,15 @@ class AlertsNamespace(Namespace):

logger.basicConfig(level=logger.INFO, format=LOG_FORMAT, datefmt=LOG_TIME_FORMAT)

SQLALCHEMY_ENGINE_OPTIONS = {
"json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict),
"pool_pre_ping": True
}

db = SQLAlchemy(engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy
bc = Bcrypt() # flask-bcrypt
ma = Marshmallow()
celery = make_celery(__name__)
app = Flask(__name__, static_folder='../static')

# CORS(app,
Expand Down Expand Up @@ -90,41 +101,30 @@ def ac_current_user_has_manage_perms():
return False


app.jinja_env.filters['unquote'] = lambda u: urllib.parse.unquote(u)
app.jinja_env.filters['tojsonsafe'] = lambda u: json.dumps(u, indent=4, ensure_ascii=False)
app.jinja_env.filters['tojsonindent'] = lambda u: json.dumps(u, indent=4)
app.jinja_env.filters['escape_dots'] = lambda u: u.replace('.', '[.]')
register_jinja_filters(app.jinja_env)

app.jinja_env.globals.update(user_has_perm=ac_current_user_has_permission)
app.jinja_env.globals.update(user_has_manage_perms=ac_current_user_has_manage_perms)
app.jinja_options["autoescape"] = lambda _: True
app.jinja_options['autoescape'] = lambda _: True
app.jinja_env.autoescape = True

app.config.from_object('app.configuration.Config')
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax'
)
app.config.from_object(Config)
app.config['timezone'] = 'Europe/Paris'
from app.post_init import PostInit

cache = Cache(app)

SQLALCHEMY_ENGINE_OPTIONS = {
"json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict),
"pool_pre_ping": True
}

db = SQLAlchemy(app, engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy
db.init_app(app)

bc = Bcrypt(app) # flask-bcrypt
bc.init_app(app)

lm = LoginManager() # flask-loginmanager
lm.init_app(app) # init the login manager

ma = Marshmallow(app) # Init marshmallow
ma.init_app(app)

dropzone = Dropzone(app)

celery = make_celery(app)
set_celery_flask_context(celery, app)

# store = HttpExposedFileSystemStore(
# path='images',
Expand All @@ -140,13 +140,18 @@ def ac_current_user_has_manage_perms():
socket_io.on_namespace(alerts_namespace)

oidc_client = None
if app.config.get('AUTHENTICATION_TYPE') == "oidc":
oidc_client = get_oidc_client(app)
if app.config.get('AUTHENTICATION_TYPE') == 'oidc':
oidc_client = get_oidc_client(app.config, app.logger)
from app.views import register_blueprints
from app.views import load_user
from app.views import load_user_from_request


@app.teardown_appcontext
def shutdown_session(exception=None):
db.session.remove()


@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
Expand All @@ -157,17 +162,11 @@ def after_request(response):
return response


from app.views import register_blusprints
from app.views import load_user
from app.views import load_user_from_request

register_blusprints(app)

from app.post_init import run_post_init
register_blueprints(app)

try:

run_post_init(development=app.config['DEVELOPMENT'])
post_init = PostInit(app)
post_init.run()

except Exception as e:
app.logger.exception('Post init failed. IRIS not started')
Expand Down
2 changes: 1 addition & 1 deletion source/app/alembic/alembic_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ def index_exists(table_name, index_name):
)
inspector = reflection.Inspector.from_engine(engine)
indexes = inspector.get_indexes(table_name)
return any(index['name'] == index_name for index in indexes)
return any(index['name'] == index_name for index in indexes)
6 changes: 3 additions & 3 deletions source/app/alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
from alembic import context
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from app.configuration import SQLALCHEMY_BASE_ADMIN_URI
from app.configuration import PG_DB_

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
Expand All @@ -11,11 +14,8 @@
# This line sets up loggers basically.
fileConfig(config.config_file_name)

import os
os.environ["ALEMBIC"] = "1"

from app.configuration import SQLALCHEMY_BASE_ADMIN_URI, PG_DB_

config.set_main_option('sqlalchemy.url', SQLALCHEMY_BASE_ADMIN_URI + PG_DB_)

# add your model's MetaData object here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def upgrade():
op.alter_column('ioc', 'case_id', nullable=True)
return


# Fetch all ioc_link rows
ioc_links = conn.execute(text("SELECT ioc_id, case_id FROM ioc_link")).fetchall()
if not ioc_links:
Expand Down Expand Up @@ -146,7 +145,6 @@ def upgrade():
case_id = row.case_id
events_by_ioc_and_case.setdefault((ioc_id, case_id), []).append(row.event_id)


# We'll keep track of which (ioc_id, case_id) pairs we've already handled
# so we don't do duplicate work if multiple ioc_link rows refer to the same pair.
already_handled = set()
Expand Down Expand Up @@ -231,7 +229,6 @@ def upgrade():
# Update our global in-memory map so future checks won't create a second duplicate
existing_map[key] = new_ioc_id


# Move ioc_comments to the new ioc
if ioc_id in comments_by_ioc:
old_comment_links = comments_by_ioc[ioc_id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def _get_icons(asset_name):
"Windows Account - AD - krbtgt": ("Windows Account - AD - krbtgt", "user.png", "ioc_user.png"),
"Windows Account - AD - Service": ("Windows Account - AD - krbtgt", "user.png", "ioc_user.png")
}
if assets.get(asset_name):
return assets.get(asset_name)[1], assets.get(asset_name)[2]
else:
return "question-mark.png","ioc_question-mark.png"
if not assets.get(asset_name):
return "question-mark.png", "ioc_question-mark.png"
return assets.get(asset_name)[1], assets.get(asset_name)[2]
1 change: 1 addition & 0 deletions source/app/blueprints/graphql/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def resolve_case(root, info, case_id):
permissions_check_current_user_has_some_case_access(case_id, [CaseAccessLevel.full_access])
return Cases.query.get(case_id)


class CaseConnection(Connection):
class Meta:
node = CaseObject
Expand Down
3 changes: 1 addition & 2 deletions source/app/blueprints/graphql/graphql_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@
import warnings

# Ignore all UserWarnings
warnings.filterwarnings("ignore", category=UserWarning)

warnings.filterwarnings('ignore', category=UserWarning)


class Query(ObjectType):
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/pages/case/case_notes_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def case_comment_note_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_note.case_note', cid=caseid, redirect=True))

note = get_note(cur_id, caseid=caseid)
note = get_note(cur_id)
if not note:
return response_error('Invalid note ID')

Expand Down
16 changes: 9 additions & 7 deletions source/app/blueprints/pages/login/login_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
from app import bc
from app import db
from app import oidc_client
from app.blueprints.access_controls import is_authentication_oidc, is_authentication_ldap
from app.blueprints.access_controls import is_authentication_oidc
from app.blueprints.access_controls import is_authentication_ldap
from app.blueprints.responses import response_error
from app.business.auth import validate_ldap_login, _retrieve_user_by_username, wrap_login_user
from app.business.auth import validate_ldap_login
from app.business.users import retrieve_user_by_username
from app.business.auth import wrap_login_user
from app.datamgmt.manage.manage_users_db import create_user
from app.datamgmt.manage.manage_users_db import get_user
from app.forms import LoginForm, MFASetupForm
Expand All @@ -56,7 +59,6 @@
# filter User out of database through username



def _render_template_login(form, msg):
organisation_name = app.config.get('ORGANISATION_NAME')
login_banner = app.config.get('LOGIN_BANNER_TEXT')
Expand All @@ -68,7 +70,7 @@ def _render_template_login(form, msg):


def _validate_local_login(username, password):
user = _retrieve_user_by_username(username)
user = retrieve_user_by_username(username)
if not user:
return None

Expand All @@ -93,7 +95,7 @@ def _authenticate_ldap(form, username, password, local_fallback=True):


def _authenticate_password(form, username, password):
user = _retrieve_user_by_username(username)
user = retrieve_user_by_username(username)
if not user or user.is_service_account:
return _render_template_login(form, 'Wrong credentials. Please try again.')

Expand Down Expand Up @@ -223,7 +225,7 @@ def oidc_authorise():

@app.route('/auth/mfa-setup', methods=['GET', 'POST'])
def mfa_setup():
user = _retrieve_user_by_username(username=session['username'])
user = retrieve_user_by_username(username=session['username'])
form = MFASetupForm()

if form.submit() and form.validate():
Expand Down Expand Up @@ -275,7 +277,7 @@ def mfa_verify():

return redirect(url_for('login.login'))

user = _retrieve_user_by_username(username=session['username'])
user = retrieve_user_by_username(username=session['username'])

# Redirect user to MFA setup if MFA is not fully set up
if not user.mfa_secrets or not user.mfa_setup_complete:
Expand Down
1 change: 0 additions & 1 deletion source/app/blueprints/rest/alerts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,6 @@ def alerts_batch_escalate_route() -> Response:
return response_error(str(e))



@alerts_rest_blueprint.route('/alerts/<int:alert_id>/comments/list', methods=['GET'])
@ac_api_requires(Permissions.alerts_read)
def alert_comments_get(alert_id):
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/case/case_assets_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,4 @@ def case_comment_asset_delete(cur_id, com_id, caseid):
call_modules_hook('on_postload_asset_comment_delete', data=com_id, caseid=caseid)

track_activity(f"comment {com_id} on asset {cur_id} deleted", caseid=caseid)
return response_success(msg)
return response_success(msg)
Loading