Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-10 08:39:00

0001 import json
0002 import os
0003 import socket
0004 from threading import Lock
0005 from typing import List
0006 
0007 from pandacommon.pandalogger.LogWrapper import LogWrapper
0008 from pandacommon.pandalogger.PandaLogger import PandaLogger
0009 from pandacommon.pandautils.PandaUtils import naive_utcnow
0010 
0011 from pandaserver.api.v1.common import generate_response, get_dn, request_validation
0012 from pandaserver.config import panda_config
0013 from pandaserver.proxycache import panda_proxy_cache, token_cache
0014 from pandaserver.srvcore import CoreUtils
0015 from pandaserver.srvcore.CoreUtils import clean_user_id, get_bare_dn
0016 from pandaserver.srvcore.panda_request import PandaRequest
0017 from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
0018 
0019 _logger = PandaLogger().getLogger("api_cred_management")
0020 
0021 # These global variables are initialized in the init_task_buffer method
0022 global_task_buffer = None
0023 global_dispatch_parameter_cache = None
0024 
0025 # These global variables don't depend on DB access and can be initialized here
0026 global_proxy_cache = panda_proxy_cache.MyProxyInterface()
0027 global_token_cache = token_cache.TokenCache()
0028 
0029 global_lock = Lock()
0030 
0031 
0032 def init_task_buffer(task_buffer: TaskBuffer) -> None:
0033     """
0034     Initialize the task buffer. This method needs to be called before any other method in this module.
0035     """
0036 
0037     with global_lock:
0038         global global_task_buffer
0039         global_task_buffer = task_buffer
0040 
0041         # This variable depends on having an initialized task buffer
0042         global global_dispatch_parameter_cache
0043         global_dispatch_parameter_cache = CoreUtils.CachedObject("dispatcher_params", 60 * 10, _get_dispatch_parameters, _logger)
0044 
0045         global global_token_cache_config
0046         global_token_cache_config = _read_token_cache_configuration()
0047 
0048 
0049 def _read_token_cache_configuration():
0050     # config of token cacher
0051     try:
0052         with open(panda_config.token_cache_config) as f:
0053             return json.load(f)
0054     except Exception:
0055         return {}
0056 
0057 
0058 def _get_dispatch_parameters():
0059     """
0060     Wrapper function around taskBuffer.get_special_dispatch_params to convert list to set since task buffer cannot return set
0061     """
0062     parameters = global_task_buffer.get_special_dispatch_params()
0063     for client_name, client_data in parameters["tokenKeys"].items():
0064         client_data["fullList"] = set(client_data["fullList"])
0065 
0066     return True, parameters
0067 
0068 
0069 def _validate_user_permissions(compact_name, tokenized=False) -> dict:
0070     allowed_names = global_dispatch_parameter_cache.get("allowProxy", [])
0071 
0072     # The user is allowed to get a proxy or token
0073     if compact_name in allowed_names:
0074         return True, ""
0075 
0076     # Permission denied for user
0077     secret = "proxy" if not tokenized else "access token"
0078     tmp_msg = f"failed: '{compact_name}' not in authorized users with 'p' in {panda_config.schemaMETA}.USERS.GRIDPREF to get {secret}"
0079 
0080     return False, tmp_msg
0081 
0082 
0083 @request_validation(_logger, secure=True, request_method="POST")
0084 def set_user_secrets(req: PandaRequest, key: str = None, value: str = None) -> dict:
0085     """
0086     Set user secrets
0087 
0088     Set a key-value pair to store in PanDA. Requires a secure connection.
0089 
0090     API details:
0091         HTTP Method: POST
0092         Path: /v1/creds/set_user_secrets
0093 
0094     Args:
0095         req(PandaRequest): internally generated request object
0096         key(str): Key to reference the secret
0097         value(str): Value of the secret
0098 
0099     Returns:
0100         dict: The system response. True for success, False for failure, and an error message.
0101     """
0102 
0103     tmp_logger = LogWrapper(_logger, f"set_user_secret-{naive_utcnow().isoformat('/')}")
0104 
0105     # Get client DN and clean it. The request validation has already verified that the DN is present.
0106     dn = get_dn(req)
0107     owner = clean_user_id(dn)
0108     tmp_logger.debug(f"Setting secret for client={owner}")
0109     success, message = global_task_buffer.set_user_secret(owner, key, value)
0110     return generate_response(success, message)
0111 
0112 
0113 @request_validation(_logger, secure=True, request_method="GET")
0114 def get_user_secrets(req: PandaRequest, keys: List[str] = None) -> dict:
0115     """
0116     Get user secrets
0117 
0118     Get the secrets for a user identified by a list of keys. Requires a secure connection.
0119 
0120     API details:
0121         HTTP Method: POST
0122         Path: /v1/creds/get_user_secrets
0123 
0124     Args:
0125         req(PandaRequest): internally generated request object
0126         keys(list of str, optional): List of keys to reference the secrets to retrieve
0127 
0128     Returns:
0129         dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the user secrets. When unsuccessful, the message field contains the error message.
0130     """
0131     tmp_logger = LogWrapper(_logger, f"get_user_secrets-{naive_utcnow().isoformat('/')}")
0132 
0133     # Get client DN and clean it. The request validation has already verified that the DN is present.
0134     dn = get_dn(req)
0135     owner = clean_user_id(dn)
0136     tmp_logger.debug(f"Start for client={owner}")
0137 
0138     # The task buffer method expects a comma-separated string of keys
0139     if keys is None:
0140         keys_str = ""
0141     else:
0142         keys_str = ",".join(keys)
0143 
0144     success, data_or_message = global_task_buffer.get_user_secrets(owner, keys_str)
0145     message, data = "", {}
0146     if success:
0147         data = data_or_message
0148     else:
0149         message = data_or_message
0150     tmp_logger.debug(f"Done for client={owner}")
0151     return generate_response(success, message, data)
0152 
0153 
0154 @request_validation(_logger, secure=True, request_method="GET")
0155 def get_key_pair(req: PandaRequest, public_key_name: str, private_key_name: str) -> dict:
0156     """
0157     Get key pair
0158 
0159     This function retrieves the distinguished name (DN) from the request and uses it to get a key pair. Requires a secure connection.
0160 
0161     API details:
0162         HTTP Method: GET
0163         Path: /v1/creds/get_key_pair
0164 
0165     Args:
0166         req(PandaRequest): internally generated request object
0167         public_key_name(str): The name of the public key.
0168         private_key_name(str): The name of the private key.
0169 
0170     Returns:
0171         dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the user secrets. When unsuccessful, the message field contains the error message.
0172     """
0173     tmp_logger = LogWrapper(_logger, f"get_key_pair {public_key_name}/{private_key_name}")
0174     tmp_logger.debug("Start")
0175 
0176     real_dn = get_dn(req)
0177 
0178     # get compact DN
0179     compact_dn = clean_user_id(real_dn)
0180 
0181     # check permission
0182     global_dispatch_parameter_cache.update()
0183     allowed_keys = global_dispatch_parameter_cache.get("allowKeyPair", [])
0184     if compact_dn not in allowed_keys:
0185         # permission denied
0186         tmp_msg = f"Failed since '{compact_dn}' not authorized with 'k' in {panda_config.schemaMETA}.USERS.GRIDPREF"
0187         tmp_logger.debug(tmp_msg)
0188         return generate_response(False, tmp_msg)
0189 
0190     # retrieve the key pairs
0191     key_pair_dict = global_dispatch_parameter_cache.get("keyPair", {})
0192 
0193     if public_key_name not in key_pair_dict:  # public key is missing
0194         tmp_msg = f"Failed for '{compact_dn}' since {public_key_name} is missing on {socket.getfqdn()}"
0195         tmp_logger.debug(tmp_msg)
0196         return generate_response(False, tmp_msg)
0197 
0198     if private_key_name not in key_pair_dict:  # private key is missing
0199         tmp_msg = f"Failed for '{compact_dn}' since {private_key_name} is missing on {socket.getfqdn()}"
0200         tmp_logger.debug(tmp_msg)
0201         return generate_response(False, tmp_msg)
0202 
0203     # key pair is available
0204     data = {"public_key": key_pair_dict[public_key_name], "private_key": key_pair_dict[private_key_name]}
0205     tmp_logger.debug(f"Done with key-pair for '{compact_dn}'")
0206 
0207     return generate_response(True, data=data)
0208 
0209 
0210 @request_validation(_logger, secure=True, request_method="GET")
0211 def get_proxy(req: PandaRequest, role: str = None, dn: str = None) -> dict:
0212     """
0213     Get proxy
0214 
0215     Get the x509 proxy certificate for a user with a role. Requires a secure connection.
0216 
0217     API details:
0218         HTTP Method: GET
0219         Path: /v1/creds/get_proxy
0220 
0221     Args:
0222         req(PandaRequest): internally generated request object
0223         role(str, optional): The role of the user. Defaults to None.
0224         dn(str, optional): The distinguished name of the user. Defaults to None.
0225 
0226     Returns:
0227         dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the x509 proxy. When unsuccessful, the message field contains the error message.
0228     """
0229     tmp_logger = LogWrapper(_logger, f"get_proxy PID={os.getpid()}")
0230 
0231     # Set the requested DN or, if not set, the requestor DN
0232     real_dn = get_dn(req)
0233     target_dn = dn if dn else real_dn
0234 
0235     # Default roles
0236     if role == "":
0237         role = None
0238 
0239     tmp_logger.debug(f"Start for DN='{real_dn}' role={role}")
0240 
0241     # check permission
0242     global_dispatch_parameter_cache.update()
0243     real_dn_compact = clean_user_id(real_dn)
0244     allowed, tmp_msg = _validate_user_permissions(real_dn_compact, tokenized=False)
0245     if not allowed:
0246         tmp_logger.debug(tmp_msg)
0247         return generate_response(False, tmp_msg)
0248 
0249     # remove redundant extensions and retrieve the proxy
0250     target_dn_bare = get_bare_dn(target_dn, keep_digits=False)
0251     output = global_proxy_cache.retrieve(target_dn_bare, role=role)
0252 
0253     # proxy not found
0254     if output is None:
0255         tmp_msg = f"'proxy' not found for {target_dn_bare}"
0256         tmp_logger.debug(tmp_msg)
0257         return generate_response(False, tmp_msg)
0258 
0259     data = {"user_proxy": output}
0260     tmp_logger.debug(f"Done")
0261     return generate_response(True, data=data)
0262 
0263 
0264 @request_validation(_logger, secure=True, request_method="GET")
0265 def get_access_token(req: PandaRequest, client_name: str, token_key: str = None) -> dict:
0266     """
0267     Get access token
0268 
0269     Get the OAuth access token for the specified client. Requires a secure connection.
0270 
0271     API details:
0272         HTTP Method: GET
0273         Path: /v1/creds/get_access_token
0274 
0275     Args:
0276         req(PandaRequest): internally generated request object
0277         client_name(str): client_name for the token as defined in token_cache_config
0278         token_key(str, optional): key to get the token from the token cache. Defaults to None.
0279 
0280     Returns:
0281         dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the access token. When unsuccessful, the message field contains the error message.
0282     """
0283 
0284     tmp_logger = LogWrapper(_logger, f"get_proxy PID={os.getpid()}")
0285 
0286     # Set the requested DN or, if not set, the requestor DN
0287     real_dn = get_dn(req)
0288     target_dn = client_name if client_name else real_dn
0289 
0290     tmp_logger.debug(f"Start for DN='{real_dn}' target_dn='{target_dn}' token_key={token_key}")
0291 
0292     # check permission
0293     global_dispatch_parameter_cache.update()
0294     real_dn_compact = clean_user_id(real_dn)
0295     allowed, tmp_msg = _validate_user_permissions(real_dn_compact, tokenized=True)
0296     if not allowed:
0297         tmp_logger.debug(tmp_msg)
0298         return generate_response(False, tmp_msg)
0299 
0300     # check if the token key is valid
0301     if target_dn in global_token_cache_config and global_token_cache_config[target_dn].get("use_token_key") is True:
0302         if (
0303             target_dn not in global_dispatch_parameter_cache["tokenKeys"]
0304             or token_key not in global_dispatch_parameter_cache["tokenKeys"][target_dn]["fullList"]
0305         ):
0306             tmp_msg = f"failed since token key is invalid for {target_dn}"
0307             tmp_logger.debug(tmp_msg)
0308             return generate_response(False, tmp_msg)
0309 
0310     # remove redundant extensions
0311     target_dn_bare = get_bare_dn(target_dn, keep_digits=False)
0312 
0313     # get token
0314     output = global_token_cache.get_access_token(target_dn_bare)
0315 
0316     # access token not found
0317     if output is None:
0318         tmp_msg = f"'token' not found for {target_dn_bare}"
0319         tmp_logger.debug(tmp_msg)
0320         return generate_response(False, tmp_msg)
0321 
0322     data = {"user_proxy": output}
0323     tmp_logger.debug("Done")
0324     return generate_response(True, data=data)
0325 
0326 
0327 @request_validation(_logger, secure=True, request_method="GET")
0328 def get_token_key(req: PandaRequest, client_name: str) -> dict:
0329     """
0330     Get token key
0331 
0332     This function retrieves the distinguished name (DN) from the request and uses it to get a token key for the specified client. Requires a secure connection.
0333 
0334     API details:
0335         HTTP Method: GET
0336         Path: /v1/creds/get_token_key
0337 
0338     Args:
0339         req(PandaRequest): internally generated request object
0340         client_name (str): The name of the client requesting the token key
0341 
0342     Returns:
0343         dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the token key. When unsuccessful, the message field contains the error message.
0344     """
0345     tmp_logger = LogWrapper(_logger, f"get_token_key client={client_name} PID={os.getpid()}")
0346     tmp_logger.debug("Start")
0347 
0348     # get DN and clean it
0349     real_dn = get_dn(req)
0350     compact_name = clean_user_id(real_dn)
0351 
0352     global_dispatch_parameter_cache.update()
0353 
0354     # check if user is allowed to retrieved token keys
0355     allowed_users = global_dispatch_parameter_cache.get("allowTokenKey", [])
0356     if compact_name not in allowed_users:
0357         # permission denied
0358         tmp_msg = f"Denied since '{compact_name}' not authorized with 't' in {panda_config.schemaMETA}.USERS.GRIDPREF"
0359         tmp_logger.debug(tmp_msg)
0360         return generate_response(False, tmp_msg)
0361 
0362     # token key is missing
0363     if client_name not in global_dispatch_parameter_cache["tokenKeys"]:
0364         tmp_msg = f"Token key is missing for '{client_name}'"
0365         tmp_logger.debug(tmp_msg)
0366         return generate_response(False, tmp_msg)
0367 
0368     # send token key
0369     data = {"tokenKey": global_dispatch_parameter_cache["tokenKeys"][client_name]["latest"]}
0370     tmp_logger.debug(f"Done getting token key for '{compact_name}'")
0371 
0372     return generate_response(True, data=data)