Skip to content

Commit dc498a8

Browse files
authored
Merge pull request #7170 from ic-hep/tfsystem_perm_8.1
[8.1] Add finer permission model for Transformation System
2 parents 4409bf4 + 8fdd2f5 commit dc498a8

File tree

9 files changed

+271
-43
lines changed

9 files changed

+271
-43
lines changed

docs/source/AdministratorGuide/Configuration/ConfReference/Tips/Authorization/index.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ are showed in the next table:
3535
+----------------------------+------------------------------------------------------------------+-------------+
3636
| *PrivateLimitedDelegation* | Allow getting only limited proxies for one self | |
3737
+----------------------------+------------------------------------------------------------------+-------------+
38-
| *ProductionManagement* | Allow managing production | |
38+
| *ProductionManagement* | Allow managing all productions | |
39+
+----------------------------+------------------------------------------------------------------+-------------+
40+
| *ProductionSharing* | Allow managing productions owned by the same group | |
41+
+----------------------------+------------------------------------------------------------------+-------------+
42+
| *ProductionUser* | Allow managing productions owned by the same user | |
3943
+----------------------------+------------------------------------------------------------------+-------------+
4044
| *ProxyManagement* | Allow managing proxies | |
4145
+----------------------------+------------------------------------------------------------------+-------------+

docs/source/AdministratorGuide/Tutorials/installTS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ Using the ``Configuration Manager`` application in the WebApp, create a new sect
9292
After restarting the ``ProxyManager``, you should now be able to get a proxy belonging to the ``dirac_prod`` group that
9393
will be automatically uploaded.
9494

95+
The ``ProductionManagement`` property allows users in the group to access and change all transformations. There is also
96+
a ``ProductionSharing`` property to only allow access to transformations in the same group and ``ProductionUser`` to
97+
only allow users to access their own transformations.
98+
9599
Add a ProdManager Shifter
96100
=========================
97101

src/DIRAC/Core/Security/Properties.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ class SecurityProperty(str, Enum):
4444
PRIVATE_LIMITED_DELEGATION = "PrivateLimitedDelegation"
4545
#: Allow managing proxies
4646
PROXY_MANAGEMENT = "ProxyManagement"
47-
#: Allow managing production
47+
#: Allow managing all productions
4848
PRODUCTION_MANAGEMENT = "ProductionManagement"
49+
#: Allow managing all productions in the same group
50+
PRODUCTION_SHARING = "ProductionSharing"
51+
#: Allows user to manage productions they own only
52+
PRODUCTION_USER = "ProductionUser"
4953
#: Allow production request approval on behalf of PPG
5054
PPG_AUTHORITY = "PPGAuthority"
5155
#: Allow Bookkeeping Management

src/DIRAC/FrameworkSystem/Client/ComponentInstaller.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
PROXY_MANAGEMENT,
8888
SERVICE_ADMINISTRATOR,
8989
TRUSTED_HOST,
90+
PRODUCTION_MANAGEMENT,
9091
)
9192
from DIRAC.Core.Utilities.Extensions import (
9293
extensionsByPriority,
@@ -438,6 +439,7 @@ def _getCentralCfg(self, installCfg):
438439
FULL_DELEGATION,
439440
PROXY_MANAGEMENT,
440441
OPERATOR,
442+
PRODUCTION_MANAGEMENT,
441443
]
442444

