Skip to content

Commit 99f1af8

Browse files
chaensfayer
authored andcommitted
fix: do not use eval in RequestDB
1 parent a975d69 commit 99f1af8

1 file changed

Lines changed: 53 additions & 22 deletions

File tree

src/DIRAC/RequestManagementSystem/DB/RequestDB.py

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
TEXT,
4141
BigInteger,
4242
distinct,
43+
inspect,
4344
)
4445

4546
# # from DIRAC
@@ -188,6 +189,38 @@ class RequestDB:
188189
db holding requests
189190
"""
190191

192+
@staticmethod
193+
def _get_column(table_name, column_name):
194+
"""Resolve supported ORM column attributes without evaluating input."""
195+
196+
models = {"Request": Request, "Operation": Operation}
197+
aliases = {"Status": "_Status"}
198+
199+
model = models.get(table_name)
200+
if model is None:
201+
raise ValueError(f"Unknown table '{table_name}'")
202+
203+
resolved_name = aliases.get(column_name, column_name)
204+
if resolved_name not in inspect(model).column_attrs:
205+
raise ValueError(f"Unknown {table_name} attribute '{column_name}'")
206+
207+
return getattr(model, resolved_name)
208+
209+
@classmethod
210+
def _apply_web_filter(cls, query, table_name, column_name, value):
211+
column = cls._get_column(table_name, column_name)
212+
if isinstance(value, list):
213+
return query.filter(column.in_(value))
214+
return query.filter(column == value)
215+
216+
@classmethod
217+
def _get_order_expression(cls, table_name, column_name, direction):
218+
column = cls._get_column(table_name, column_name)
219+
normalized_direction = direction.lower()
220+
if normalized_direction not in {"asc", "desc"}:
221+
raise ValueError(f"Unknown sort direction '{direction}'")
222+
return getattr(column, normalized_direction)()
223+
191224
def __getDBConnectionInfo(self, fullname):
192225
"""Collect from the CS all the info needed to connect to the DB.
193226
This should be in a base class eventually
@@ -659,13 +692,12 @@ def getRequestSummaryWeb(self, selectDict, sortList, startItem, maxItems):
659692
elif key == "Status":
660693
key = "_Status"
661694

662-
if isinstance(value, list):
663-
summaryQuery = summaryQuery.filter(eval(f"{tableName}.{key}.in_({value})"))
664-
else:
665-
summaryQuery = summaryQuery.filter(eval(f"{tableName}.{key}") == value)
695+
summaryQuery = self._apply_web_filter(summaryQuery, tableName, key, value)
666696

667697
if sortList:
668-
summaryQuery = summaryQuery.order_by(eval(f"Request.{sortList[0][0]}.{sortList[0][1].lower()}()"))
698+
summaryQuery = summaryQuery.order_by(
699+
self._get_order_expression("Request", sortList[0][0], sortList[0][1])
700+
)
669701

670702
try:
671703
requestLists = summaryQuery.all()
@@ -699,6 +731,8 @@ def getRequestSummaryWeb(self, selectDict, sortList, startItem, maxItems):
699731
resultDict["TotalRecords"] = nRequests
700732

701733
return S_OK(resultDict)
734+
except ValueError as e:
735+
return S_ERROR(str(e))
702736
#
703737
except Exception as e:
704738
self.log.exception("getRequestSummaryWeb: unexpected exception", lException=e)
@@ -718,17 +752,15 @@ def getRequestCountersWeb(self, groupingAttribute, selectDict):
718752

719753
session = self.DBSession()
720754

721-
if groupingAttribute == "Type":
722-
groupingAttribute = "Operation.Type"
723-
elif groupingAttribute == "Status":
724-
groupingAttribute = "Request._Status"
725-
else:
726-
groupingAttribute = f"Request.{groupingAttribute}"
727-
728755
try:
756+
if groupingAttribute == "Type":
757+
groupingColumn = self._get_column("Operation", "Type")
758+
else:
759+
groupingColumn = self._get_column("Request", groupingAttribute)
760+
729761
summaryQuery = session.query(
730-
eval(groupingAttribute), func.count(Request.RequestID) # pylint: disable=not-callable
731-
)
762+
groupingColumn, func.count(Request.RequestID)
763+
) # pylint: disable=not-callable,no-member
732764

733765
for key, value in selectDict.items():
734766
if key == "ToDate":
@@ -743,12 +775,9 @@ def getRequestCountersWeb(self, groupingAttribute, selectDict):
743775
elif key == "Status":
744776
key = "_Status"
745777

746-
if isinstance(value, list):
747-
summaryQuery = summaryQuery.filter(eval(f"{objectType}.{key}.in_({value})"))
748-
else:
749-
summaryQuery = summaryQuery.filter(eval(f"{objectType}.{key}") == value)
778+
summaryQuery = self._apply_web_filter(summaryQuery, objectType, key, value)
750779

751-
summaryQuery = summaryQuery.group_by(eval(groupingAttribute))
780+
summaryQuery = summaryQuery.group_by(groupingColumn)
752781

753782
try:
754783
requestLists = summaryQuery.all()
@@ -760,6 +789,8 @@ def getRequestCountersWeb(self, groupingAttribute, selectDict):
760789

761790
return S_OK(resultDict)
762791

792+
except ValueError as e:
793+
return S_ERROR(str(e))
763794
except Exception as e:
764795
self.log.exception("getRequestSummaryWeb: unexpected exception", lException=e)
765796
return S_ERROR(f"getRequestSummaryWeb: unexpected exception : {e}")
@@ -772,11 +803,11 @@ def getDistinctValues(self, tableName, columnName):
772803

773804
session = self.DBSession()
774805
distinctValues = []
775-
if columnName == "Status":
776-
columnName = "_Status"
777806
try:
778-
result = session.query(distinct(eval(f"{tableName}.{columnName}"))).all()
807+
result = session.query(distinct(self._get_column(tableName, columnName))).all()
779808
distinctValues = [dist[0] for dist in result]
809+
except ValueError as e:
810+
return S_ERROR(str(e))
780811
except NoResultFound:
781812
pass
782813
except Exception as e:

0 commit comments

Comments
 (0)