diff --git a/bayer/epdb_hooks.py b/bayer/epdb_hooks.py
new file mode 100644
index 00000000..b843782b
--- /dev/null
+++ b/bayer/epdb_hooks.py
@@ -0,0 +1,15 @@
+import logging
+
+from epdb.template_registry import register_template
+
+logger = logging.getLogger(__name__)
+
+register_template(
+ "epdb.actions.collections.compound",
+ "actions/collections/new_pes.html",
+)
+register_template(
+ "modals.collections.compound",
+ "modals/collections/new_pes_modal.html",
+)
+
diff --git a/bayer/templates/actions/collections/new_pes.html b/bayer/templates/actions/collections/new_pes.html
new file mode 100644
index 00000000..bdae544f
--- /dev/null
+++ b/bayer/templates/actions/collections/new_pes.html
@@ -0,0 +1,9 @@
+{% if meta.can_edit %}
+
+{% endif %}
\ No newline at end of file
diff --git a/bayer/templates/modals/collections/new_package_modal.html b/bayer/templates/modals/collections/new_package_modal.html
index 8d7cf5ab..54e03fae 100644
--- a/bayer/templates/modals/collections/new_package_modal.html
+++ b/bayer/templates/modals/collections/new_package_modal.html
@@ -133,10 +133,8 @@
class="select select-bordered w-full"
>
- {% for obj in meta.available_groups %}
- {% if obj.secret %}
-
- {% endif %}
+ {% for obj in meta.secret_groups %}
+
{% endfor %}
diff --git a/bayer/templates/modals/collections/new_pes_modal.html b/bayer/templates/modals/collections/new_pes_modal.html
new file mode 100644
index 00000000..2cf11949
--- /dev/null
+++ b/bayer/templates/modals/collections/new_pes_modal.html
@@ -0,0 +1,173 @@
+{% load static %}
+
+
\ No newline at end of file
diff --git a/bayer/templates/static/login.html b/bayer/templates/static/login.html
new file mode 100644
index 00000000..3f3fdf28
--- /dev/null
+++ b/bayer/templates/static/login.html
@@ -0,0 +1,149 @@
+{% extends "static/login_base.html" %}
+
+{% block title %}enviPath - Sign In{% endblock %}
+
+{% block extra_styles %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
Welcome to the new enviPath!
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_scripts %}
+
+{% endblock %}
diff --git a/bayer/urls.py b/bayer/urls.py
new file mode 100644
index 00000000..ebe5fc0d
--- /dev/null
+++ b/bayer/urls.py
@@ -0,0 +1,8 @@
+from django.urls import re_path
+
+from . import views as v
+
+
+urlpatterns = [
+ re_path(r"^depict_pes$", v.visualize_pes, name="depict_pes"),
+]
diff --git a/bayer/views.py b/bayer/views.py
index 91ea44a2..41657eb1 100644
--- a/bayer/views.py
+++ b/bayer/views.py
@@ -1,3 +1,59 @@
-from django.shortcuts import render
+import requests
+from django.conf import settings as s
+from django.http import HttpResponse
+from pydantic import BaseModel
-# Create your views here.
+from utilities.chem import FormatConverter
+
+
+class PES(BaseModel):
+ pass
+
+
+def fetch_pes(request, pes_url) -> dict:
+ proxies = {
+ "http": "http://10.185.190.100:8080",
+ "https": "http://10.185.190.100:8080",
+ }
+
+ from epauth.views import get_access_token_from_request
+ token = get_access_token_from_request(request)
+
+ if token:
+ for k, v in s.PES_API_MAPPING.items():
+ if pes_url.startsWith(k):
+ pes_id = pes_url.split('/')[-1]
+ headers = {"Authorization": f"Bearer {token['access_token']}"}
+ params = {"pes_reg_entity_corporate_id": pes_id}
+
+ res = requests.get(v, headers=headers, params=params, proxies=proxies)
+
+ try:
+ res.raise_for_status()
+ pes_data = res.json()
+
+ if len(pes_data) == 0:
+ raise ValueError(f"PES with id {pes_id} not found")
+
+ res_data = pes_data[0]
+ res_data["pes_url"] = pes_url
+ return res_data
+
+ except requests.exceptions.HTTPError as e:
+ raise ValueError(f"Error fetching PES with id {pes_id}: {e}")
+ else:
+ raise ValueError(f"Unknown URL {pes_url}")
+ else:
+ raise ValueError("Could not fetch access token from request.")
+
+
+def visualize_pes(request):
+ pes_link = request.GET.get('pesLink')
+
+ if pes_link:
+ pes_data = fetch_pes(request, pes_link)
+ print(pes_data)
+
+ return HttpResponse(
+ FormatConverter.to_png("c1ccccc1"), content_type="image/png"
+ )
diff --git a/epauth/views.py b/epauth/views.py
index b4dbc64b..b419d465 100644
--- a/epauth/views.py
+++ b/epauth/views.py
@@ -1,12 +1,33 @@
import msal
from django.conf import settings as s
+from django.contrib.auth import get_user_model
from django.contrib.auth import login
from django.shortcuts import redirect
-from django.contrib.auth import get_user_model
from epdb.logic import UserManager, GroupManager
from epdb.models import Group
+
+def get_msal_app_with_cache(request):
+ """
+ Create MSAL app with session-based token cache.
+ """
+ cache = msal.SerializableTokenCache()
+
+ # Load cache from session if it exists
+ if request.session.get("msal_token_cache"):
+ cache.deserialize(request.session["msal_token_cache"])
+
+ msal_app = msal.ConfidentialClientApplication(
+ client_id=s.MS_ENTRA_CLIENT_ID,
+ client_credential=s.MS_ENTRA_CLIENT_SECRET,
+ authority=s.MS_ENTRA_AUTHORITY,
+ token_cache=cache
+ )
+
+ return msal_app, cache
+
+
def entra_login(request):
msal_app = msal.ConfidentialClientApplication(
client_id=s.MS_ENTRA_CLIENT_ID,
@@ -23,11 +44,7 @@ def entra_login(request):
def entra_callback(request):
- msal_app = msal.ConfidentialClientApplication(
- client_id=s.MS_ENTRA_CLIENT_ID,
- client_credential=s.MS_ENTRA_CLIENT_SECRET,
- authority=s.MS_ENTRA_AUTHORITY,
- )
+ msal_app, cache = get_msal_app_with_cache(request)
flow = request.session.pop("msal_auth_flow", None)
if not flow:
@@ -35,24 +52,10 @@ def entra_callback(request):
# Acquire token using the flow and callback request
result = msal_app.acquire_token_by_auth_code_flow(flow, request.GET)
- print(result)
- # if "error" in result:
- # {'correlation_id': '626f511b-5230-4d06-9ffd-d89a764082c6',
- # 'error': 'invalid_client',
- # 'error_codes': [7000222],
- # 'error_description': 'AADSTS7000222: The provided client secret keys for app '
- # "'35c75dfb-bd15-493d-b4e9-af847f2df894' are expired. "
- # 'Visit the Azure portal to create new keys for your app: '
- # 'https://aka.ms/NewClientSecret, or consider using '
- # 'certificate credentials for added security: '
- # 'https://aka.ms/certCreds. Trace ID: '
- # '30ba1c58-c949-4432-9ed6-3b6136856700 Correlation ID: '
- # '626f511b-5230-4d06-9ffd-d89a764082c6 Timestamp: '
- # '2026-04-15 08:21:15Z',
- # 'error_uri': 'https://login.microsoftonline.com/error?code=7000222',
- # 'timestamp': '2026-04-15 08:21:15Z',
- # 'trace_id': '30ba1c58-c949-4432-9ed6-3b6136856700'}
- # return redirect("/")
+
+ # Save the token cache to session
+ if cache.has_state_changed:
+ request.session["msal_token_cache"] = cache.serialize()
claims = result["id_token_claims"]
@@ -79,7 +82,8 @@ def entra_callback(request):
# Ensure groups exists in eP
for id, name in s.ENTRA_SECRET_GROUPS.items():
if not Group.objects.filter(uuid=id).exists():
- g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
+ g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
+ uuid=id)
else:
g = Group.objects.get(uuid=id)
# Ensure its secret
@@ -88,7 +92,8 @@ def entra_callback(request):
for id, name in s.ENTRA_GROUPS.items():
if not Group.objects.filter(uuid=id).exists():
- g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
+ g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
+ uuid=id)
else:
g = Group.objects.get(uuid=id)
@@ -100,3 +105,53 @@ def entra_callback(request):
# EDIT END
return redirect(s.SERVER_URL) # Handle errors
+
+
+def get_access_token_from_request(request, scopes=None):
+ """
+ Get an access token from the request using MSAL token cache.
+ """
+ if scopes is None:
+ scopes = s.MS_ENTRA_SCOPES
+
+ # Get user from request (must be authenticated)
+ if not request.user.is_authenticated:
+ return None
+
+ # Create MSAL app with persistent cache
+ msal_app, cache = get_msal_app_with_cache(request)
+
+ # Try to get accounts from cache
+ accounts = msal_app.get_accounts()
+
+ if not accounts:
+ return None
+
+ # Find the account that matches the current user
+ user_account = None
+ for account in accounts:
+ if account.get("local_account_id") == str(request.user.uuid):
+ user_account = account
+ break
+
+ # If no matching account found, use the first available account
+ if not user_account and accounts:
+ user_account = accounts[0]
+
+ if not user_account:
+ return None
+
+ # Try to acquire token silently from cache
+ result = msal_app.acquire_token_silent(
+ scopes=scopes,
+ account=user_account
+ )
+
+ # Save cache changes back to session
+ if cache.has_state_changed:
+ request.session["msal_token_cache"] = cache.serialize()
+
+ if result and "access_token" in result:
+ return result
+
+ return None
diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py
index 079ea3d0..23e1b1f9 100644
--- a/epdb/legacy_api.py
+++ b/epdb/legacy_api.py
@@ -160,8 +160,46 @@ class SimpleModel(SimpleObject):
def login(request, loginusername: Form[str], loginpassword: Form[str]):
from django.contrib.auth import authenticate, login
- email = User.objects.get(username=loginusername).email
- user = authenticate(username=email, password=loginpassword)
+ if request.headers.get("Authorization"):
+ import jwt
+ import requests
+
+ TENANT_ID = s.MS_ENTRA_TENANT_ID
+ CLIENT_ID = s.MS_ENTRA_CLIENT_ID
+
+ def validate_token(token: str) -> dict:
+ # Fetch Microsoft's public keys
+ jwks_uri = f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
+ jwks = requests.get(jwks_uri).json()
+
+ header = jwt.get_unverified_header(token)
+
+ public_key = jwt.algorithms.RSAAlgorithm.from_jwk(
+ next(k for k in jwks["keys"] if k["kid"] == header["kid"])
+ )
+
+ claims = jwt.decode(
+ token,
+ public_key,
+ algorithms=["RS256"],
+ audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
+ issuer=f"https://sts.windows.net/{TENANT_ID}/",
+ )
+ return claims
+
+ token = request.headers.get("Authorization").split(" ")[1]
+
+ claims = validate_token(token)
+
+ if not User.objects.filter(uuid=claims['oid']).exists():
+ user = None
+ else:
+ user = User.objects.get(uuid=claims['oid'])
+
+ else:
+ email = User.objects.get(username=loginusername).email
+ user = authenticate(username=email, password=loginpassword)
+
if user:
login(request, user)
return user
diff --git a/epdb/views.py b/epdb/views.py
index 26d3a4f1..ad643475 100644
--- a/epdb/views.py
+++ b/epdb/views.py
@@ -388,6 +388,9 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
"debug": s.DEBUG,
"external_databases": ExternalDatabase.get_databases(),
"site_id": s.MATOMO_SITE_ID,
+ # EDIT START
+ "secret_groups": Group.objects.filter(secret=True),
+ # EDIT END
},
}
diff --git a/templates/components/navbar.html b/templates/components/navbar.html
index 12f57597..2960c314 100644
--- a/templates/components/navbar.html
+++ b/templates/components/navbar.html
@@ -88,6 +88,12 @@
>Scenario
+
+
+ Group
+