Skip to content

Commit 7c69e01

Browse files
authored
Merge pull request #931 from dfir-iris/v2_api_notes_directories_filter
Api v2 notes directories filter
2 parents 2973772 + 1cf72a9 commit 7c69e01

File tree

9 files changed

+108
-28
lines changed

9 files changed

+108
-28
lines changed

source/app/blueprints/rest/case/case_notes_routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ def case_get_notes_group(cur_id, caseid):
366366

367367

368368
@case_notes_rest_blueprint.route('/case/notes/directories/filter', methods=['GET'])
369+
@endpoint_deprecated('GET', '/api/v2/cases/{case_identifier}/notes-directories')
369370
@ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
370371
@ac_api_requires()
371372
def case_filter_notes_directories(caseid):

source/app/blueprints/rest/v2/case_routes/evidences.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
from flask import request
2121
from marshmallow import ValidationError
2222

23-
from app.blueprints.access_controls import ac_api_requires, ac_fast_check_current_user_has_case_access
23+
from app.blueprints.access_controls import ac_api_requires
24+
from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access
2425
from app.models.authorization import CaseAccessLevel
2526
from app.business.errors import BusinessProcessingError
2627
from app.business.errors import ObjectNotFoundError

source/app/blueprints/rest/v2/case_routes/notes_directories.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,34 @@
2020
from flask import request
2121
from marshmallow import ValidationError
2222

23-
from app.blueprints.access_controls import ac_api_requires, ac_fast_check_current_user_has_case_access
23+
from app.blueprints.access_controls import ac_api_requires
24+
from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access
25+
from app.blueprints.rest.parsing import parse_pagination_parameters
2426
from app.blueprints.rest.endpoints import response_api_created
2527
from app.blueprints.rest.endpoints import response_api_success
2628
from app.blueprints.rest.endpoints import response_api_deleted
2729
from app.blueprints.rest.endpoints import response_api_error
2830
from app.blueprints.rest.endpoints import response_api_not_found
31+
from app.blueprints.rest.endpoints import response_api_paginated
2932
from app.blueprints.access_controls import ac_api_return_access_denied
3033
from app.business.errors import ObjectNotFoundError
3134
from app.business.errors import BusinessProcessingError
3235
from app.schema.marshables import CaseNoteDirectorySchema
36+
from app.schema.marshables import SearchCaseNoteDirectorySchema
3337
from app.business.notes_directories import notes_directories_create
3438
from app.business.notes_directories import notes_directories_get
3539
from app.business.notes_directories import notes_directories_update
3640
from app.business.notes_directories import notes_directories_delete
3741
from app.business.cases import cases_exists
42+
from app.business.notes_directories import notes_directories_filter
3843
from app.models.authorization import CaseAccessLevel
3944

4045

4146
class NotesDirectories:
4247

4348
def __init__(self):
4449
self._schema = CaseNoteDirectorySchema()
50+
self._schema_search = SearchCaseNoteDirectorySchema(exclude=('parent_id', 'case_id'))
4551

4652
@staticmethod
4753
def _get_note_directory_in_case(identifier, case_identifier):
@@ -53,6 +59,16 @@ def _get_note_directory_in_case(identifier, case_identifier):
5359
def _load(self, request_data, **kwargs):
5460
return self._schema.load(request_data, **kwargs)
5561

62+
def search(self, case_identifier):
63+
if not cases_exists(case_identifier):
64+
return response_api_not_found()
65+
if not ac_fast_check_current_user_has_case_access(case_identifier, [CaseAccessLevel.full_access]):
66+
return ac_api_return_access_denied(case_identifier)
67+
68+
pagination_parameters = parse_pagination_parameters(request, default_order_by='name', default_direction='asc')
69+
directories = notes_directories_filter(case_identifier, pagination_parameters)
70+
return response_api_paginated(self._schema_search, directories)
71+
5672
def create(self, case_identifier):
5773
if not cases_exists(case_identifier):
5874
return response_api_not_found()
@@ -69,9 +85,8 @@ def create(self, case_identifier):
6985
directory = self._load(request_data)
7086

7187
notes_directories_create(directory)
72-
result = self._schema.dump(directory)
7388

74-
return response_api_created(result)
89+
return response_api_created(self._schema.dump(directory))
7590
except ValidationError as e:
7691
return response_api_error('Data error', data=e.normalized_messages())
7792

@@ -85,8 +100,7 @@ def get(self, case_identifier, identifier):
85100
try:
86101
note_directory = self._get_note_directory_in_case(identifier, case_identifier)
87102

