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
34 changes: 34 additions & 0 deletions src/DIRAC/FrameworkSystem/Service/DisetTokenManagerHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""TokenManager service is responsible for token management, namely storing, updating,
requesting new tokens for DIRAC components that have the appropriate permissions.

.. literalinclude:: ../ConfigTemplate.cfg
:start-after: ##BEGIN TokenManager:
:end-before: ##END
:dedent: 2
:caption: TokenManager options

The most common use of this service is to obtain tokens with certain scope to return to the user for its purposes,
or to provide to the DIRAC service to perform asynchronous tasks on behalf of the user.
This is mainly about the :py:meth:`export_getToken` method.

.. image:: /_static/Systems/FS/TokenManager_getToken.png
:alt: https://dirac.readthedocs.io/en/integration/_images/TokenManager_getToken.png (source https://github.com/TaykYoku/DIRACIMGS/raw/main/TokenManagerService_getToken.ai)

The client has a mechanism for caching the received tokens.
This helps reduce the number of requests to both the service and the Identity Provider (IdP).

If the client has a valid **access token** in the cache, it is used until it expires.
After that you need to update. The client can update it independently if on the server where it is in ``dirac.cfg``
``client_id`` and ``client_secret`` of the Identity Provider client are registered.

Otherwise, the client makes an RPC call to the **TornadoManager** service.
The ``refresh token`` from :py:class:`TokenDB <DIRAC.FrameworkSystem.DB.TokenDB.TokenDB>`
is taken and the **exchange token** request to Identity Provider is made.
"""

from DIRAC.Core.DISET.RequestHandler import RequestHandler
from DIRAC.FrameworkSystem.Service.TokenManagerHandler import TokenManagerHandlerMixin


class DisetTokenManagerHandler(TokenManagerHandlerMixin, RequestHandler):
pass
25 changes: 20 additions & 5 deletions src/DIRAC/FrameworkSystem/Service/TokenManagerHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,17 @@

from DIRAC import S_OK, S_ERROR
from DIRAC.Core.Security import Properties
from DIRAC.Core.Utilities import ThreadSafe
from DIRAC.Core.Utilities.DictCache import DictCache
from DIRAC.Core.Tornado.Server.TornadoService import TornadoService
from DIRAC.FrameworkSystem.DB.TokenDB import TokenDB
from DIRAC.ConfigurationSystem.Client.Helpers import Registry
from DIRAC.Resources.IdProvider.IdProviderFactory import IdProviderFactory
from DIRAC.FrameworkSystem.Utilities.TokenManagementUtilities import (
getIdProviderClient,
getCachedKey,
DEFAULT_RT_EXPIRATION_TIME,
DEFAULT_AT_EXPIRATION_TIME,
)


class TokenManagerHandler(TornadoService):
class TokenManagerHandlerMixin:
DEFAULT_AUTHORIZATION = ["authenticated"]

@classmethod
Expand All @@ -69,6 +65,9 @@ def initializeHandler(cls, *args):

return S_OK()

auth_getUserTokensInfo = ["authenticated"]
types_getUserTokensInfo = []

def export_getUserTokensInfo(self):
"""Generate information dict about user tokens

Expand All @@ -89,6 +88,7 @@ def export_getUserTokensInfo(self):
return S_OK(tokensInfo)

auth_getUsersTokensInfo = [Properties.PROXY_MANAGEMENT]
types_getUserTokensInfo = [list]

def export_getUsersTokensInfo(self, users: list):
"""Get the info about the user tokens in the database
Expand Down Expand Up @@ -119,6 +119,8 @@ def export_getUsersTokensInfo(self, users: list):
tokensInfo.append(tokenDict)
return S_OK(tokensInfo)

types_updateToken = [dict, str, str, int]

def export_updateToken(self, token: dict, userID: str, provider: str, rt_expired_in: int = 24 * 3600):
"""Using this method, you can transfer user tokens for storage in the TokenManager.

Expand Down Expand Up @@ -173,6 +175,8 @@ def __checkProperties(self, requestedUserDN: str, requestedUserGroup: str):
# Not authorized!
return S_ERROR("You can't get tokens!")

types_getToken = [None, None, None, None, None]

def export_getToken(
self,
username: str = None,
Expand Down Expand Up @@ -215,6 +219,9 @@ def export_getToken(

# Get the client token with requested scope and audience
result = idpObj.fetchToken(grant_type="client_credentials", scope=scope, audience=audience)
# DEncode can not encode OAuth2Token object
if result["OK"]:
result["Value"] = dict(result["Value"])

return result

Expand Down Expand Up @@ -244,6 +251,8 @@ def export_getToken(
# Collect all errors when trying to get a token, or if no user ID is registered
return S_ERROR("; ".join(err or [f"No user ID found for {username}"]))

types_deleteToken = [str]

def export_deleteToken(self, userDN: str):
"""Delete a token from the DB

Expand All @@ -263,6 +272,8 @@ def export_deleteToken(self, userDN: str):
result = Registry.getIDFromDN(userDN)
return self.__tokenDB.removeToken(user_id=result["Value"]) if result["OK"] else result

types_getTokensByUserID = [str]

def export_getTokensByUserID(self, userID: str):
"""Retrieve a token from the DB

Expand All @@ -271,3 +282,7 @@ def export_getTokensByUserID(self, userID: str):
:return: S_OK(list)/S_ERROR() token row in dict format
"""
return self.__tokenDB.getTokensByUserID(userID)


class TokenManagerHandler(TokenManagerHandlerMixin, TornadoService):
pass