File indexing completed on 2026-04-10 08:39:02
0001 from pandaserver.config import panda_config
0002 from pandaserver.srvcore.CoreUtils import CacheDict
0003
0004 if panda_config.token_authType is None:
0005 pass
0006 elif panda_config.token_authType == "scitokens":
0007 import scitokens
0008 else:
0009 from pandaserver.srvcore.oidc_utils import TokenDecoder
0010
0011 token_decoder = TokenDecoder()
0012
0013 import traceback
0014
0015 import jwt
0016
0017 cache_dict = CacheDict()
0018
0019
0020
0021 def decode_token(serialized_token, env, tmp_log):
0022 authenticated = False
0023 message_str = None
0024 subprocess_env = {}
0025 try:
0026 vo = None
0027 role = None
0028 if panda_config.token_authType == "oidc":
0029 if "HTTP_ORIGIN" in env:
0030
0031 vo = env["HTTP_ORIGIN"]
0032
0033 else:
0034 vo = None
0035
0036 vo_role = vo.replace(":", ".")
0037 token = token_decoder.deserialize_token(serialized_token, panda_config.auth_config, vo, tmp_log, panda_config.legacy_token_issuers)
0038
0039 if "vo" in token:
0040 vo_raw = token["vo"]
0041 tmp_log.debug(f"Raw VO from token: {vo_raw}")
0042
0043 if vo_raw.startswith("vo."):
0044
0045 if ":" in vo_raw:
0046 vo, role = vo_raw.split(":", 1)
0047 else:
0048 vo, role = vo_raw, None
0049 else:
0050
0051 if ":" in vo_raw:
0052 vo, role = vo_raw.split(":", 1)
0053 elif "." in vo_raw:
0054 parts = vo_raw.rsplit(".", 1)
0055 vo, role = parts[0], parts[1]
0056 else:
0057 vo, role = vo_raw, None
0058
0059 tmp_log.debug(f"Parsed VO: {vo}, role: {role}")
0060
0061
0062 if vo not in panda_config.auth_policies:
0063 tmp_log.error(f"VO '{vo}' not found in auth_policies: {list(panda_config.auth_policies.keys())}")
0064 message_str = f"Unknown vo : {vo}"
0065 else:
0066
0067 groups_claim_name = "groups"
0068 if vo_role in panda_config.auth_vo_dict:
0069 jwt_profile = panda_config.auth_vo_dict[vo_role].get("jwt_profile")
0070 if jwt_profile == "wlcg":
0071 groups_claim_name = "wlcg.groups"
0072
0073 if vo_role in panda_config.auth_vo_dict and "robot_ids" in panda_config.auth_vo_dict[vo_role]:
0074 robot_ids = panda_config.auth_vo_dict[vo_role].get("robot_ids")
0075 if isinstance(robot_ids, str):
0076 robot_ids = robot_ids.split(",")
0077 if not robot_ids:
0078 robot_ids = []
0079 robot_ids = [i for i in robot_ids if i]
0080 if token["sub"] in robot_ids:
0081 if groups_claim_name not in token:
0082 if role:
0083 token[groups_claim_name] = [f"{vo}/{role}"]
0084 else:
0085 token[groups_claim_name] = [f"{vo}"]
0086 if "name" not in token:
0087 token["name"] = f"robot {role}"
0088
0089 if role:
0090 if f"{vo}/{role}" not in token[groups_claim_name] and f"/{vo}/{role}" not in token[groups_claim_name]:
0091 message_str = f"Not a member of the {vo}/{role} group"
0092 else:
0093 subprocess_env["PANDA_OIDC_VO"] = vo
0094 subprocess_env["PANDA_OIDC_GROUP"] = role
0095 subprocess_env["PANDA_OIDC_ROLE"] = role
0096 authenticated = True
0097 else:
0098 for member_string, member_info in panda_config.auth_policies[vo]:
0099 if member_string in token[groups_claim_name] or f"/{member_string}" in token[groups_claim_name]:
0100 subprocess_env["PANDA_OIDC_VO"] = vo
0101 subprocess_env["PANDA_OIDC_GROUP"] = member_info["group"]
0102 subprocess_env["PANDA_OIDC_ROLE"] = member_info["role"]
0103 authenticated = True
0104 break
0105 if not authenticated:
0106 message_str = f"Not a member of the {vo} group"
0107 else:
0108 token = scitokens.SciToken.deserialize(serialized_token, audience=panda_config.token_audience)
0109
0110
0111 if "iss" not in token:
0112 message_str = "Issuer is undefined in the token"
0113 else:
0114 if panda_config.token_authType == "scitokens":
0115 items = token.claims()
0116 else:
0117 items = token.items()
0118 for c, v in items:
0119 subprocess_env[f"PANDA_OIDC_CLAIM_{str(c)}"] = str(v)
0120
0121 if "SSL_CLIENT_S_DN" not in env:
0122 if "name" in token:
0123 subprocess_env["SSL_CLIENT_S_DN"] = " ".join([t[:1].upper() + t[1:].lower() for t in str(token["name"]).split()])
0124 if "preferred_username" in token:
0125 subprocess_env["SSL_CLIENT_S_DN"] += f"/CN=nickname:{token['preferred_username']}"
0126 else:
0127 subprocess_env["SSL_CLIENT_S_DN"] = str(token["sub"])
0128 i = 0
0129 for scope in token.get("scope", "").split():
0130 if scope.startswith("role:"):
0131 subprocess_env[f"GRST_CRED_AUTH_TOKEN_{i}"] = "VOMS " + str(scope.split(":")[-1])
0132 i += 1
0133 if role:
0134 subprocess_env[f"GRST_CRED_AUTH_TOKEN_{i}"] = f"VOMS /{vo}/Role={role}"
0135 i += 1
0136 else:
0137
0138 subprocess_env["SSL_CLIENT_S_DN"] = env["SSL_CLIENT_S_DN"]
0139 for key in env:
0140 if key.startswith("GRST_CRED_") or key.startswith("GRST_CONN_"):
0141 subprocess_env[key] = env[key]
0142
0143 except jwt.exceptions.InvalidTokenError as e:
0144 message_str = f"Invalid token. {str(e)}"
0145 except Exception as e:
0146 message_str = f"Token decode failure. {str(e)} {traceback.format_exc()}"
0147 return {"authenticated": authenticated, "message": message_str, "subprocess_env": subprocess_env}
0148
0149
0150
0151 class PandaRequest:
0152 def __init__(self, env, tmp_log):
0153
0154 self.subprocess_env = env
0155
0156 self.headers_in = {}
0157
0158 self.authenticated = True
0159
0160 self.message = None
0161
0162
0163 if "CONTENT_LENGTH" in self.subprocess_env:
0164 self.headers_in["content-length"] = self.subprocess_env["CONTENT_LENGTH"]
0165
0166
0167 try:
0168 if panda_config.token_authType in ["scitokens", "oidc"] and "HTTP_AUTHORIZATION" in env:
0169 serialized_token = env["HTTP_AUTHORIZATION"].split()[1]
0170 decision_key = f"""{serialized_token} : {env.get("HTTP_ORIGIN", None)} : {env.get("SSL_CLIENT_S_DN", None)}"""
0171 cached_decision = cache_dict.get(decision_key, tmp_log, decode_token, serialized_token, env, tmp_log)
0172 self.authenticated = cached_decision["authenticated"]
0173 self.message = cached_decision["message"]
0174 if self.message:
0175 tmp_log.error(f"""{self.message} - Origin: {env.get("HTTP_ORIGIN", None)}, Token: {env["HTTP_AUTHORIZATION"]}""")
0176 self.subprocess_env.update(cached_decision["subprocess_env"])
0177 except Exception as e:
0178 self.message = f"Failed to instantiate reqeust object. {str(e)}"
0179 tmp_log.error(
0180 f"""{self.message} - Origin: {env.get("HTTP_ORIGIN", None)}, Token: {env.get("HTTP_AUTHORIZATION", None)}\n{traceback.format_exc()}"""
0181 )
0182
0183
0184 def get_remote_host(self):
0185 if "REMOTE_HOST" in self.subprocess_env:
0186 return self.subprocess_env["REMOTE_HOST"]
0187 return ""
0188
0189
0190 def acceptJson(self):
0191 try:
0192 if "HTTP_ACCEPT" in self.subprocess_env:
0193 return "application/json" in self.subprocess_env["HTTP_ACCEPT"]
0194 except Exception:
0195 pass
0196 return False