Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.1] Add finer permission model for Transformation System #7170

Merged
merged 1 commit into from
Aug 14, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ are showed in the next table:
+----------------------------+------------------------------------------------------------------+-------------+
| *PrivateLimitedDelegation* | Allow getting only limited proxies for one self | |
+----------------------------+------------------------------------------------------------------+-------------+
| *ProductionManagement* | Allow managing production | |
| *ProductionManagement* | Allow managing all productions | |
+----------------------------+------------------------------------------------------------------+-------------+
| *ProductionSharing* | Allow managing productions owned by the same group | |
+----------------------------+------------------------------------------------------------------+-------------+
| *ProductionUser* | Allow managing productions owned by the same user | |
+----------------------------+------------------------------------------------------------------+-------------+
| *ProxyManagement* | Allow managing proxies | |
+----------------------------+------------------------------------------------------------------+-------------+
Expand Down
4 changes: 4 additions & 0 deletions docs/source/AdministratorGuide/Tutorials/installTS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ Using the ``Configuration Manager`` application in the WebApp, create a new sect
After restarting the ``ProxyManager``, you should now be able to get a proxy belonging to the ``dirac_prod`` group that
will be automatically uploaded.

The ``ProductionManagement`` property allows users in the group to access and change all transformations. There is also
a ``ProductionSharing`` property to only allow access to transformations in the same group and ``ProductionUser`` to
only allow users to access their own transformations.

Add a ProdManager Shifter
=========================

Expand Down
6 changes: 5 additions & 1 deletion src/DIRAC/Core/Security/Properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ class SecurityProperty(str, Enum):
PRIVATE_LIMITED_DELEGATION = "PrivateLimitedDelegation"
#: Allow managing proxies
PROXY_MANAGEMENT = "ProxyManagement"
#: Allow managing production
#: Allow managing all productions
PRODUCTION_MANAGEMENT = "ProductionManagement"
#: Allow managing all productions in the same group
PRODUCTION_SHARING = "ProductionSharing"
#: Allows user to manage productions they own only
PRODUCTION_USER = "ProductionUser"
#: Allow production request approval on behalf of PPG
PPG_AUTHORITY = "PPGAuthority"
#: Allow Bookkeeping Management
Expand Down
2 changes: 2 additions & 0 deletions src/DIRAC/FrameworkSystem/Client/ComponentInstaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
PROXY_MANAGEMENT,
SERVICE_ADMINISTRATOR,
TRUSTED_HOST,
PRODUCTION_MANAGEMENT,
)
from DIRAC.Core.Utilities.Extensions import (
extensionsByPriority,
Expand Down Expand Up @@ -438,6 +439,7 @@ def _getCentralCfg(self, installCfg):
FULL_DELEGATION,
PROXY_MANAGEMENT,
OPERATOR,
PRODUCTION_MANAGEMENT,
]