443445
for section in (

src/DIRAC/TransformationSystem/Service/TransformationManagerHandler.py

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33
from DIRAC import S_OK, S_ERROR
44
from DIRAC.Core.DISET.RequestHandler import RequestHandler
5+
from DIRAC.Core.Security.Properties import SecurityProperty
56
from DIRAC.Core.Utilities.DEncode import ignoreEncodeWarning
67
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
78
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
@@ -33,6 +34,32 @@ def initializeHandler(cls, serviceInfoDict):
3334

3435
return S_OK()
3536

37+
def checkPermissions(self, transName: str):
38+
"""
39+
checks if remote user has permission to access to a given transformation
40+
41+
:param str transName: Name of the transformation to check
42+
43+
:return: S_ERROR if user does not have permission or if transformation does not exist
44+
S_OK otherwise
45+
"""
46+
credDict = self.getRemoteCredentials()
47+
groupProperties = credDict.get("properties", [])
48+
if SecurityProperty.PRODUCTION_MANAGEMENT in groupProperties:
49+
return S_OK()
50+
tfDetails = self.transformationDB.getTransformation(transName)
51+
if not tfDetails["OK"]:
52+
return S_ERROR(f"Could not retrieve transformation {transName} details for permissions check.")
53+
authorGroup = tfDetails["Value"]["AuthorGroup"]
54+
author = tfDetails["Value"]["Author"]
55+
if SecurityProperty.PRODUCTION_SHARING in groupProperties:
56+
if authorGroup == credDict.get("group", None):
57+
return S_OK()
58+
if SecurityProperty.PRODUCTION_USER in groupProperties:
59+
if author == credDict.get("username", None):
60+
return S_OK()
61+
return S_ERROR(f"You do not have permissions for transformation {transName}")
62+
3663
types_getCounters = [str, list, dict]
3764

3865
@classmethod
@@ -70,6 +97,13 @@ def export_addTransformation(
7097
credDict = self.getRemoteCredentials()
7198
author = credDict.get("username")
7299
authorGroup = credDict.get("group")
100+
groupProperties = credDict.get("properties", [])
101+
if (
102+
SecurityProperty.PRODUCTION_MANAGEMENT not in groupProperties
103+
and SecurityProperty.PRODUCTION_SHARING not in groupProperties
104+
and SecurityProperty.PRODUCTION_USER not in groupProperties
105+
):
106+
return S_ERROR("You do not have permission to add a Transformation")
73107
res = self.transformationDB.addTransformation(
74108
transName,
75109
description,
@@ -97,36 +131,45 @@ def export_addTransformation(
97131
types_deleteTransformation = [[int, str]]
98132

99133
def export_deleteTransformation(self, transName):
134+
if not (result := self.checkPermissions(transName))["OK"]:
135+
return result
100136
credDict = self.getRemoteCredentials()
101137
author = credDict.get("username")
102138
return self.transformationDB.deleteTransformation(transName, author=author)
103139

104140
types_completeTransformation = [[int, str]]
105141

106142
def export_completeTransformation(self, transName):
143+
if not (result := self.checkPermissions(transName))["OK"]:
144+
return result
107145
credDict = self.getRemoteCredentials()
108146
author = credDict.get("username")
109147
return self.transformationDB.setTransformationParameter(transName, "Status", "Completed", author=author)
110148

111149
types_cleanTransformation = [[int, str]]
112150

113151
def export_cleanTransformation(self, transName):
152+
if not (result := self.checkPermissions(transName))["OK"]:
153+
return result
114154
credDict = self.getRemoteCredentials()
115155
author = credDict.get("username")
116156
return self.transformationDB.cleanTransformation(transName, author=author)
117157

118158
types_setTransformationParameter = [[int, str], str]
119159

120160
def export_setTransformationParameter(self, transName, paramName, paramValue):
161+
if not (result := self.checkPermissions(transName))["OK"]:
162+
return result
121163
credDict = self.getRemoteCredentials()
122164
author = credDict.get("username")
123165
return self.transformationDB.setTransformationParameter(transName, paramName, paramValue, author=author)
124166

125167
types_deleteTransformationParameter = [[int, str], str]
126168

127-
@classmethod
128-
def export_deleteTransformationParameter(cls, transName, paramName):
129-
return cls.transformationDB.deleteTransformationParameter(transName, paramName)
169+
def export_deleteTransformationParameter(self, transName, paramName):
170+
if not (result := self.checkPermissions(transName))["OK"]:
171+
return result
172+
return self.transformationDB.deleteTransformationParameter(transName, paramName)
130173

131174
types_getTransformations = []
132175

@@ -159,15 +202,21 @@ def export_getTransformations(
159202

160203
types_getTransformation = [[int, str]]
161204

162-
@classmethod
163-
def export_getTransformation(cls, transName, extraParams=False):
164-
return cls.transformationDB.getTransformation(transName, extraParams=extraParams)
205+
def export_getTransformation(self, transName, extraParams=False):
206+
# check first if transformation exists to avoid returning permissions error for non-existing transformation
207+
tfDetails = self.transformationDB.getTransformation(transName, extraParams=extraParams)
208+
if not tfDetails["OK"]:
209+
return tfDetails
210+
if not (result := self.checkPermissions(transName))["OK"]:
211+
return result
212+
return tfDetails
165213

166214
types_getTransformationParameters = [[int, str], [str, list]]
167215

168-
@classmethod
169-
def export_getTransformationParameters(cls, transName, parameters):
170-
return cls.transformationDB.getTransformationParameters(transName, parameters)
216+
def export_getTransformationParameters(self, transName, parameters):
217+
if not (result := self.checkPermissions(transName))["OK"]:
218+
return result
219+
return self.transformationDB.getTransformationParameters(transName, parameters)
171220

172221
types_getTransformationWithStatus = [[str, list, tuple]]
173222

@@ -182,28 +231,30 @@ def export_getTransformationWithStatus(cls, status):
182231

183232
types_addFilesToTransformation = [[int, str], [list, tuple]]
184233

185-
@classmethod
186-
def export_addFilesToTransformation(cls, transName, lfns):
187-
return cls.transformationDB.addFilesToTransformation(transName, lfns)
234+
def export_addFilesToTransformation(self, transName, lfns):
235+
if not (result := self.checkPermissions(transName))["OK"]:
236+
return result
237+
return self.transformationDB.addFilesToTransformation(transName, lfns)
188238

189239
types_addTaskForTransformation = [[int, str]]
190240

191-
@classmethod
192-
def export_addTaskForTransformation(cls, transName, lfns=[], se="Unknown"):
193-
return cls.transformationDB.addTaskForTransformation(transName, lfns=lfns, se=se)
241+
def export_addTaskForTransformation(self, transName, lfns=[], se="Unknown"):
242+
if not (result := self.checkPermissions(transName))["OK"]:
243+
return result
244+
return self.transformationDB.addTaskForTransformation(transName, lfns=lfns, se=se)
194245

195246
types_setFileStatusForTransformation = [[int, str], dict]
196247

197-
@classmethod
198248
@ignoreEncodeWarning
199-
def export_setFileStatusForTransformation(cls, transName, dictOfNewFilesStatus):
249+
def export_setFileStatusForTransformation(self, transName, dictOfNewFilesStatus):
200250
"""Sets the file status for the transformation.
201251
202252
The dictOfNewFilesStatus is a dictionary with the form:
203253
{12345: ('StatusA', errorA), 6789: ('StatusB',errorB), ... } where the keys are fileIDs
204254
The tuple may be a string with only the status if the client was from an older version
205255
"""
206-
256+
if not (result := self.checkPermissions(transName))["OK"]:
257+
return result
207258
if not dictOfNewFilesStatus:
208259
return S_OK({})
209260

@@ -213,35 +264,43 @@ def export_setFileStatusForTransformation(cls, transName, dictOfNewFilesStatus):
213264
else:
214265
return S_ERROR("Status field should be two values")
215266

216-
res = cls.transformationDB._getConnectionTransID(False, transName)
267+
res = self.transformationDB._getConnectionTransID(False, transName)
217268
if not res["OK"]:
218269
return res
219270
connection = res["Value"]["Connection"]
220271
transID = res["Value"]["TransformationID"]
221272

222-
return cls.transformationDB.setFileStatusForTransformation(transID, newStatusForFileIDs, connection=connection)
273+
return self.transformationDB.setFileStatusForTransformation(transID, newStatusForFileIDs, connection=connection)
223274

224275
types_getTransformationStats = [[int, str]]
225276

226-
@classmethod
227-
def export_getTransformationStats(cls, transName):
228-
return cls.transformationDB.getTransformationStats(transName)
277+
def export_getTransformationStats(self, transName):
278+
if not (result := self.checkPermissions(transName))["OK"]:
279+
return result
280+
return self.transformationDB.getTransformationStats(transName)
229281

230282
types_getTransformationFilesCount = [[int, str], str]
231283

232-
@classmethod
233-
def export_getTransformationFilesCount(cls, transName, field, selection={}):
234-
return cls.transformationDB.getTransformationFilesCount(transName, field, selection=selection)
284+
def export_getTransformationFilesCount(self, transName, field, selection={}):
285+
if not (result := self.checkPermissions(transName))["OK"]:
286+
return result
287+
return self.transformationDB.getTransformationFilesCount(transName, field, selection=selection)
235288

236289
types_getTransformationFiles = []
237290

238-
@classmethod
239291
def export_getTransformationFiles(
240-
cls, condDict=None, older=None, newer=None, timeStamp="LastUpdate", orderAttribute=None, limit=None, offset=None
292+
self,
293+
condDict=None,
294+
older=None,
295+
newer=None,
296+
timeStamp="LastUpdate",
297+
orderAttribute=None,
298+
limit=None,
299+
offset=None,
241300
):
242301
if not condDict:
243302
condDict = {}
244-
return cls.transformationDB.getTransformationFiles(
303+
return self.transformationDB.getTransformationFiles(
245304
condDict=condDict,
246305
older=older,
247306
newer=newer,
@@ -286,32 +345,39 @@ def export_getTransformationTasks(
286345

287346
types_setTaskStatus = [[int, str], [list, int], str]
288347

289-
@classmethod
290-
def export_setTaskStatus(cls, transName, taskID, status):
291-
return cls.transformationDB.setTaskStatus(transName, taskID, status)
348+
def export_setTaskStatus(self, transName, taskID, status):
349+
if not (result := self.checkPermissions(transName))["OK"]:
350+
return result
351+
return self.transformationDB.setTaskStatus(transName, taskID, status)
292352

293353
types_setTaskStatusAndWmsID = [[int, str], int, str, str]
294354

295-
@classmethod
296-
def export_setTaskStatusAndWmsID(cls, transName, taskID, status, taskWmsID):
297-
return cls.transformationDB.setTaskStatusAndWmsID(transName, taskID, status, taskWmsID)
355+
def export_setTaskStatusAndWmsID(self, transName, taskID, status, taskWmsID):
356+
if not (result := self.checkPermissions(transName))["OK"]:
357+
return result
358+
return self.transformationDB.setTaskStatusAndWmsID(transName, taskID, status, taskWmsID)
298359

299360
types_getTransformationTaskStats = [[int, str]]
300361

301-
@classmethod
302-
def export_getTransformationTaskStats(cls, transName):
303-
return cls.transformationDB.getTransformationTaskStats(transName)
362+
def export_getTransformationTaskStats(self, transName):
363+
if not (result := self.checkPermissions(transName))["OK"]:
364+
return result
365+
return self.transformationDB.getTransformationTaskStats(transName)
304366

305367
types_deleteTasks = [[int, str], int, int]
306368

307369
def export_deleteTasks(self, transName, taskMin, taskMax):
370+
if not (result := self.checkPermissions(transName))["OK"]:
371+
return result
308372
credDict = self.getRemoteCredentials()
309373
author = credDict.get("username")
310374
return self.transformationDB.deleteTasks(transName, taskMin, taskMax, author=author)
311375

312376
types_extendTransformation = [[int, str], int]
313377

314378
def export_extendTransformation(self, transName, nTasks):
379+
if not (result := self.checkPermissions(transName))["OK"]:
380+
return result
315381
credDict = self.getRemoteCredentials()
316382
author = credDict.get("username")
317383
return self.transformationDB.extendTransformation(transName, nTasks, author=author)
@@ -320,6 +386,8 @@ def export_extendTransformation(self, transName, nTasks):
320386

321387
def export_getTasksToSubmit(self, transName, numTasks, site=""):
322388
"""Get information necessary for submission for a given number of tasks for a given transformation"""
389+
if not (result := self.checkPermissions(transName))["OK"]:
390+
return result
323391
res = self.transformationDB.getTransformation(transName)
324392
if not res["OK"]:
325393
return res
@@ -349,20 +417,26 @@ def export_getTasksToSubmit(self, transName, numTasks, site=""):
349417
types_createTransformationMetaQuery = [[int, str], dict, str]
350418

351419
def export_createTransformationMetaQuery(self, transName, queryDict, queryType):
420+
if not (result := self.checkPermissions(transName))["OK"]:
421+
return result
352422
credDict = self.getRemoteCredentials()
353423
author = credDict.get("username")
354424
return self.transformationDB.createTransformationMetaQuery(transName, queryDict, queryType, author=author)
355425

356426
types_deleteTransformationMetaQuery = [[int, str], str]
357427

358428
def export_deleteTransformationMetaQuery(self, transName, queryType):
429+
if not (result := self.checkPermissions(transName))["OK"]:
430+
return result
359431
credDict = self.getRemoteCredentials()
360432
author = credDict.get("username")
361433
return self.transformationDB.deleteTransformationMetaQuery(transName, queryType, author=author)
362434

363435
types_getTransformationMetaQuery = [[int, str], str]
364436

365437
def export_getTransformationMetaQuery(self, transName, queryType):
438+
if not (result := self.checkPermissions(transName))["OK"]:
439+
return result
366440
return self.transformationDB.getTransformationMetaQuery(transName, queryType)
367441

368442
####################################################################
@@ -373,6 +447,8 @@ def export_getTransformationMetaQuery(self, transName, queryType):
373447
types_getTransformationLogging = [[int, str]]
374448

375449
def export_getTransformationLogging(self, transName):
450+
if not (result := self.checkPermissions(transName))["OK"]:
451+
return result
376452
return self.transformationDB.getTransformationLogging(transName)
377453

378454
####################################################################
@@ -383,6 +459,8 @@ def export_getTransformationLogging(self, transName):
383459
types_getAdditionalParameters = [[int, str]]
384460

385461
def export_getAdditionalParameters(self, transName):
462+
if not (result := self.checkPermissions(transName))["OK"]:
463+
return result
386464
return self.transformationDB.getAdditionalParameters(transName)
387465

388466
####################################################################

0 commit comments

Comments
 (0)