diff --git a/content b/content index 44f1bc7..5d5a17a 160000 --- a/content +++ b/content @@ -1 +1 @@ -Subproject commit 44f1bc75bb646bf15914671c300b704790a4e27a +Subproject commit 5d5a17a613c27f0043ef0ef18148f2a2ed47e246 diff --git a/src/utils/evaluateRules.js b/src/utils/evaluateRules.js index 5dd8068..98231f4 100644 --- a/src/utils/evaluateRules.js +++ b/src/utils/evaluateRules.js @@ -1,4 +1,15 @@ // src/utils/evaluateRules.js + +/** + * Validates a single rule string against identity and groups. + */ +function checkRule(rule, identity, groups) { + const [type, value] = rule.split(":"); + if (type === "group") return groups.includes(value); + if (type === "user") return identity === value; + return false; +} + /** * Evaluates access rules against the current identity context. * * Rules: @@ -10,20 +21,12 @@ function evaluateRules(rules, session) { if (!rules || !rules.length) return true; - const { user, groups = [] } = session; + // Map identity from standard OIDC claims (preferred_username/name) + const identity = session.user || session.preferred_username || session.name; + const groups = session.groups || []; return rules.some((requirement) => - requirement.every((rule) => { - const [type, value] = rule.split(":"); - switch (type) { - case "group": - return groups.includes(value); - case "user": - return user === value; - default: - return false; - } - }), + requirement.every((rule) => checkRule(rule, identity, groups)), ); } module.exports = { evaluateRules }; diff --git a/src/utils/processMenuLinks.js b/src/utils/processMenuLinks.js index 63482b3..7d3c739 100644 --- a/src/utils/processMenuLinks.js +++ b/src/utils/processMenuLinks.js @@ -7,6 +7,8 @@ */ function promoteAttributes(parent, child) { const promote = child.promote; + if (!promote) return; + let keys = []; if (promote === "true" || promote === true) { @@ -21,6 +23,49 @@ parent[key] = child[key]; }); } +function normalizePath(item, currentPath) { + if (item.appendCurrentPath && typeof item.href === "string") { + const shouldAppend = + currentPath !== "/" && !item.href.endsWith(currentPath); + if (shouldAppend) item.href += currentPath; + } + + if (item.html || item.frame || item.mermaid) { + item.href = `/docs/hexa/${item.html || item.frame || item.mermaid}`; + } + return item; +} + +function handleSubmenuLogic(item, session, currentPath, activePolicy) { + const nextPolicy = activePolicy === "deny-children" ? "deny" : activePolicy; + const processedSub = processMenuLinks( + item.submenu, + session, + currentPath, + nextPolicy, + ); + + if (processedSub.length === 0) { + delete item.submenu; + return item; + } + + const primaryChild = item.submenu.find((s) => s.promote); + const isPrimaryVisible = processedSub.some( + (s) => s.label === primaryChild?.label, + ); + + if (isPrimaryVisible && primaryChild?.promote) { + promoteAttributes(item, primaryChild); + // Remove the redundant link from dropdown if it was promoted to the parent + item.submenu = processedSub.filter((s) => s.label !== primaryChild.label); + } else { + item.submenu = processedSub; + } + + if (item.submenu.length === 0) delete item.submenu; + return item; +} /** * Processes menu links with policy inheritance and parent-override logic. @@ -29,94 +74,34 @@ * @param {string} currentPath - The active URL path. * @param {string} inheritedPolicy - The policy passed down from the parent (default "allow"). */ + function processMenuLinks( links, session, currentPath, inheritedPolicy = "allow", ) { - if (!links) return []; + return (links || []).reduce((acc, link) => { + let item = { ...link }; + const activePolicy = item.policy || inheritedPolicy; - return links - .map((link) => { - const item = { ...link }; - const activePolicy = item.policy || inheritedPolicy; - // winstonLogger.info( - // JSON.stringify({ Label: item.label, Policy: activePolicy, session }), - // ); + if (item.submenu) { + item = handleSubmenuLogic(item, session, currentPath, activePolicy); + } - if (item.submenu) { - const nextPolicy = - activePolicy === "deny-children" ? "deny" : activePolicy; - const processedSub = processMenuLinks( - item.submenu, - session, - currentPath, - nextPolicy, - ); + item = normalizePath(item, currentPath); - const primaryChild = item.submenu[0]; - const isPrimaryVisible = processedSub.some( - (s) => s.label === primaryChild.label, - ); + const finalPolicy = item.policy || inheritedPolicy; + const isVisible = + finalPolicy === "allow" || + finalPolicy === "deny-children" || + (finalPolicy === "deny" && + session.isAuthenticated && + evaluateRules(item.rules, session)); - // Promotion Logic: Trigger if only the primary child remains or the menu is empty - if ( - (processedSub.length === 0 || - (processedSub.length === 1 && isPrimaryVisible)) && - primaryChild?.promote - ) { - const childPolicy = primaryChild.policy || "allow"; // Requirement: Default to allow - - const isChildAccessible = - childPolicy === "allow" || - (childPolicy === "deny" && - session.isAuthenticated && - evaluateRules(primaryChild.rules, session)); - - if (isChildAccessible) { - promoteAttributes(item, primaryChild); - - // Policy inheritance: Apply child policy only if the child has its own children - if (primaryChild.submenu && primaryChild.submenu.length > 0) { - item.policy = childPolicy; - item.submenu = processMenuLinks( - primaryChild.submenu, - session, - currentPath, - item.policy, - ); - } else { - item.policy = childPolicy; - delete item.submenu; - } - } - } else { - // Standard Dropdown: Filter out any item with the 'promote' key from the list - item.submenu = processedSub.filter((s) => !s.promote); - if (item.submenu.length === 0) delete item.submenu; - } - } - - // Path/Resource Normalization - if (item.appendCurrentPath && typeof item.href === "string") { - if (currentPath !== "/" && !item.href.endsWith(currentPath)) - item.href += currentPath; - } else if (item.html || item.frame || item.mermaid) { - item.href = `/docs/hexa/${item.html || item.frame || item.mermaid}`; - } - - return item; - }) - .filter((item) => { - // Final Security Gate (Parent Wins) - const policy = item.policy || inheritedPolicy; - if (policy === "allow" || policy === "deny-children") return true; - if (policy === "deny") { - console.log(session); - return session.isAuthenticated && evaluateRules(item.rules, session); - } - return false; - }); + if (isVisible) acc.push(item); + return acc; + }, []); } + module.exports = processMenuLinks;