@@ -153,19 +153,31 @@ def shutdown() -> DelayedResponseReturnValue:
153153
154154@unit_api_bp .route ("/system/remove_file" , methods = ["POST" , "PATCH" ])
155155def remove_file () -> DelayedResponseReturnValue :
156+ task_name = "remove_file"
156157 disallow_file = Path (os .environ ["DOT_PIOREACTOR" ]) / "DISALLOW_UI_FILE_SYSTEM"
157158 if os .path .isfile (disallow_file ):
158159 publish_to_error_log (f"Delete blocked because { disallow_file } is present" , task_name )
159160 abort (403 , "DISALLOW_UI_FILE_SYSTEM is present" )
160161
161-
162162 # use filepath in body
163- body = request .get_json ()
163+ body = current_app .get_json (request .data ) or {}
164+ filepath = body .get ("filepath" )
165+ if not filepath :
166+ abort (400 , "filepath field is required" )
167+ assert filepath is not None
164168
165- if not (body ["filepath" ].startswith ("/home/pioreactor" )):
169+ base_dir = Path (os .environ ["DOT_PIOREACTOR" ]).resolve ()
170+ candidate_path = Path (filepath ).expanduser ()
171+ if not candidate_path .is_absolute ():
172+ candidate_path = (base_dir / candidate_path ).resolve ()
173+ else :
174+ candidate_path = candidate_path .resolve ()
175+ try :
176+ candidate_path .relative_to (base_dir )
177+ except ValueError :
166178 abort (403 , "Access to this path is not allowed" )
167179
168- task = tasks .rm (body [ "filepath" ] )
180+ task = tasks .rm (str ( candidate_path ) )
169181 return create_task_response (task )
170182
171183
@@ -183,23 +195,23 @@ def get_clock_time():
183195@unit_api_bp .route ("/system/utc_clock" , methods = ["PATCH" , "POST" ])
184196def set_clock_time () -> DelayedResponseReturnValue : # type: ignore[return]
185197 if HOSTNAME == get_leader_hostname ():
186- if request .get_json (silent = True ): # don't throw 415
187- data = request .json
188- new_time = data .get ("utc_clock_time" )
189- if not new_time :
190- abort (400 , "utc_clock_time field is required" )
198+ data = request .get_json (silent = True ) # don't throw 415
199+ if not data :
200+ abort (400 , "utc_clock_time field is required" )
191201
192- # validate the timestamp
193- try :
194- to_datetime (new_time )
195- except ValueError :
196- abort (400 , "Invalid utc_clock_time format. Use ISO 8601." )
197-
198- # Update the system clock (requires admin privileges)
199- t = tasks .update_clock (new_time )
200- return create_task_response (t )
201- else :
202- abort (404 , "utc_clock_time field required" )
202+ new_time = data .get ("utc_clock_time" )
203+ if not new_time :
204+ abort (400 , "utc_clock_time field is required" )
205+
206+ # validate the timestamp
207+ try :
208+ to_datetime (new_time )
209+ except ValueError :
210+ abort (400 , "Invalid utc_clock_time format. Use ISO 8601." )
211+
212+ # Update the system clock (requires admin privileges)
213+ t = tasks .update_clock (new_time )
214+ return create_task_response (t )
203215 else :
204216 # sync using chrony
205217 t = tasks .sync_clock ()
@@ -401,7 +413,7 @@ def get_settings_for_a_specific_job(job_name: str) -> ResponseReturnValue:
401413 if settings :
402414 return jsonify ({"settings" : {s ["setting" ]: s ["value" ] for s in settings }})
403415 else :
404- return { "status" : "error" }, 404
416+ abort ( 404 , "No settings found for job." )
405417
406418
407419@unit_api_bp .route ("/jobs/settings/job_name/<job_name>/setting/<setting>" , methods = ["GET" ])
@@ -421,7 +433,7 @@ def get_specific_setting_for_a_job(job_name: str, setting: str) -> ResponseRetur
421433 if setting_metadata :
422434 return jsonify ({setting_metadata ["setting" ]: setting_metadata ["value" ]})
423435 else :
424- return { "status" : "error" }, 404
436+ abort ( 404 , "Setting not found." )
425437
426438
427439@unit_api_bp .route ("/jobs/settings/job_name/<job_name>" , methods = ["PATCH" ])
@@ -480,15 +492,15 @@ def get_installed_plugins() -> ResponseReturnValue:
480492 status , msg = False , "Timed out."
481493
482494 if not status :
483- return jsonify ([]), 404
495+ abort ( 404 , msg )
484496 else :
485497 # sometimes an error from a plugin will be printed. We just want to last line, the json bit.
486498 _ , _ , plugins_as_json = msg .rpartition ("\n " )
487499 return attach_cache_control (
488500 Response (
489501 response = plugins_as_json ,
490502 status = 200 ,
491- mimetype = "text /json" ,
503+ mimetype = "application /json" ,
492504 )
493505 )
494506
@@ -910,6 +922,7 @@ def get_calibrations_by_device(device: str) -> ResponseReturnValue:
910922 # first try to open it using our struct, but only to verify it.
911923 cal = to_builtins (yaml_decode (file .read_bytes (), type = AllCalibrations ))
912924 cal ["is_active" ] = c .get (device ) == cal ["calibration_name" ]
925+ cal ["pioreactor_unit" ] = HOSTNAME
913926 calibrations .append (cal )
914927 except Exception as e :
915928 publish_to_error_log (f"Error reading { file .stem } : { e } " , "get_calibrations_by_device" )
@@ -930,6 +943,7 @@ def get_calibration(device: str, cal_name: str) -> ResponseReturnValue:
930943 try :
931944 cal = to_builtins (yaml_decode (calibration_path .read_bytes (), type = AllCalibrations ))
932945 cal ["is_active" ] = c .get (device ) == cal ["calibration_name" ]
946+ cal ["pioreactor_unit" ] = HOSTNAME
933947 return attach_cache_control (jsonify (cal ), max_age = 10 )
934948 except Exception as e :
935949 publish_to_error_log (f"Error reading { calibration_path .stem } : { e } " , "get_calibration" )
0 commit comments