Skip to content
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
47 changes: 18 additions & 29 deletions src/DIRAC/FrameworkSystem/DB/ProxyDB.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
""" ProxyDB class is a front-end to the ProxyDB MySQL database.
"""ProxyDB class is a front-end to the ProxyDB MySQL database.

Database contains the following tables:
Database contains the following tables:

* ProxyDB_Requests -- a delegation requests storage table for a given proxy Chain
* ProxyDB_CleanProxies -- table for storing proxies in "clean" form, ie without
the presence of DIRAC and VOMS extensions.
* ProxyDB_VOMSProxies -- proxy storage table with VOMS extension already added.
* ProxyDB_Log -- table with logs.
* ProxyDB_Requests -- a delegation requests storage table for a given proxy Chain
* ProxyDB_CleanProxies -- table for storing proxies in "clean" form, ie without
the presence of DIRAC and VOMS extensions.
* ProxyDB_VOMSProxies -- proxy storage table with VOMS extension already added.
* ProxyDB_Log -- table with logs.
"""

import textwrap
from threading import Lock

Expand Down Expand Up @@ -167,8 +168,7 @@ def generateDelegationRequest(self, proxyChain, userDN):
data = retVal["Value"]
if not data:
return S_ERROR("Insertion of the request in the db didn't work as expected")
userGroup = proxyChain.getDIRACGroup().get("Value") or "unset"
self.logAction("request upload", userDN, userGroup, userDN, "any")
self.logAction("request upload", userDN, userDN)
# Here we go!
return S_OK({"id": data[0][0], "request": reqStr})

Expand Down Expand Up @@ -249,8 +249,7 @@ def completeDelegation(self, requestId, userDN, delegatedPem):
return self.deleteRequest(requestId) if retVal["OK"] else retVal

def __storeProxy(self, userDN, chain, proxyProvider=None):
"""Store user proxy into the Proxy repository for a user specified by his
DN and group or proxy provider.
"""Store user proxy into the Proxy repository for a user specified by their DN

:param str userDN: user DN from proxy
:param X509Chain() chain: proxy chain
Expand Down Expand Up @@ -353,7 +352,7 @@ def __storeProxy(self, userDN, chain, proxyProvider=None):
sqlSet.append(f"{k} = {dValues[k]}")
cmd = f"UPDATE `{sTable}` SET {', '.join(sqlSet)} WHERE {' AND '.join(sqlWhere)}"

self.logAction("store proxy", userDN, proxyProvider, userDN, proxyProvider)
self.logAction("store proxy", userDN, userDN)
return self._update(cmd)

def purgeExpiredProxies(self, sendNotifications=True):
Expand All @@ -376,23 +375,17 @@ def purgeExpiredProxies(self, sendNotifications=True):
return result
return S_OK(purged)

def deleteProxy(self, userDN, userGroup=None, proxyProvider=None):
def deleteProxy(self, userDN):
"""Remove proxy of the given user from the repository

:param str userDN: user DN
:param str userGroup: DIRAC group
:param str proxyProvider: proxy provider name

:return: S_OK()/S_ERROR()
"""
try:
userDN = self._escapeString(userDN)["Value"]
if userGroup:
userGroup = self._escapeString(userGroup)["Value"]
if proxyProvider:
proxyProvider = self._escapeString(proxyProvider)["Value"]
except KeyError:
return S_ERROR("Invalid DN or group or proxy provider")
return S_ERROR("Invalid DN")
errMsgs = []
req = f"DELETE FROM `ProxyDB_CleanProxies` WHERE UserDN={userDN}"
result = self._update(req)
Expand Down Expand Up @@ -552,7 +545,7 @@ def __getProxyFromProxyProviders(self, userDN, userGroup, requiredLifeTime):
result = chain.generateProxyToString(remainingSecs, diracGroup=userGroup)
if result["OK"]:
return S_OK((result["Value"], remainingSecs))
errMsgs.append(f"\"{proxyProvider}\": {result['Message']}")
errMsgs.append(f'"{proxyProvider}": {result["Message"]}')

return S_ERROR("Cannot generate proxy%s" % (errMsgs and ": " + ", ".join(errMsgs) or ""))

Expand Down Expand Up @@ -592,8 +585,8 @@ def getProxy(self, userDN, userGroup, requiredLifeTime=None):

# Proxy is invalid for some reason, let's delete it
if not chain.isValidProxy()["OK"]:
self.deleteProxy(userDN, userGroup)
return S_ERROR(DErrno.EPROXYFIND, f"{userDN}@{userGroup} has no proxy registered")
self.deleteProxy(userDN)
return S_ERROR(DErrno.EPROXYFIND, f"{userDN} has no proxy registered")
return S_OK((chain, timeLeft))

def __getVOMSAttribute(self, userGroup, requiredVOMSAttribute=False):
Expand Down Expand Up @@ -824,27 +817,23 @@ def getProxiesContent(self, selDict, sortList, start=0, limit=0):
totalRecords = len(data)
return S_OK({"ParameterNames": fields, "Records": data, "TotalRecords": totalRecords})

def logAction(self, action, issuerDN, issuerGroup, targetDN, targetGroup):
def logAction(self, action, issuerDN, targetDN):
"""Add an action to the log