for section in (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
from DIRAC import S_OK, S_ERROR
from DIRAC.Core.DISET.RequestHandler import RequestHandler
from DIRAC.Core.Security.Properties import SecurityProperty
from DIRAC.Core.Utilities.DEncode import ignoreEncodeWarning
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
Expand Down Expand Up @@ -33,6 +34,32 @@ def initializeHandler(cls, serviceInfoDict):

return S_OK()

def checkPermissions(self, transName: str):
"""
checks if remote user has permission to access to a given transformation

:param str transName: Name of the transformation to check

:return: S_ERROR if user does not have permission or if transformation does not exist
S_OK otherwise
"""
credDict = self.getRemoteCredentials()
groupProperties = credDict.get("properties", [])
if SecurityProperty.PRODUCTION_MANAGEMENT in groupProperties:
return S_OK()
tfDetails = self.transformationDB.getTransformation(transName)
if not tfDetails["OK"]:
return S_ERROR(f"Could not retrieve transformation {transName} details for permissions check.")
authorGroup = tfDetails["Value"]["AuthorGroup"]
author = tfDetails["Value"]["Author"]
if SecurityProperty.PRODUCTION_SHARING in groupProperties:
if authorGroup == credDict.get("group", None):
return S_OK()
if SecurityProperty.PRODUCTION_USER in groupProperties:
if author == credDict.get("username", None):
return S_OK()
return S_ERROR(f"You do not have permissions for transformation {transName}")

types_getCounters = [str, list, dict]

@classmethod
Expand Down Expand Up @@ -70,6 +97,13 @@ def export_addTransformation(
credDict = self.getRemoteCredentials()
author = credDict.get("username")
authorGroup = credDict.get("group")
groupProperties = credDict.get("properties", [])
if (
SecurityProperty.PRODUCTION_MANAGEMENT not in groupProperties
and SecurityProperty.PRODUCTION_SHARING not in groupProperties
and SecurityProperty.PRODUCTION_USER not in groupProperties
):
return S_ERROR("You do not have permission to add a Transformation")
res = self.transformationDB.addTransformation(
transName,
description,
Expand Down Expand Up @@ -97,36 +131,45 @@ def export_addTransformation(
types_deleteTransformation = [[int, str]]

def export_deleteTransformation(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.deleteTransformation(transName, author=author)

types_completeTransformation = [[int, str]]

def export_completeTransformation(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.setTransformationParameter(transName, "Status", "Completed", author=author)

types_cleanTransformation = [[int, str]]

def export_cleanTransformation(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.cleanTransformation(transName, author=author)

types_setTransformationParameter = [[int, str], str]

def export_setTransformationParameter(self, transName, paramName, paramValue):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.setTransformationParameter(transName, paramName, paramValue, author=author)

types_deleteTransformationParameter = [[int, str], str]

@classmethod
def export_deleteTransformationParameter(cls, transName, paramName):
return cls.transformationDB.deleteTransformationParameter(transName, paramName)
def export_deleteTransformationParameter(self, transName, paramName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.deleteTransformationParameter(transName, paramName)

types_getTransformations = []

Expand Down Expand Up @@ -159,15 +202,21 @@ def export_getTransformations(

types_getTransformation = [[int, str]]

@classmethod
def export_getTransformation(cls, transName, extraParams=False):
return cls.transformationDB.getTransformation(transName, extraParams=extraParams)
def export_getTransformation(self, transName, extraParams=False):
# check first if transformation exists to avoid returning permissions error for non-existing transformation
tfDetails = self.transformationDB.getTransformation(transName, extraParams=extraParams)
if not tfDetails["OK"]:
return tfDetails
if not (result := self.checkPermissions(transName))["OK"]:
return result
return tfDetails

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

@classmethod
def export_getTransformationParameters(cls, transName, parameters):
return cls.transformationDB.getTransformationParameters(transName, parameters)
def export_getTransformationParameters(self, transName, parameters):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationParameters(transName, parameters)

types_getTransformationWithStatus = [[str, list, tuple]]

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

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

@classmethod
def export_addFilesToTransformation(cls, transName, lfns):
return cls.transformationDB.addFilesToTransformation(transName, lfns)
def export_addFilesToTransformation(self, transName, lfns):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.addFilesToTransformation(transName, lfns)

types_addTaskForTransformation = [[int, str]]

@classmethod
def export_addTaskForTransformation(cls, transName, lfns=[], se="Unknown"):
return cls.transformationDB.addTaskForTransformation(transName, lfns=lfns, se=se)
def export_addTaskForTransformation(self, transName, lfns=[], se="Unknown"):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.addTaskForTransformation(transName, lfns=lfns, se=se)

types_setFileStatusForTransformation = [[int, str], dict]

@classmethod
@ignoreEncodeWarning
def export_setFileStatusForTransformation(cls, transName, dictOfNewFilesStatus):
def export_setFileStatusForTransformation(self, transName, dictOfNewFilesStatus):
"""Sets the file status for the transformation.

The dictOfNewFilesStatus is a dictionary with the form:
{12345: ('StatusA', errorA), 6789: ('StatusB',errorB), ... } where the keys are fileIDs
The tuple may be a string with only the status if the client was from an older version
"""

if not (result := self.checkPermissions(transName))["OK"]:
return result
if not dictOfNewFilesStatus:
return S_OK({})

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

res = cls.transformationDB._getConnectionTransID(False, transName)
res = self.transformationDB._getConnectionTransID(False, transName)
if not res["OK"]:
return res
connection = res["Value"]["Connection"]
transID = res["Value"]["TransformationID"]

return cls.transformationDB.setFileStatusForTransformation(transID, newStatusForFileIDs, connection=connection)
return self.transformationDB.setFileStatusForTransformation(transID, newStatusForFileIDs, connection=connection)

types_getTransformationStats = [[int, str]]

@classmethod
def export_getTransformationStats(cls, transName):
return cls.transformationDB.getTransformationStats(transName)
def export_getTransformationStats(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationStats(transName)

types_getTransformationFilesCount = [[int, str], str]

@classmethod
def export_getTransformationFilesCount(cls, transName, field, selection={}):
return cls.transformationDB.getTransformationFilesCount(transName, field, selection=selection)
def export_getTransformationFilesCount(self, transName, field, selection={}):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationFilesCount(transName, field, selection=selection)

types_getTransformationFiles = []

@classmethod
def export_getTransformationFiles(
cls, condDict=None, older=None, newer=None, timeStamp="LastUpdate", orderAttribute=None, limit=None, offset=None
self,
condDict=None,
older=None,
newer=None,
timeStamp="LastUpdate",
orderAttribute=None,
limit=None,
offset=None,
):
if not condDict:
condDict = {}
return cls.transformationDB.getTransformationFiles(
return self.transformationDB.getTransformationFiles(
condDict=condDict,
older=older,
newer=newer,
Expand Down Expand Up @@ -286,32 +345,39 @@ def export_getTransformationTasks(

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

@classmethod
def export_setTaskStatus(cls, transName, taskID, status):
return cls.transformationDB.setTaskStatus(transName, taskID, status)
def export_setTaskStatus(self, transName, taskID, status):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.setTaskStatus(transName, taskID, status)

types_setTaskStatusAndWmsID = [[int, str], int, str, str]

@classmethod
def export_setTaskStatusAndWmsID(cls, transName, taskID, status, taskWmsID):
return cls.transformationDB.setTaskStatusAndWmsID(transName, taskID, status, taskWmsID)
def export_setTaskStatusAndWmsID(self, transName, taskID, status, taskWmsID):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.setTaskStatusAndWmsID(transName, taskID, status, taskWmsID)

types_getTransformationTaskStats = [[int, str]]

@classmethod
def export_getTransformationTaskStats(cls, transName):
return cls.transformationDB.getTransformationTaskStats(transName)
def export_getTransformationTaskStats(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationTaskStats(transName)

types_deleteTasks = [[int, str], int, int]

def export_deleteTasks(self, transName, taskMin, taskMax):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.deleteTasks(transName, taskMin, taskMax, author=author)

types_extendTransformation = [[int, str], int]

def export_extendTransformation(self, transName, nTasks):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.extendTransformation(transName, nTasks, author=author)
Expand All @@ -320,6 +386,8 @@ def export_extendTransformation(self, transName, nTasks):

def export_getTasksToSubmit(self, transName, numTasks, site=""):
"""Get information necessary for submission for a given number of tasks for a given transformation"""
if not (result := self.checkPermissions(transName))["OK"]:
return result
res = self.transformationDB.getTransformation(transName)
if not res["OK"]:
return res
Expand Down Expand Up @@ -349,20 +417,26 @@ def export_getTasksToSubmit(self, transName, numTasks, site=""):
types_createTransformationMetaQuery = [[int, str], dict, str]

def export_createTransformationMetaQuery(self, transName, queryDict, queryType):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.createTransformationMetaQuery(transName, queryDict, queryType, author=author)

types_deleteTransformationMetaQuery = [[int, str], str]

def export_deleteTransformationMetaQuery(self, transName, queryType):
if not (result := self.checkPermissions(transName))["OK"]:
return result
credDict = self.getRemoteCredentials()
author = credDict.get("username")
return self.transformationDB.deleteTransformationMetaQuery(transName, queryType, author=author)

types_getTransformationMetaQuery = [[int, str], str]

def export_getTransformationMetaQuery(self, transName, queryType):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationMetaQuery(transName, queryType)

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

def export_getTransformationLogging(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getTransformationLogging(transName)

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

def export_getAdditionalParameters(self, transName):
if not (result := self.checkPermissions(transName))["OK"]:
return result
return self.transformationDB.getAdditionalParameters(transName)

####################################################################
Expand Down
Loading
Loading