diff --git a/back-end/controllers/celery_controller/celery_tasks.py b/back-end/controllers/celery_controller/celery_tasks.py index 1e8348b..ee2af65 100755 --- a/back-end/controllers/celery_controller/celery_tasks.py +++ b/back-end/controllers/celery_controller/celery_tasks.py @@ -182,7 +182,7 @@ def toggle_tiles(self, markers, userid, folderid, polygonfeatures): status_code = 0 session = Session() try: - user_folder = folder_ops.get_folder_with_id(userid=userid, folderid=folderid, session=session) + user_folder = folder_ops.get_folder_with_id(folderid=folderid, session=session) if user_folder: # Process each polygon feature for index, feature in enumerate(polygonfeatures): @@ -277,9 +277,9 @@ def async_folder_copy_for_export(self, userid, folderid, serialized_csv): newfolder_name = f"Exported Filing at {current_datetime}" - csv_name = EXPORT_CSV_NAME_TEMPLATE.format(brand_name=userVal.brand_name, current_datetime=current_datetime) + csv_name = EXPORT_CSV_NAME_TEMPLATE.format(brand_name=userVal.organization.brand_name, current_datetime=current_datetime) - original_folder = folder_ops.get_folder_with_id(userid=userid, folderid=folderid, session=session) + original_folder = folder_ops.get_folder_with_id(folderid=folderid, session=session) new_folder = original_folder.copy(name=newfolder_name,type='export', deadline=current_datetime, export=True, session=session) csv_file = file_ops.create_file(filename=csv_name, content=serialized_csv.encode('utf-8'), folderid=new_folder.id, filetype='export', session=session) session.add(csv_file) @@ -296,7 +296,7 @@ def async_folder_copy_for_import(self, userid, folderid, deadline): session = Session() newfolder_name = f"Filing for Deadline {deadline}" - original_folder = folder_ops.get_folder_with_id(userid=userid, folderid=folderid, session=session) + original_folder = folder_ops.get_folder_with_id(folderid=folderid, session=session) new_folder = original_folder.copy(name=newfolder_name, type='upload', deadline=deadline, export=False, session=session) session.commit() return new_folder.id @@ -382,7 +382,8 @@ def preview_fabric_locaiton_coverage(self, data, userid, outfile_name): logger.debug("Executing:", gdal_polygonize_cmd) subprocess.run(gdal_polygonize_cmd, shell=True) - folderVal = folder_ops.get_upload_folder(userid, session=session) + userVal = user_ops.get_user_with_id(userid=userid) + folderVal = folder_ops.get_upload_folder(userVal.organization_id, session=session) if folderVal is None: return {'error': 'Folder not found'} @@ -434,12 +435,12 @@ def raster2vector(self, data, userid, outfile_name): subprocess.run(gdal_polygonize_cmd, shell=True) userVal = user_ops.get_user_with_id(userid, session=session) - folderVal = folder_ops.get_upload_folder(userVal.id, session=session) + folderVal = folder_ops.get_upload_folder(userVal.organization_id, session=session) if folderVal is None: num_folders = folder_ops.get_number_of_folders_for_user(userVal.id, session=session) folder_name = f"{userVal.username}-{num_folders + 1}" deadline = "September 2024" - folderVal = folder_ops.create_folder(folder_name, userVal.id, deadline, 'upload', session=session) + folderVal = folder_ops.create_folder(folder_name, userVal.organization_id, deadline, 'upload', session=session) session.commit() vector_file_name = outfile_name + '.kml' diff --git a/back-end/controllers/database_controller/fabric_ops.py b/back-end/controllers/database_controller/fabric_ops.py index 9d2d90f..ff7badd 100755 --- a/back-end/controllers/database_controller/fabric_ops.py +++ b/back-end/controllers/database_controller/fabric_ops.py @@ -52,7 +52,6 @@ def write_to_db(fileid): cur.copy_expert("COPY temp_fabric FROM STDIN CSV HEADER DELIMITER ','", output) output.seek(0) - # Insert data from temporary table to final table with user_id try: cur.execute(f'INSERT INTO fabric_data SELECT *, {fileid} as file_id FROM temp_fabric;') connection.commit() diff --git a/back-end/controllers/database_controller/file_ops.py b/back-end/controllers/database_controller/file_ops.py index 1172586..a463c26 100755 --- a/back-end/controllers/database_controller/file_ops.py +++ b/back-end/controllers/database_controller/file_ops.py @@ -1,6 +1,6 @@ import psycopg2 from database.sessions import ScopedSession, Session -from database.models import file, kml_data, file_editfile_link +from database.models import user, file, kml_data, file_editfile_link from threading import Lock from datetime import datetime from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound @@ -236,4 +236,28 @@ def delete_file(fileid, session=None): raise finally: if owns_session: - session.close() \ No newline at end of file + session.close() + + +def file_belongs_to_organization(file_id, user_id, session): + # Retrieve the user based on user_id + userVal = session.query(user).filter(user.id == user_id).first() + if not userVal: + return False + + # Get the user's organization + organization = userVal.organization + if not organization: + return False + + # Check if the file belongs to this organization + fileVal = session.query(file).filter(file.id == file_id).first() + if not fileVal: + return False + + # Check if the file's folder belongs to the same organization + folder = fileVal.folder + if not folder: + return False + + return folder.user.organization_id == organization.id diff --git a/back-end/controllers/database_controller/folder_ops.py b/back-end/controllers/database_controller/folder_ops.py index 16b7d2e..abc0607 100755 --- a/back-end/controllers/database_controller/folder_ops.py +++ b/back-end/controllers/database_controller/folder_ops.py @@ -6,85 +6,85 @@ from sqlalchemy.exc import SQLAlchemyError from .user_ops import get_user_with_id -def get_export_folder(userid, folderid=None, session=None): +def get_export_folder(orgid, folderid=None, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - # Query to retrieve the last folder of type 'upload' for the given user + # Query to retrieve the last folder of type 'upload' for the given org if not folderid: folderVal = (session.query(folder) - .filter(folder.user_id == userid, folder.type == "export") + .filter(folder.organization_id == orgid, folder.type == "export") .order_by(folder.id.desc()) .first()) else: - folderVal = session.query(folder).filter(folder.id == folderid, folder.user_id == userid).one() + folderVal = session.query(folder).filter(folder.id == folderid).one() return folderVal except NoResultFound: return None except MultipleResultsFound: - return "Multiple results found for the given user ID" + return "Multiple results found for the given org ID" except Exception as e: return str(e) finally: if owns_session: session.close() -def get_upload_folder(userid, folderid=None, session=None): +def get_upload_folder(orgid, folderid=None, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - # Query to retrieve the last folder of type 'upload' for the given user + # Query to retrieve the last folder of type 'upload' for the given org if not folderid: folderVal = (session.query(folder) - .filter(folder.user_id == userid, folder.type == "upload") + .filter(folder.organization_id == orgid, folder.type == "upload") .order_by(folder.id.desc()) .first()) else: - folderVal = session.query(folder).filter(folder.id == folderid, folder.user_id == userid).one() + folderVal = session.query(folder).filter(folder.id == folderid).one() return folderVal except NoResultFound: return None except MultipleResultsFound: - return "Multiple results found for the given user ID" + return "Multiple results found for the given org ID" except Exception as e: return str(e) finally: if owns_session: session.close() -def get_folder_with_id(userid, folderid, session=None): +def get_folder_with_id(folderid, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - folderVal = session.query(folder).filter(folder.id == folderid, folder.user_id == userid).one() + folderVal = session.query(folder).filter(folder.id == folderid).one() return folderVal except NoResultFound: return None except MultipleResultsFound: - return "Multiple results found for the given user ID" + return "Multiple results found for the given org ID" except Exception as e: return str(e) finally: if owns_session: session.close() -def get_folders_by_type_for_user(userid, foldertype, session=None): +def get_folders_by_type_for_org(orgid, foldertype, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - folders = session.query(folder).filter(folder.user_id == userid, folder.type == foldertype).all() + folders = session.query(folder).filter(folder.organization_id == orgid, folder.type == foldertype).all() return folders except NoResultFound: @@ -96,14 +96,14 @@ def get_folders_by_type_for_user(userid, foldertype, session=None): session.close() -def create_folder(foldername, userid, filingDeadline, foldertype, session=None): +def create_folder(foldername, orgid, filingDeadline, foldertype, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - new_folder = folder(name=foldername, user_id=userid, deadline = filingDeadline, type=foldertype) + new_folder = folder(name=foldername, organization_id=orgid, deadline=filingDeadline, type=foldertype) session.add(new_folder) if owns_session: session.commit() @@ -116,13 +116,13 @@ def create_folder(foldername, userid, filingDeadline, foldertype, session=None): if owns_session: session.close() -def get_number_of_folders_for_user(userid, session=None): +def get_number_of_folders_for_org(orgid, session=None): owns_session = False if session is None: session = Session() owns_session = True try: - count = session.query(folder).filter(folder.user_id == userid).count() + count = session.query(folder).filter(folder.organization_id == orgid).count() return count except Exception as e: return -1 @@ -155,4 +155,24 @@ def delete_folder(folderid, session=None): return "An error occurred while deleting the folder: " + str(e) finally: if owns_session: - session.close() \ No newline at end of file + session.close() + + +def folder_belongs_to_organization(folder_id, user_id, session): + + # Retrieve the user based on user_id + userVal = session.query(user).filter(user.id == user_id).first() + if not userVal: + return False + + # Get the user's organization + organization = userVal.organization + if not organization: + return False + + # Check if the folder belongs to this organization + folderVal = session.query(folder).filter(folder.id == folder_id).first() + if not folderVal: + return False + + return folderVal.user.organization_id == organization.id \ No newline at end of file diff --git a/back-end/controllers/database_controller/organization_ops.py b/back-end/controllers/database_controller/organization_ops.py new file mode 100644 index 0000000..a80fceb --- /dev/null +++ b/back-end/controllers/database_controller/organization_ops.py @@ -0,0 +1,6 @@ +from database.models import organization +from database.sessions import Session +from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound +from utils.logger_config import logger + + diff --git a/back-end/database/models.py b/back-end/database/models.py index c89af01..afe8814 100755 --- a/back-end/database/models.py +++ b/back-end/database/models.py @@ -28,10 +28,10 @@ class tower(Base): id = Column(Integer, primary_key=True) tower_name = Column(String, nullable=False) - user_id = Column(Integer, ForeignKey('user.id'), nullable=False) + organization_id = Column(Integer, ForeignKey('organization.id'), nullable=False) # Relationship to TowerInfo and RasterData models (assuming they exist) - user = relationship('user', back_populates='towers') + organization = relationship('organization', back_populates='towers') tower_info = relationship('towerinfo', back_populates='tower', uselist=False, cascade='all, delete') raster_data = relationship('rasterdata', back_populates='tower', uselist=False, cascade='all, delete') @@ -75,8 +75,8 @@ class folder(Base): #filing, will change the name later for less confusion when name = Column(String, nullable=False) type = Column(String, default='upload') # Currently upload or export deadline = Column(Date) #This deadline makes it a filing for the current period - user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE')) - user = relationship('user', back_populates='folders') + organization_id = Column(Integer, ForeignKey('organization.id', ondelete='CASCADE')) + organization = relationship('organization', back_populates='folders') files = relationship('file', back_populates='folder', cascade='all, delete') mbtiles = relationship('mbtiles', back_populates='folder', cascade='all, delete') editfiles = relationship('editfile', back_populates='folder', cascade='all,delete') @@ -84,7 +84,7 @@ class folder(Base): #filing, will change the name later for less confusion when def copy(self, session, export=True, name=None, type=None, deadline=None): name = name if name is not None else self.name type = type if type is not None else self.type - new_folder = folder(name=name, type=type, user_id=self.user_id, deadline = deadline) + new_folder = folder(name=name, type=type, organization_id=self.organization_id, deadline = deadline) session.add(new_folder) session.flush() # To generate an ID for the new folder diff --git a/back-end/routes.py b/back-end/routes.py index ca629ae..f5c3ad1 100755 --- a/back-end/routes.py +++ b/back-end/routes.py @@ -60,13 +60,17 @@ def get_number_records(folderid): session = Session() try: identity = get_jwt_identity() - if folderid == -1: - return jsonify({'error': 'Folder ID is invalid'}), 400 + if folderid < 0: + return jsonify({'error': 'Filling ID is invalid'}), 400 else: - folderVal = folder_ops.get_folder_with_id(userid=identity['id'], folderid=folderid, session=session) - return jsonify(kml_ops.get_kml_data(folderid=folderVal.id, session=session)), 200 + if not folder_ops.folder_belongs_to_organization(folderid, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + + return jsonify(kml_ops.get_kml_data(folderid=folderid, session=session)), 200 except NoAuthorizationError: return jsonify({'error': 'Token is invalid or expired'}), 401 + finally: + session.close() @app.route('/api/submit-data/', methods=['POST', 'GET']) @jwt_required() @@ -102,6 +106,8 @@ def submit_data(folderid): return jsonify({'Status': "Failed, invalid deadline format"}), 400 if import_folder_id != -1: + if not folder_ops.folder_belongs_to_organization(import_folder_id, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 # Asynchronous copy and create new folder with deadline logger.debug("here 1") task_chain = chain( @@ -114,7 +120,7 @@ def submit_data(folderid): # Create new folder with deadline logger.debug("here 2") new_folder_name = f"Filing for Deadline {deadline_date}" - folderVal = folder_ops.create_folder(new_folder_name, userVal.id, deadline_date, 'upload', session) + folderVal = folder_ops.create_folder(new_folder_name, userVal.organization_id, deadline_date, 'upload', session) session.commit() task_chain = chain( @@ -124,10 +130,10 @@ def submit_data(folderid): else: logger.debug("here 3") - folderVal = folder_ops.get_folder_with_id(userid=userVal.id, folderid=folderid, session=session) - + if not folder_ops.folder_belongs_to_organization(folderid, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 task_chain = chain( - add_files_to_folder.s(folderVal.id, file_contents), + add_files_to_folder.s(folderid, file_contents), process_data.s(recompute=False) ) @@ -198,9 +204,10 @@ def search_location(folderid): session = Session() try: userVal = user_ops.get_user_with_id(identity['id'], session) - folderVal = folder_ops.get_folder_with_id(userVal.id, folderid, session) + if not folder_ops.folder_belongs_to_organization(folderid, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 - results_dict = fabric_ops.address_query(folderVal.id, query, session) + results_dict = fabric_ops.address_query(folderid, query, session) return jsonify(results_dict) except Exception as e: session.rollback() @@ -277,9 +284,10 @@ def exportFiling(folderid): session = Session() try: userVal = user_ops.get_user_with_id(identity['id'], session) - folderVal = folder_ops.get_folder_with_id(userVal.id, folderid, session) + if not folder_ops.folder_belongs_to_organization(folderid, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 - csv_output = kml_ops.export(userVal.id, folderVal.id, userVal.provider_id, userVal.brand_name, session) + csv_output = kml_ops.export(userVal.id, folderid, userVal.provider_id, userVal.brand_name, session) if csv_output: current_time = datetime.now().strftime(DATETIME_FORMAT) @@ -321,6 +329,12 @@ def serve_tile_with_folderid(folder_id, zoom, x, y): folder_id = int(folder_id) identity = get_jwt_identity() + session = Session() + if not folder_ops.folder_belongs_to_organization(folder_id, identity['id'], session): + session.close() + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + session.close() + zoom = int(zoom) x = int(x) y = int(y) @@ -349,7 +363,14 @@ def toggle_markers(): logger.info(polygonfeatures) if folderid == -1: return jsonify({'error': 'Invalid folder id'}), 400 + identity = get_jwt_identity() + session = Session() + if not folder_ops.folder_belongs_to_organization(folderid, identity['id'], session): + session.close() + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + session.close() + task = toggle_tiles.apply_async(args=[markers, identity['id'], folderid, polygonfeatures]) return jsonify({'Status': "OK", 'task_id': task.id}), 200 @@ -365,6 +386,10 @@ def get_files(): # Verify user own this folder folder_ID = int(request.args.get('folder_ID')) session = Session() + + if not folder_ops.folder_belongs_to_organization(folder_ID, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + if folder_ID: filesinfo = file_ops.get_filesinfo_in_folder(folder_ID, session=session) if not filesinfo: @@ -385,6 +410,10 @@ def get_editfiles(): # Verify user own this folder folder_ID = int(request.args.get('folder_ID')) session = Session() + + if not folder_ops.folder_belongs_to_organization(folder_ID, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + if folder_ID: filesinfo = editfile_ops.get_editfilesinfo_in_folder(folder_ID, session=session) if not filesinfo: @@ -403,8 +432,9 @@ def get_folders_with_deadlines(): try: identity = get_jwt_identity() user_id = identity['id'] - - folders = folder_ops.get_folders_by_type_for_user(user_id, 'upload') + session = Session() + userVal = user_ops.get_user_with_id(userid=user_id, session=session) + folders = folder_ops.get_folders_by_type_for_org(userVal.organization_id, 'upload', session) folder_info = [{ 'folder_id': folder.id, @@ -415,15 +445,19 @@ def get_folders_with_deadlines(): return jsonify(folder_info), 200 except Exception as e: return jsonify({'error': str(e)}), 500 + finally: + session.close() @app.route('/api/get-last-upload-folder', methods=['GET']) @jwt_required() def get_last_folder(): try: identity = get_jwt_identity() + session = Session() user_id = identity['id'] + userVal = user_ops.get_user_with_id(userid=user_id, session=session) # Hackey way to use this method to get the lastest filing of user - folderVal = folder_ops.get_upload_folder(userid=user_id) + folderVal = folder_ops.get_upload_folder(orgid=userVal.organization_id, session=session) if not folderVal: folderid = -1 else: @@ -443,8 +477,10 @@ def get_network_files(folder_id): # multi-filing support folder_id = int(folder_id) - folderVal = folder_ops.get_folder_with_id(userid=identity['id'], folderid=folder_id, session=session) - files = file_ops.get_all_network_files_for_fileinfoedit_table(folderVal.id, session) + if not folder_ops.folder_belongs_to_organization(folder_id, identity['id'], session): + return jsonify({'error': 'You are accessing a filing not belong to your organization'}), 400 + + files = file_ops.get_all_network_files_for_fileinfoedit_table(folder_id, session) files_data = [] for file in files: # Assuming each file has at least one kml_data or it's an empty list @@ -478,6 +514,10 @@ def update_network_file(file_id): identity = get_jwt_identity() session = Session() file_id = int(file_id) + + if not file_ops.file_belongs_to_organization(file_id=file_id, user_id=identity['id'], session=session): + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + file = file_ops.get_file_with_id(file_id, session) if not file: return jsonify({'error': 'File not found'}), 404 @@ -505,6 +545,13 @@ def delete_files(fileid): fileid = int(fileid) try: identity = get_jwt_identity() + + session = Session() + if not file_ops.file_belongs_to_organization(file_id=fileid, user_id=identity['id'], session=session): + session.close() + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + session.close() + task = deleteFiles.apply_async(args=[fileid, identity['id']]) return jsonify({'Status': "OK", 'task_id': task.id}), 200 # return task id to the client except NoAuthorizationError: @@ -516,7 +563,8 @@ def get_exported_folders(): session = Session() try: identity = get_jwt_identity() - folders = folder_ops.get_folders_by_type_for_user(userid=identity['id'], foldertype='export', session=session) + userVal = user_ops.get_user_with_id(userid=identity['id'], session=session) + folders = folder_ops.get_folders_by_type_for_org(orgid=userVal.organization_id, foldertype='export', session=session) response_data = [] for fldr in folders: @@ -560,6 +608,9 @@ def delete_export(fileid): session = Session() try: identity = get_jwt_identity() + if not file_ops.file_belongs_to_organization(file_id=fileid, user_id=identity['id'], session=session): + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + fileVal = file_ops.get_file_with_id(fileid=fileid, session=session) print(f'file id is {fileVal.id}', flush=True) folder_ops.delete_folder(folderid=fileVal.folder_id, session=session) @@ -735,6 +786,9 @@ def download_export(fileid): session = Session() try: identity = get_jwt_identity() + if not file_ops.file_belongs_to_organization(file_id=fileid, user_id=identity['id'], session=session): + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + fileVal = file_ops.get_file_with_id(fileid=fileid, session=session) if not fileVal: return jsonify({'error': 'File not found'}), 404 @@ -828,6 +882,9 @@ def get_edit_geojson(fileid): try: identity = get_jwt_identity() fileid = int(fileid) + if not file_ops.file_belongs_to_organization(file_id=fileid, user_id=identity['id'], session=session): + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + editfile = editfile_ops.get_editfile_with_id(fileid=fileid, session=session) if editfile is None: @@ -850,6 +907,9 @@ def get_edit_geojson_centroid(fileid): session = Session() try: identity = get_jwt_identity() + if not file_ops.file_belongs_to_organization(file_id=fileid, user_id=identity['id'], session=session): + return jsonify({'error': 'You are accessing a file not belong to your organization'}), 400 + editfile = editfile_ops.get_editfile_with_id(fileid=fileid, session=session) if editfile is None: