Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 63 additions & 0 deletions API/Routes/DataFile/DataFileRoute.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,33 @@

datafile_api = Blueprint('DataFileRoute', __name__)

# Allowed solver identifiers (case-insensitive comparison applied in route).
_ALLOWED_SOLVERS = frozenset({'glpk', 'cbc'})


def _validate_case_inputs(casename, caserunname=None):
"""Validate casename and optional caserunname against path traversal.

The Osemosys constructor validates casename via Config.validate_path, but
caserunname is never checked there. This helper closes the gap by
validating both at the route boundary before any filesystem operations.

Raises PermissionError on traversal attempts, consistent with existing
download routes.
"""
Config.validate_path(Config.DATA_STORAGE, casename)
if caserunname is not None:
Config.validate_path(
Config.DATA_STORAGE,
os.path.join(casename, 'res', caserunname)
)

@datafile_api.route("/generateDataFile", methods=['POST'])
def generateDataFile():
try:
casename = request.json['casename']
caserunname = request.json['caserunname']
_validate_case_inputs(casename, caserunname)

if casename != None:
txtFile = DataFile(casename)
Expand All @@ -23,6 +45,8 @@ def generateDataFile():
"status_code": "success"
}
return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -35,12 +59,15 @@ def createCaseRun():
casename = request.json['casename']
caserunname = request.json['caserunname']
data = request.json['data']
_validate_case_inputs(casename, caserunname)

if casename != None:
caserun = DataFile(casename)
response = caserun.createCaseRun(caserunname, data)

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -54,12 +81,16 @@ def updateCaseRun():
caserunname = request.json['caserunname']
oldcaserunname = request.json['oldcaserunname']
data = request.json['data']
_validate_case_inputs(casename, caserunname)
_validate_case_inputs(casename, oldcaserunname)

if casename != None:
caserun = DataFile(casename)
response = caserun.updateCaseRun(caserunname, oldcaserunname, data)

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand Down Expand Up @@ -92,6 +123,8 @@ def deleteCaseRun():
caserun = DataFile(casename)
response = caserun.deleteCaseRun(caserunname, resultsOnly)
return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except FileNotFoundError:
return jsonify('No existing cases!'), 404
except OSError:
Expand All @@ -102,12 +135,15 @@ def deleteScenarioCaseRuns():
try:
scenarioId = request.json['scenarioId']
casename = request.json['casename']
_validate_case_inputs(casename)

if casename != None:
caserun = DataFile(casename)
response = caserun.deleteScenarioCaseRuns(scenarioId)

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -117,12 +153,15 @@ def saveView():
casename = request.json['casename']
param = request.json['param']
data = request.json['data']
_validate_case_inputs(casename)

if casename != None:
caserun = DataFile(casename)
response = caserun.saveView(data, param)

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -132,12 +171,15 @@ def updateViews():
casename = request.json['casename']
param = request.json['param']
data = request.json['data']
_validate_case_inputs(casename)

if casename != None:
caserun = DataFile(casename)
response = caserun.updateViews(data, param)

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -146,13 +188,16 @@ def readDataFile():
try:
casename = request.json['casename']
caserunname = request.json['caserunname']
_validate_case_inputs(casename, caserunname)
if casename != None:
txtFile = DataFile(casename)
data = txtFile.readDataFile(caserunname)
response = data
else:
response = None
return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand Down Expand Up @@ -186,13 +231,16 @@ def validateInputs():
try:
casename = request.json['casename']
caserunname = request.json['caserunname']
_validate_case_inputs(casename, caserunname)
if casename != None:
df = DataFile(casename)
validation = df.validateInputs(caserunname)
response = validation
else:
response = None
return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand Down Expand Up @@ -290,6 +338,11 @@ def run():
casename = request.json['casename']
caserunname = request.json['caserunname']
solver = request.json['solver']
_validate_case_inputs(casename, caserunname)

if str(solver).lower() not in _ALLOWED_SOLVERS:
return jsonify({'message': 'Invalid solver.', 'status_code': 'error'}), 400

logger.info("Starting optimization process for model %s caserun %s", casename, caserunname)
txtFile = DataFile(casename)
response = txtFile.run(solver, caserunname)
Expand All @@ -299,6 +352,8 @@ def run():
# print(ex)
# return ex, 404

except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('No existing cases!'), 404

Expand All @@ -311,6 +366,9 @@ def batchRun():
start = time.time()
modelname = request.json['modelname']
cases = request.json['cases']
_validate_case_inputs(modelname)
for caserun in cases:
_validate_case_inputs(modelname, caserun)

if modelname != None:
txtFile = DataFile(modelname)
Expand All @@ -322,19 +380,24 @@ def batchRun():
end = time.time()
response['time'] = end-start
return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('Error!'), 404

@datafile_api.route("/cleanUp", methods=['POST'])
def cleanUp():
try:
modelname = request.json['modelname']
_validate_case_inputs(modelname)

if modelname != None:
model = DataFile(modelname)
logger.info("Cleaning up results for model %s", modelname)
response = model.cleanUp()

return jsonify(response), 200
except PermissionError:
return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400
except(IOError):
return jsonify('Error!'), 404
Loading
Loading