88-
result = self._schema.dump(note_directory)
89-
return response_api_success(result)
103+
return response_api_success(self._schema.dump(note_directory))
90104
except ObjectNotFoundError:
91105
return response_api_not_found()
92106
except BusinessProcessingError as e:
@@ -107,8 +121,7 @@ def update(self, case_identifier, identifier):
107121
self._schema.verify_parent_id(request_data['parent_id'], case_id=case_identifier, current_id=identifier)
108122
new_directory = self._load(request_data, instance=directory, partial=True)
109123
notes_directories_update(new_directory)
110-
result = self._schema.dump(new_directory)
111-
return response_api_success(result)
124+
return response_api_success(self._schema.dump(new_directory))
112125
except ValidationError as e:
113126
return response_api_error('Data error', data=e.normalized_messages())
114127
except ObjectNotFoundError:
@@ -154,3 +167,9 @@ def update_note_directory(case_identifier, identifier):
154167
@ac_api_requires()
155168
def delete_note_directory(case_identifier, identifier):
156169
return notes_directories.delete(case_identifier, identifier)
170+
171+
172+
@case_notes_directories_blueprint.get('')
173+
@ac_api_requires()
174+
def get_note_directory_filter(case_identifier):
175+
return notes_directories.search(case_identifier)

source/app/business/notes_directories.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
from app.business.errors import ObjectNotFoundError
2323
from app.datamgmt.case.case_notes_db import get_directory
2424
from app.datamgmt.case.case_notes_db import delete_directory
25+
from app.datamgmt.case.case_notes_db import paginate_notes_directories
26+
from app.models.pagination_parameters import PaginationParameters
27+
28+
29+
def notes_directories_filter(case_identifier: int, pagination_parameters: PaginationParameters):
30+
return paginate_notes_directories(case_identifier, pagination_parameters)
2531

2632

2733
def notes_directories_create(directory: NoteDirectory):

source/app/datamgmt/case/case_notes_db.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
from sqlalchemy import or_
2121
from sqlalchemy.exc import IntegrityError
2222
from datetime import datetime
23+
from flask_sqlalchemy.pagination import Pagination
2324

2425
from app import db
2526
from app.datamgmt.persistence_error import PersistenceError
2627
from app.blueprints.iris_user import iris_current_user
2728
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
2829
from app.datamgmt.states import update_notes_state
29-
from app.models.comments import Comments, NotesComments
30+
from app.models.comments import Comments
31+
from app.models.comments import NotesComments
3032
from app.models.models import NoteDirectory
3133
from app.models.models import NoteRevisions
3234
from app.models.models import Notes
@@ -35,6 +37,8 @@
3537
from app.models.authorization import User
3638
from app.models.cases import Cases
3739
from app.models.models import Client
40+
from app.models.pagination_parameters import PaginationParameters
41+
from app.datamgmt.filtering import paginate
3842

3943

4044
def get_note(note_id):
@@ -164,7 +168,7 @@ def update_note_revision(note: Notes) -> bool:
164168
raise PersistenceError(e)
165169

166170

167-
def add_note(note_title, creation_date, user_id, caseid, directory_id, note_content=""):
171+
def add_note(note_title, creation_date, user_id, caseid, directory_id, note_content=''):
168172
note = Notes()
169173
note.note_title = note_title
170174
note.note_creationdate = note.note_lastupdate = creation_date
@@ -279,7 +283,7 @@ def add_note_group(group_title, caseid, userid, creationdate):
279283
db.session.commit()
280284

281285
if group_title == '':
282-
ng.group_title = "New notes group"
286+
ng.group_title = 'New notes group'
283287

284288
db.session.commit()
285289

@@ -420,7 +424,7 @@ def delete_note_comment(note_id, comment_id):
420424
Comments.comment_user_id == iris_current_user.id
421425
).first()
422426
if not comment:
423-
return False, "You are not allowed to delete this comment"
427+
return False, 'You are not allowed to delete this comment'
424428

