PES Adjustments, Check is User is in enviPath Entra Group
Some checks failed
CI / test (pull_request) Failing after 28s
API CI / api-tests (pull_request) Failing after 42s

This commit is contained in:
Tim Lorsbach
2026-08-20 08:59:24 +02:00
parent 0033513d99
commit 67aa3731cb
4 changed files with 96 additions and 40 deletions

View File

@ -85,6 +85,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libxext6 \ libxext6 \
libfontconfig1 \ libfontconfig1 \
nano \ nano \
openjdk-21-jre-headless \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN useradd -ms /bin/bash django RUN useradd -ms /bin/bash django

View File

@ -8,7 +8,7 @@ from django.shortcuts import redirect
from bayer.models import PESCompound from bayer.models import PESCompound
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import Pathway, Node from epdb.models import Pathway, Node, Group
from epdb.views import _anonymous_or_real, error from epdb.views import _anonymous_or_real, error
from utilities.decorators import package_permission_required from utilities.decorators import package_permission_required
@ -18,6 +18,23 @@ Package = s.GET_PACKAGE_MODEL()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def has_secret_group(user):
"""
Determines if the specified user belongs to any secret group.
This function checks whether the given user is a member of any group
that is marked as secret.
Args:
user: The user for whom the check is performed.
Returns:
bool: True if the user belongs to at least one secret group,
False otherwise.
"""
return Group.objects.filter(secret=True, user_member=user).exists()
@package_permission_required() @package_permission_required()
def create_pes(request, package_uuid): def create_pes(request, package_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -38,7 +55,7 @@ def create_pes(request, package_uuid):
if pes_link: if pes_link:
try: try:
pes_data = fetch_pes(request, pes_link) pes_data = fetch_pes(request, pes_link, current_user)
except ValueError as e: except ValueError as e:
return error( return error(
request, request,
@ -98,7 +115,7 @@ def create_pes_node(request, package_uuid, pathway_uuid):
if pes_link: if pes_link:
try: try:
pes_data = fetch_pes(request, pes_link) pes_data = fetch_pes(request, pes_link, current_user)
except ValueError as e: except ValueError as e:
return error( return error(
request, request,
@ -157,16 +174,33 @@ def create_pes_node(request, package_uuid, pathway_uuid):
return HttpResponseNotAllowed(["POST"]) return HttpResponseNotAllowed(["POST"])
def fetch_pes(request, pes_url) -> dict: def get_application_token(prod: bool) -> str:
from epauth.views import get_access_token_from_request scope = f"{s.PROD_PES_SCOPE if prod else s.NON_PROD_PES_SCOPE}/.default"
token = get_access_token_from_request(request)
if token is None: url = f"https://login.microsoftonline.com/{s.MS_TENANT_ID}/oauth2/v2.0/token"
token = pes_url.split('/')[-1] == 'dummy' data = {
"grant_type": "client_credentials",
"client_id": s.MS_ENTRA_CLIENT_ID,
"client_secret": s.MS_ENTRA_CLIENT_SECRET,
"scope": scope,
}
try:
response = requests.post(url, data=data)
response.raise_for_status()
return response.json()["access_token"]
except requests.exceptions.HTTPError as e:
logger.error(f"Could not fetch application token: {e}")
raise ValueError(f"Could not fetch application token!")
def fetch_pes(request, pes_url, user) -> dict:
if token:
for k, v in s.PES_API_MAPPING.items(): for k, v in s.PES_API_MAPPING.items():
if pes_url.startswith(k): if pes_url.startswith(k):
prod = "cropkey-np" not in pes_url
pes_id = pes_url.split('/')[-1] pes_id = pes_url.split('/')[-1]
if pes_id == 'dummy': if pes_id == 'dummy':
@ -175,7 +209,16 @@ def fetch_pes(request, pes_url) -> dict:
res_data["pes_url"] = pes_url res_data["pes_url"] = pes_url
return res_data return res_data
else: else:
headers = {"Authorization": f"Bearer {token['access_token']}"} headers = {
"accept": "*/*",
"authorization": "Bearer " + get_application_token(prod),
}
# Restrict request if user is not part of any secret group
if not has_secret_group(user):
headers["app-classification-level-restriction"] = "restrict-pes-secret-structure-access"
params = {"pes_reg_entity_corporate_id": pes_id} params = {"pes_reg_entity_corporate_id": pes_id}
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None) res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
@ -184,6 +227,11 @@ def fetch_pes(request, pes_url) -> dict:
res.raise_for_status() res.raise_for_status()
pes_data = res.json() pes_data = res.json()
# Handle missing response
if "detail" in pes_data and "The following PES Reg Entities Corporate Ids could not be found" in pes_data["detail"]:
raise ValueError(f"PES with id {pes_id} not found")
# Ensure we have a entity
if len(pes_data) == 0: if len(pes_data) == 0:
raise ValueError(f"PES with id {pes_id} not found") raise ValueError(f"PES with id {pes_id} not found")
@ -193,17 +241,15 @@ def fetch_pes(request, pes_url) -> dict:
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
raise ValueError(f"Error fetching PES with id {pes_id}: {e}") raise ValueError(f"Error fetching PES with id {pes_id}: {e}")
else:
raise ValueError(f"Unknown URL {pes_url}") raise ValueError(f"Unknown URL {pes_url}")
else:
raise ValueError("Could not fetch access token from request.")
def visualize_pes(request): def visualize_pes(request):
pes_link = request.GET.get('pesLink') pes_link = request.GET.get('pesLink')
if pes_link: if pes_link:
pes_data = fetch_pes(request, pes_link) pes_data = fetch_pes(request, pes_link, request.user)
representations = pes_data.get('representations') representations = pes_data.get('representations')

View File

@ -9,7 +9,7 @@ from django.shortcuts import redirect
from epdb.logic import UserManager, GroupManager from epdb.logic import UserManager, GroupManager
from epdb.models import Group from epdb.models import Group
from epdb.views import get_remote_address from epdb.views import get_remote_address, error
auth_log = logging.getLogger("auth") auth_log = logging.getLogger("auth")
@ -72,6 +72,15 @@ def entra_callback(request):
claims = result["id_token_claims"] claims = result["id_token_claims"]
if claims.get("roles") is None or claims.get("roles") == [] or "envipath_registered_user" not in claims.get("roles"):
auth_log.error(f"Login attempt by {get_remote_address(request)} failed due to missing role")
return error(
request,
"Login Failed",
"The user is not authenticated. A reason for this might be a missing assignment to the respective enviPath group.",
403,
)
user_name = claims.get("name") user_name = claims.get("name")
# preferred_username is a fallback for 2nd CWID # preferred_username is a fallback for 2nd CWID
user_email = claims.get("emailaddress", claims.get("email", claims.get("preferred_username"))) user_email = claims.get("emailaddress", claims.get("email", claims.get("preferred_username")))

View File

@ -889,7 +889,7 @@ def create_package_compound(
from bayer.models import PESCompound from bayer.models import PESCompound
try: try:
pes_data = fetch_pes(request, c.pesLink) pes_data = fetch_pes(request, c.pesLink, request.user)
except ValueError as e: except ValueError as e:
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"} return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
@ -2014,7 +2014,7 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
from bayer.models import PESCompound from bayer.models import PESCompound
try: try:
pes_data = fetch_pes(request, n.pesLink) pes_data = fetch_pes(request, n.pesLink, request.user)
except ValueError as e: except ValueError as e:
return 400, {"message": f"Could not fetch PES data for {n.pesLink}"} return 400, {"message": f"Could not fetch PES data for {n.pesLink}"}