:param str action: proxy action
:param str issuerDN: user DN of issuer
:param str issuerGroup: DIRAC group of issuer
:param str targetDN: user DN of target
:param str targetGroup: DIRAC group of target

:return: S_ERROR()
"""
try:
sAction = self._escapeString(action)["Value"]
sIssuerDN = self._escapeString(issuerDN)["Value"]
sIssuerGroup = self._escapeString(issuerGroup)["Value"]
sTargetDN = self._escapeString(targetDN)["Value"]
sTargetGroup = self._escapeString(targetGroup)["Value"]
except KeyError:
return S_ERROR("Can't escape from death")
cmd = "INSERT INTO `ProxyDB_Log` ( Action, IssuerDN, IssuerGroup, TargetDN, TargetGroup, Timestamp ) VALUES "
cmd += f"( {sAction}, {sIssuerDN}, {sIssuerGroup}, {sTargetDN}, {sTargetGroup}, UTC_TIMESTAMP() )"
cmd += f"( {sAction}, {sIssuerDN}, 'IssuerGroup' {sTargetDN}, 'TargetGroup', UTC_TIMESTAMP() )"
retVal = self._update(cmd)
if not retVal["OK"]:
self.log.error("Can't add a proxy action log: ", retVal["Message"])
Expand Down
17 changes: 7 additions & 10 deletions src/DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def export_completeDelegationUpload(self, requestId, pemChain):
:return: S_OK(dict)/S_ERROR() -- dict contain proxies
"""
credDict = self.getRemoteCredentials()
userId = f'{credDict["username"]}:{credDict["group"]}'
userId = f"{credDict['username']}:{credDict['group']}"
retVal = self.__proxyDB.completeDelegation(requestId, credDict["DN"], pemChain)
if not retVal["OK"]:
gLogger.error("Upload proxy failed", f"id: {requestId} user: {userId} message: {retVal['Message']}")
Expand Down Expand Up @@ -240,7 +240,7 @@ def __getVOMSProxy(self, userDN, userGroup, requestPem, requiredLifetime, vomsAt
requiredLifetime = int(min(secsLeft, requiredLifetime * self.__maxExtraLifeFactor))
return chain.generateChainFromRequestString(requestPem, lifetime=requiredLifetime, requireLimited=forceLimited)

types_deleteProxyBundle = [(list, tuple)]
types_deleteProxyBundle = [list]

def export_deleteProxyBundle(self, idList):
"""delete a list of id's
Expand All @@ -252,9 +252,7 @@ def export_deleteProxyBundle(self, idList):
errorInDelete = []
deleted = 0
for _id in idList:
if len(_id) != 2:
errorInDelete.append(f"{str(_id)} doesn't have two fields")
retVal = self.export_deleteProxy(_id[0], _id[1])
retVal = self.export_deleteProxy(_id)
if not retVal["OK"]:
errorInDelete.append(f"{str(_id)} : {retVal['Message']}")
else:
Expand All @@ -263,24 +261,23 @@ def export_deleteProxyBundle(self, idList):
return S_ERROR(f"Could not delete some proxies: {','.join(errorInDelete)}")
return S_OK(deleted)

types_deleteProxy = [(list, tuple)]
types_deleteProxy = [str]

def export_deleteProxy(self, userDN, userGroup):
def export_deleteProxy(self, userDN):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would break the existing API. I assume communities have any client depending on it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be announced in the release notes, and a minor release can be done. At the same time this deleteProxy is never called anywhere in (LHCb)(Web)DIRAC, and I doubt that it is ever used anywhere else.

"""Delete a proxy from the DB

:param str userDN: user DN
:param str userGroup: DIRAC group

:return: S_OK()/S_ERROR()
"""
credDict = self.getRemoteCredentials()
if Properties.PROXY_MANAGEMENT not in credDict["properties"]:
if userDN != credDict["DN"]:
return S_ERROR("You aren't allowed!")
retVal = self.__proxyDB.deleteProxy(userDN, userGroup)
retVal = self.__proxyDB.deleteProxy(userDN)
if not retVal["OK"]:
return retVal
self.__proxyDB.logAction("delete proxy", credDict["DN"], credDict["group"], userDN, userGroup)
self.__proxyDB.logAction("delete proxy", credDict["DN"], userDN)
return S_OK()

types_getContents = [dict, (list, tuple), int, int]
Expand Down
74 changes: 8 additions & 66 deletions src/DIRAC/FrameworkSystem/scripts/dirac_proxy_destroy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
Example:
$ dirac-proxy-destroy -a
"""

import os

import DIRAC
from DIRAC import S_OK, gLogger
from DIRAC.ConfigurationSystem.Client.Helpers import Registry
from DIRAC.Core.Base.Client import Client
from DIRAC.Core.Base.Script import Script
from DIRAC.Core.Security import Locations, ProxyInfo
from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager
Expand All @@ -25,16 +24,8 @@ def __init__(self):
"""
creates a Params class with default values
"""
self.vos = []
self.delete_all = False

def addVO(self, voname):
"""
adds a VO to be deleted from remote proxies
"""
self.vos.append(voname)
return S_OK()

def setDeleteAll(self, _):
"""
deletes local and remote proxies
Expand All @@ -46,7 +37,7 @@ def needsValidProxy(self):
"""
returns true if any remote operations are required
"""
return self.vos or self.delete_all
return self.delete_all

# note the magic : and =
def registerCLISwitches(self):
Expand All @@ -56,49 +47,19 @@ def registerCLISwitches(self):
Script.registerSwitch(
"a", "all", "Delete the local and all uploaded proxies (the nuclear option)", self.setDeleteAll
)
Script.registerSwitch("v:", "vo=", "Delete uploaded proxy for vo name given", self.addVO)


def getProxyGroups():
"""
Returns a set of all remote proxy groups stored on the dirac server for the user invoking the command.
"""
proxies = gProxyManager.getUserProxiesInfo()
if not proxies["OK"]:
raise RuntimeError("Could not retrieve uploaded proxy info.")

user_groups = set()
for dn in proxies["Value"]:
dn_groups = set(proxies["Value"][dn].keys())
user_groups.update(dn_groups)

return user_groups


def mapVoToGroups(voname):
"""
Returns all groups available for a given VO as a set.
"""

vo_dict = Registry.getGroupsForVO(voname)
if not vo_dict["OK"]:
raise RuntimeError(f"Could not retrieve groups for vo {voname}.")

return set(vo_dict["Value"])


def deleteRemoteProxy(userdn, vogroup):
def deleteRemoteProxy(userdn):
"""
Deletes proxy for a vogroup for the user envoking this function.
Returns a list of all deleted proxies (if any).
"""
rpcClient = Client(url="Framework/ProxyManager")
retVal = rpcClient.deleteProxyBundle([(userdn, vogroup)])
retVal = gProxyManager.deleteProxyBundle([(userdn)])

if retVal["OK"]:
gLogger.notice(f"Deleted proxy for {vogroup}.")
gLogger.notice("Deleted proxy.")
else:
gLogger.error(f"Failed to delete proxy for {vogroup}.")
gLogger.error("Failed to delete proxy.")


def deleteLocalProxy(proxyLoc):
Expand All @@ -123,7 +84,7 @@ def run():

Script.parseCommandLine(ignoreErrors=True)

if options.delete_all and options.vos:
if options.delete_all:
gLogger.error("-a and -v options are mutually exclusive. Please pick one or the other.")
return 1

Expand All @@ -142,27 +103,8 @@ def run():

userDN = result["Value"]["identity"]

deleteRemoteProxy(userDN)
if options.delete_all:
# delete remote proxies
remote_groups = getProxyGroups()
if not remote_groups:
gLogger.notice("No remote proxies found.")
for vo_group in remote_groups:
deleteRemoteProxy(userDN, vo_group)
# delete local proxy
deleteLocalProxy(proxyLoc)
elif options.vos:
vo_groups = set()
for voname in options.vos:
vo_groups.update(mapVoToGroups(voname))
# filter set of all groups to only contain groups for which there is a user proxy
user_groups = getProxyGroups()
vo_groups.intersection_update(user_groups)
if not vo_groups:
gLogger.notice("You have no proxies registered for any of the specified VOs.")
for group in vo_groups:
deleteRemoteProxy(userDN, group)
else:
deleteLocalProxy(proxyLoc)

return 0
Expand Down
18 changes: 8 additions & 10 deletions tests/Integration/Framework/Test_ProxyDB.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" This is a test of the ProxyDB
It supposes that the DB is present and installed in DIRAC
"""This is a test of the ProxyDB
It supposes that the DB is present and installed in DIRAC
"""

# pylint: disable=invalid-name,wrong-import-position,protected-access
import os
import re
Expand All @@ -19,7 +20,6 @@

DIRAC.initialize(require_auth=False, host_credentials=True) # Initialize configuration

import DIRAC
from DIRAC import gLogger, gConfig, S_OK, S_ERROR
from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error
from DIRAC.FrameworkSystem.DB.ProxyDB import ProxyDB
Expand All @@ -39,8 +39,8 @@
DIRAC_CA
{{
ProviderType = DIRACCA
CertFile = {os.path.join(certsPath, 'ca/ca.cert.pem')}
KeyFile = {os.path.join(certsPath, 'ca/ca.key.pem')}
CertFile = {os.path.join(certsPath, "ca/ca.cert.pem")}
KeyFile = {os.path.join(certsPath, "ca/ca.key.pem")}
Supplied = C, O, OU, CN
Optional = emailAddress
DNOrder = C, O, OU, CN, emailAddress
Expand Down Expand Up @@ -391,7 +391,7 @@ def test_purgeExpiredProxies(self):
def test_getRemoveProxy(self):
"""Testing get, store proxy"""
gLogger.info("\n* Check that DB is clean..")
result = db.getProxiesContent({"UserName": ["user_ca", "user", "user_1" "user_2", "user_3"]}, {})
result = db.getProxiesContent({"UserName": ["user_ca", "user", "user_1", "user_2", "user_3"]}, {})
self.assertTrue(result["OK"], "\n" + result.get("Message", "Error message is absent."))
self.assertTrue(bool(int(result["Value"]["TotalRecords"]) == 0), "In DB present proxies.")

Expand Down Expand Up @@ -441,9 +441,7 @@ def test_getRemoveProxy(self):
)

gLogger.info("* Check that DB is clean..")
result = db.deleteProxy(
"/C=DN/O=DIRACCA/OU=None/CN=user_ca/emailAddress=user_ca@diracgrid.org", proxyProvider="DIRAC_CA"
)
result = db.deleteProxy("/C=DN/O=DIRACCA/OU=None/CN=user_ca/emailAddress=user_ca@diracgrid.org")
self.assertTrue(result["OK"], "\n" + result.get("Message", "Error message is absent."))
result = db.getProxiesContent({"UserName": ["user_ca", "user", "user_1", "user_2", "user_3"]}, {})
self.assertTrue(result["OK"], "\n" + result.get("Message", "Error message is absent."))
Expand Down Expand Up @@ -523,7 +521,7 @@ def test_getRemoveProxy(self):
gLogger.info(f"Msg: {result['Message']}")

gLogger.info("* Check that DB is clean..")
result = db.deleteProxy("/C=CC/O=DN/O=DIRAC/CN=user", proxyProvider="Certificate")
result = db.deleteProxy("/C=CC/O=DN/O=DIRAC/CN=user")
self.assertTrue(result["OK"], "\n" + result.get("Message", "Error message is absent."))
result = db.getProxiesContent({"UserName": ["user_ca", "user", "user_2", "user_3"]}, {})
self.assertTrue(result["OK"], "\n" + result.get("Message", "Error message is absent."))
Expand Down
Loading