425429
NotesComments.query.filter(
426430
NotesComments.comment_note_id == note_id,
@@ -430,7 +434,7 @@ def delete_note_comment(note_id, comment_id):
430434
db.session.delete(comment)
431435
db.session.commit()
432436

433-
return True, "Comment deleted"
437+
return True, 'Comment deleted'
434438

435439

436440
def get_directories_with_note_count(case_id):
@@ -454,6 +458,12 @@ def get_directories_with_note_count(case_id):
454458
return directories_with_note_count
455459

456460

461+
def paginate_notes_directories(case_id, pagination_parameters: PaginationParameters) -> Pagination:
462+
query = NoteDirectory.query.filter_by(case_id=case_id)
463+
464+
return paginate(NoteDirectory, pagination_parameters, query)
465+
466+
457467
def get_directory_with_note_count(directory):
458468
note_count = Notes.query.filter_by(directory_id=directory.id).count()
459469

source/app/datamgmt/case/case_rfiles_db.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
2626
from app.datamgmt.states import update_evidences_state
2727
from app.models.models import CaseReceivedFile
28-
from app.models.comments import Comments, EvidencesComments
28+
from app.models.comments import Comments
29+
from app.models.comments import EvidencesComments
2930
from app.models.authorization import User
3031
from app.models.pagination_parameters import PaginationParameters
31-
from app.datamgmt.conversions import convert_sort_direction
32+
from app.datamgmt.filtering import paginate
3233

3334

3435
def get_rfiles(caseid):
@@ -45,18 +46,8 @@ def get_paginated_evidences(case_identifier, pagination_parameters: PaginationPa
4546
query = CaseReceivedFile.query.filter(
4647
CaseReceivedFile.case_id == case_identifier
4748
)
48-
order_func = convert_sort_direction(pagination_parameters.get_direction())
4949

50-
order_by = pagination_parameters.get_order_by()
51-
column = getattr(CaseReceivedFile, order_by)
52-
53-
query = query.order_by(order_func(column))
54-
55-
return query.paginate(
56-
page=pagination_parameters.get_page(),
57-
per_page=pagination_parameters.get_per_page(),
58-
error_out=False
59-
)
50+
return paginate(CaseReceivedFile, pagination_parameters, query)
6051

6152

6253
def add_rfile(evidence: CaseReceivedFile, caseid, user_id):

source/app/datamgmt/filtering.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,15 @@ def get_filtered_data(model,
209209
log.exception(e)
210210
raise BusinessProcessingError(f'Error parsing custom_conditions: {e}')
211211

212-
# Apply ordering if requested.
212+
return paginate(model, pagination_parameters, query)
213+
214+
215+
def paginate(model, pagination_parameters: PaginationParameters, query):
213216
order_by = pagination_parameters.get_order_by()
214217
if order_by is not None and hasattr(model, order_by):
215218
order_func = convert_sort_direction(pagination_parameters.get_direction())
216-
query = query.order_by(order_func(getattr(model, order_by)))
219+
column = getattr(model, order_by)
220+
query = query.order_by(order_func(column))
217221

218222
# Paginate and return the results.
219223
result = query.paginate(

source/app/schema/marshables.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,20 @@ def verify_directory_name(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str
231231
return data
232232

233233

234+
class SearchCaseNoteDirectorySchema(CaseNoteDirectorySchema):
235+
"""Schema for serializing and deserializing SearchCaseNoteDirectory objects.
236+
237+
This schema defines the fields to include when serializing and deserializing CaseNoteDirectory objects.
238+
It includes fields for the CSRF token, directory name, directory description, and directory ID.
239+
It also includes a method for verifying the directory name.
240+
241+
"""
242+
243+
note_count: int = fields.Integer(required=False)
244+
subdirectories: List[str] = fields.List(fields.String, required=False)
245+
notes: List[str] = fields.List(fields.String, required=False)
246+
247+
234248
class UserSchema(ma.SQLAlchemyAutoSchema):
235249
"""Schema for serializing and deserializing User objects.
236250

tests/tests_rest_notes_directories.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,37 @@ def test_delete_note_directory_should_return_403_when_user_has_no_access_to_case
252252
user = self._subject.create_dummy_user()
253253
response = user.delete(f'/api/v2/cases/{case_identifier}/notes-directories/{identifier}')
254254
self.assertEqual(403, response.status_code)
255+
256+
def test_get_notes_directories_filter_should_return_200(self):
257+
case_identifier = self._subject.create_dummy_case()
258+
body = {'name': 'directory_name'}
259+
self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', body).json()
260+
261+
response = self._subject.get(f'/api/v2/cases/{case_identifier}/notes-directories')
262+
self.assertEqual(200, response.status_code)
263+
264+
def test_get_notes_directories_should_return_403_when_user_has_no_access_to_case(self):
265+
case_identifier = self._subject.create_dummy_case()
266+
body = {'name': 'directory_name'}
267+
self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', body).json()
268+
269+
user = self._subject.create_dummy_user()
270+
response = user.get(f'/api/v2/cases/{case_identifier}/notes-directories')
271+
self.assertEqual(403, response.status_code)
272+
273+
def test_get_notes_directories_filter_should_return_total(self):
274+
case_identifier = self._subject.create_dummy_case()
275+
body = {'name': 'directory_name'}
276+
self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', body).json()
277+
278+
response = self._subject.get(f'/api/v2/cases/{case_identifier}/notes-directories').json()
279+
self.assertEqual(1, response['total'])
280+
281+
def test_get_notes_directories_filter_should_accept_per_page_query_parameter(self):
282+
case_identifier = self._subject.create_dummy_case()
283+
body = {'name': 'directory_name'}
284+
self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', body).json()
285+
self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', body).json()
286+
287+
response = self._subject.get(f'/api/v2/cases/{case_identifier}/notes-directories', {'per_page': 1}).json()
288+
self.assertEqual(1, len(response['data']))

0 commit comments

Comments
 (0)