adapting times POST, PUT, DELETE to properly include the special case, where a BSMD user should also be allowed to handle times entries of an AGENCY, if the agency has the flag set.
This commit is contained in:
parent
83520a2bc7
commit
cd8c246d72
@ -299,6 +299,11 @@ class SQLQuery():
|
|||||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?"
|
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?"
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_participant_from_id()->str:
|
||||||
|
query = "SELECT id, type, flags FROM participant WHERE id=?participant_id?"
|
||||||
|
return query
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_shipcall_post(schemaModel:dict)->str:
|
def get_shipcall_post(schemaModel:dict)->str:
|
||||||
# a pytest proves this method to be identical to create_sql_query_shipcall_post(schemaModel)
|
# a pytest proves this method to be identical to create_sql_query_shipcall_post(schemaModel)
|
||||||
@ -362,6 +367,11 @@ class SQLQuery():
|
|||||||
query = "SELECT id, participant_id, type FROM shipcall_participant_map where shipcall_id = ?id?"
|
query = "SELECT id, participant_id, type FROM shipcall_participant_map where shipcall_id = ?id?"
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_shipcall_participant_map_by_shipcall_id_and_type()->str:
|
||||||
|
query = "SELECT id, participant_id FROM shipcall_participant_map where (shipcall_id = ?id? AND type=?type?)"
|
||||||
|
return query
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_shipcall_participant_map_delete_by_id()->str:
|
def get_shipcall_participant_map_delete_by_id()->str:
|
||||||
query = "DELETE FROM shipcall_participant_map WHERE id = ?existing_id?"
|
query = "DELETE FROM shipcall_participant_map WHERE id = ?existing_id?"
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from BreCal.impl.times import GetTimes
|
|||||||
|
|
||||||
from BreCal.database.enums import ParticipantType, ParticipantFlag
|
from BreCal.database.enums import ParticipantType, ParticipantFlag
|
||||||
from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, check_if_shipcall_id_is_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data, get_participant_id_dictionary, check_if_participant_id_is_valid_standalone
|
from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, check_if_shipcall_id_is_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data, get_participant_id_dictionary, check_if_participant_id_is_valid_standalone
|
||||||
|
from BreCal.database.sql_queries import SQLQuery
|
||||||
from BreCal.database.sql_handler import execute_sql_query_standalone
|
from BreCal.database.sql_handler import execute_sql_query_standalone
|
||||||
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag, check_if_string_has_special_characters
|
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag, check_if_string_has_special_characters
|
||||||
import werkzeug
|
import werkzeug
|
||||||
@ -281,44 +282,50 @@ class InputValidationTimes():
|
|||||||
This method does not validate, what the POST-request contains, but it validates, whether the *user* is
|
This method does not validate, what the POST-request contains, but it validates, whether the *user* is
|
||||||
authorized to send the request.
|
authorized to send the request.
|
||||||
|
|
||||||
|
This method also checks for a special case: when an assigned AGENCY participant has the .BSMD flag enabled,
|
||||||
|
a user of type BSMD may also post the times dataset.
|
||||||
|
|
||||||
options:
|
options:
|
||||||
spm_shipcall_data:
|
spm_shipcall_data:
|
||||||
data from the ShipcallParticipantMap, which refers to the respective shipcall ID. The SPM can be
|
data from the ShipcallParticipantMap, which refers to the respective shipcall ID. The SPM can be
|
||||||
an optional argument to allow for much easier unit testing.
|
an optional argument to allow for much easier unit testing.
|
||||||
"""
|
"""
|
||||||
|
### TIMES DATASET (ShipcallParticipantMap) ###
|
||||||
# identify shipcall_id
|
# identify shipcall_id
|
||||||
shipcall_id = loadedModel["shipcall_id"]
|
shipcall_id = loadedModel["shipcall_id"]
|
||||||
|
DATASET_participant_type = ParticipantType(loadedModel["participant_type"]) if not isinstance(loadedModel["participant_type"],ParticipantType) else loadedModel["participant_type"]
|
||||||
# identify user's participant_id & type (get all participants; then filter these for the {participant_id})
|
|
||||||
participant_id = user_data["participant_id"] #participants = get_participant_id_dictionary() #participant_type = ParticipantType(participants.get(participant_id,{}).get("type"))
|
|
||||||
participant_type = ParticipantType(loadedModel["participant_type"]) if not isinstance(loadedModel["participant_type"],ParticipantType) else loadedModel["participant_type"]
|
|
||||||
|
|
||||||
# get ShipcallParticipantMap for the shipcall_id
|
# get ShipcallParticipantMap for the shipcall_id
|
||||||
if spm_shipcall_data is None:
|
if spm_shipcall_data is None:
|
||||||
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
||||||
# creates a list of {'participant_id: ..., 'type': ...} elements
|
# creates a list of {'participant_id: ..., 'type': ...} elements
|
||||||
spm_shipcall_data = execute_sql_query_standalone(
|
spm_shipcall_data = execute_sql_query_standalone(
|
||||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?",
|
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE (shipcall_id=?shipcall_id? AND type=?type?)",
|
||||||
param={"shipcall_id":shipcall_id},
|
param={"shipcall_id":shipcall_id, "type":int(DATASET_participant_type)},
|
||||||
pooledConnection=None
|
pooledConnection=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DATASET_participant_id = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type=DATASET_participant_type, spm_shipcall_data=spm_shipcall_data)
|
||||||
|
|
||||||
|
### USER DATA (token) ###
|
||||||
|
# identify user's participant_id & type (get all participants; then filter these for the {participant_id})
|
||||||
|
user_participant_id = user_data["participant_id"] #participants = get_participant_id_dictionary() #participant_type = ParticipantType(participants.get(participant_id,{}).get("type"))
|
||||||
|
|
||||||
|
if (ParticipantType.AGENCY in DATASET_participant_type):
|
||||||
|
special_case__bsmd_may_edit_agency_dataset = InputValidationTimes.check_if_bsmd_may_edit_agency_dataset(user_participant_id, DATASET_participant_id, DATASET_participant_type)
|
||||||
|
if (special_case__bsmd_may_edit_agency_dataset):
|
||||||
|
# when a BSMD user posts a dataset of an AGENCY with BSMD-flag, there is no violation
|
||||||
|
return
|
||||||
|
|
||||||
# check, if participant_id is assigned to the ShipcallParticipantMap
|
# check, if participant_id is assigned to the ShipcallParticipantMap
|
||||||
matching_spm = [
|
matching_spm = [
|
||||||
spm
|
spm
|
||||||
for spm in spm_shipcall_data
|
for spm in spm_shipcall_data
|
||||||
if spm.get("participant_id")==participant_id
|
if spm.get("participant_id")==user_participant_id
|
||||||
]
|
]
|
||||||
|
|
||||||
if not len(matching_spm)>0:
|
if not len(matching_spm)>0:
|
||||||
raise ValidationError(f'The participant group with id {participant_id} is not assigned to the shipcall. Found ShipcallParticipantMap: {spm_shipcall_data}')
|
raise ValidationError(f'The participant group with id {user_participant_id} is not assigned to the shipcall. Found ShipcallParticipantMap: {spm_shipcall_data}')
|
||||||
|
|
||||||
# check, if the assigned participant_id is assigned with the same role
|
|
||||||
matching_spm_element = matching_spm[0]
|
|
||||||
matching_spm_element_participant_type = ParticipantType(matching_spm_element.get("type"))
|
|
||||||
if not matching_spm_element_participant_type in participant_type:
|
|
||||||
raise ValidationError(f'The participant group with id {participant_id} is assigned to the shipcall in a different role. Request Role: {participant_type}, ShipcallParticipantMap Role Assignment: {matching_spm_element_participant_type}')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -352,6 +359,9 @@ class InputValidationTimes():
|
|||||||
|
|
||||||
DELETE:
|
DELETE:
|
||||||
times_id is used to directly identify the matching times entry
|
times_id is used to directly identify the matching times entry
|
||||||
|
|
||||||
|
A special exception takes place, when a participant of type AGENCY is involved. In those times-entries, users with the
|
||||||
|
IS_BSMD-Flag may also edit the entry.
|
||||||
"""
|
"""
|
||||||
assert not ((loadedModel is None) and (times_id is None)), f"must provide either loadedModel OR times_id. Both are 'None'"
|
assert not ((loadedModel is None) and (times_id is None)), f"must provide either loadedModel OR times_id. Both are 'None'"
|
||||||
assert (loadedModel is None) or (times_id is None), f"must provide either loadedModel OR times_id. Both are defined."
|
assert (loadedModel is None) or (times_id is None), f"must provide either loadedModel OR times_id. Both are defined."
|
||||||
@ -359,28 +369,18 @@ class InputValidationTimes():
|
|||||||
# identify the user's participant id
|
# identify the user's participant id
|
||||||
user_participant_id = user_data["participant_id"]
|
user_participant_id = user_data["participant_id"]
|
||||||
|
|
||||||
|
# commonly used in the PUT-request
|
||||||
if loadedModel is not None:
|
if loadedModel is not None:
|
||||||
shipcall_id = loadedModel["shipcall_id"]
|
shipcall_id = loadedModel["shipcall_id"]
|
||||||
participant_type = loadedModel["participant_type"]
|
participant_type = loadedModel["participant_type"]
|
||||||
|
|
||||||
# get all times entries of the shipcall_id from the database as a list of {'participant_id':..., 'participant_type':...} elements
|
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
|
||||||
query = "SELECT participant_id, participant_type FROM times WHERE shipcall_id = ?shipcall_id?"
|
participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
|
||||||
times = execute_sql_query_standalone(query=query, param={"shipcall_id":shipcall_id}, pooledConnection=None)
|
|
||||||
|
|
||||||
# get the matching datasets, where the participant id is identical
|
|
||||||
time_datasets_of_participant_type = [time_ for time_ in times if time_.get("participant_type")==participant_type]
|
|
||||||
|
|
||||||
# when there are no matching participants, raise a ValidationError
|
|
||||||
if not len(time_datasets_of_participant_type)>0:
|
|
||||||
raise ValidationError(f"Could not find a matching time dataset for the provided participant_type: {participant_type}. Found Time Datasets: {times}")
|
|
||||||
|
|
||||||
# take the first match. There should always be only one match.
|
|
||||||
time_datasets_of_participant_type = time_datasets_of_participant_type[0]
|
|
||||||
participant_id_of_times_dataset = time_datasets_of_participant_type.get("participant_id")
|
|
||||||
|
|
||||||
|
# commonly used in the DELETE-request
|
||||||
if times_id is not None:
|
if times_id is not None:
|
||||||
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
|
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
|
||||||
query = "SELECT participant_id FROM times WHERE id = ?id?"
|
query = "SELECT participant_id, participant_type FROM times WHERE id = ?id?"
|
||||||
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
|
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
|
||||||
|
|
||||||
# extracts the participant_id from the first matching entry, if applicable
|
# extracts the participant_id from the first matching entry, if applicable
|
||||||
@ -388,15 +388,80 @@ class InputValidationTimes():
|
|||||||
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
|
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
|
||||||
raise ValidationError(f"Unknown times_id. Could not find a matching entry for ID: {times_id}")
|
raise ValidationError(f"Unknown times_id. Could not find a matching entry for ID: {times_id}")
|
||||||
else:
|
else:
|
||||||
participant_id_of_times_dataset = pdata[0].get("participant_id")
|
participant_type = pdata[0].get("participant_type")
|
||||||
|
shipcall_id = pdata[0].get("shipcall_id")
|
||||||
|
|
||||||
|
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
|
||||||
|
participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
|
||||||
|
|
||||||
|
# when the user's participant id is different from the times dataset, an exception is raised
|
||||||
if user_participant_id != participant_id_of_times_dataset:
|
if user_participant_id != participant_id_of_times_dataset:
|
||||||
|
# for some AGENCY participants, users with the BSMD flag may also edit the datasets
|
||||||
|
special_case__bsmd_may_edit_agency_dataset = InputValidationTimes.check_if_bsmd_may_edit_agency_dataset(user_participant_id, participant_id_of_times_dataset, participant_type)
|
||||||
|
if special_case__bsmd_may_edit_agency_dataset:
|
||||||
|
return
|
||||||
|
else:
|
||||||
raise ValidationError(f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}")
|
raise ValidationError(f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_participant_id_from_shipcall_participant_map(shipcall_id:int, participant_type:int, spm_shipcall_data=None)->int:
|
||||||
|
"""use shipcall_id and participant_type to identify the matching participant_id"""
|
||||||
|
if spm_shipcall_data is None:
|
||||||
|
spm_shipcall_data = execute_sql_query_standalone(
|
||||||
|
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
||||||
|
param={"id":shipcall_id, "type":participant_type},
|
||||||
|
command_type="query") # returns a list of matches
|
||||||
|
|
||||||
|
# raise an error when there are no matches
|
||||||
|
if len(spm_shipcall_data)==0:
|
||||||
|
raise ValidationError(f"Could not find a matching time dataset for the provided participant_type: {participant_type} at shipcall with id {shipcall_id}.")
|
||||||
|
|
||||||
|
participant_id_of_times_dataset = spm_shipcall_data[0].get("participant_id")
|
||||||
|
return participant_id_of_times_dataset
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_if_bsmd_may_edit_agency_dataset(user_participant_id:int, participant_id_of_times_dataset:int, participant_type:ParticipantType)->bool:
|
||||||
|
"""
|
||||||
|
This method determines, whether a BSMD user is allowed to edit an AGENCY dataset.
|
||||||
|
When the dataset does not refer to an agency, the method is not applicable (returns False).
|
||||||
|
|
||||||
|
If it is applicable,
|
||||||
|
a) find out, whether the assigned participant (AGENCY) has the BSMD flag
|
||||||
|
b) find out, whether the user is of type BSMD
|
||||||
|
If both is true, return True
|
||||||
|
|
||||||
|
args:
|
||||||
|
user_participant_id: ID of the user, obtained from the jwt-token
|
||||||
|
participant_id_of_times_dataset: assigned participant of the shipcall, obtained from the ShipcallParticipantMap
|
||||||
|
"""
|
||||||
|
# when the participant type of the dataset is not an AGENCY, this exception rule does not take place
|
||||||
|
dataset_participant_type_is_agency = int(participant_type)==int(ParticipantType.AGENCY)
|
||||||
|
if not dataset_participant_type_is_agency:
|
||||||
|
return False
|
||||||
|
|
||||||
|
### TIMES ENTRY (ShipcallParticipantMap) ###
|
||||||
|
# identify, whether the dataset's assigned participant has the BSMD flag
|
||||||
|
agency_has_bsmd_flag = InputValidationTimes.check_if_participant_has_bsmd_flag(participant_id=participant_id_of_times_dataset)
|
||||||
|
|
||||||
|
### USER DATA (token) ###
|
||||||
|
# determine, whether the user is of participant_type BSMD
|
||||||
|
user_is_bsmd_type = check_if_user_is_bsmd_type(user_data={"participant_id":user_participant_id})
|
||||||
|
return (agency_has_bsmd_flag) & (user_is_bsmd_type)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_if_participant_has_bsmd_flag(participant_id:int)->bool:
|
||||||
|
"""
|
||||||
|
Given a participant_id, this method checks, whether the participant with {participant_id}
|
||||||
|
has the .BSMD flag in the .flags field.
|
||||||
|
"""
|
||||||
|
# get the dataset's assigned Participant, which matches the SPM entry
|
||||||
|
participant = execute_sql_query_standalone(
|
||||||
|
SQLQuery.get_participant_from_id(),
|
||||||
|
param={"id":participant_id},
|
||||||
|
command_type="single",
|
||||||
|
pooledConnection=None)
|
||||||
|
|
||||||
|
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
|
||||||
|
return has_bsmd_flag
|
||||||
|
|
||||||
|
|||||||
@ -49,15 +49,20 @@ def get_shipcall_id_dictionary():
|
|||||||
shipcalls = {items.get("id"):items for items in shipcalls}
|
shipcalls = {items.get("id"):items for items in shipcalls}
|
||||||
return shipcalls
|
return shipcalls
|
||||||
|
|
||||||
|
def get_participant_type_from_participant_id(participant_id:int)->ParticipantType:
|
||||||
|
# build a dictionary of id:item pairs, so one can select the respective participant
|
||||||
|
participants = get_participant_id_dictionary()
|
||||||
|
participant = participants.get(participant_id,{})
|
||||||
|
participant_type = ParticipantType(participant.get("type",0))
|
||||||
|
return participant_type
|
||||||
|
|
||||||
|
|
||||||
def get_participant_type_from_user_data(user_data:dict)->ParticipantType:
|
def get_participant_type_from_user_data(user_data:dict)->ParticipantType:
|
||||||
# user_data = decode token
|
# user_data = decode token
|
||||||
participant_id = user_data.get("participant_id")
|
participant_id = user_data.get("participant_id")
|
||||||
|
|
||||||
# build a dictionary of id:item pairs, so one can select the respective participant
|
# builds an internal dictionary of id:item pairs, so one can select the respective participant
|
||||||
participants = get_participant_id_dictionary()
|
participant_type = get_participant_type_from_participant_id(participant_id)
|
||||||
participant = participants.get(participant_id,{})
|
|
||||||
participant_type = ParticipantType(participant.get("type",0))
|
|
||||||
return participant_type
|
return participant_type
|
||||||
|
|
||||||
def check_if_user_is_bsmd_type(user_data:dict)->bool:
|
def check_if_user_is_bsmd_type(user_data:dict)->bool:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user