{"files": {"main.ts": "import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n", "core.ts": "import {asciiEq, getGrantAction, getGrantExpires, getGrantRoles, getGrantScope, isDenyGrant, parentMap} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const make = (allowed: boolean, reason: string, matched: number[]): any => {\n    return {allowed: allowed, reason: reason, matched: matched, audit: (!allowed && policy.audit_denials) ? [reason] : []};\n  };\n  const subject: any = (payload && payload.subject) ? payload.subject : {};\n  const resource: any = (payload && payload.resource) ? payload.resource : {};\n  const now: number = payload ? payload.now : 0;\n  const issuedAt: number = payload ? payload.issued_at : 0;\n  if (subject.tenant !== resource.tenant) {\n    return make(false, 'tenant', []);\n  }\n  if (now - issuedAt > policy.token_ttl) {\n    return make(false, 'token', []);\n  }\n  const suspended: boolean = !!(subject.suspended !== undefined ? subject.suspended : subject.is_suspended);\n  if (policy.suspended_denies && suspended) {\n    return make(false, 'suspended', []);\n  }\n  const owner: boolean = !!(subject.owner !== undefined ? subject.owner : (subject.is_owner !== undefined ? subject.is_owner : subject.isOwner));\n  if (policy.owner_bypass && owner) {\n    return make(true, 'owner', []);\n  }\n  const roleSet = new Set<string>();\n  const initialRoles: string[] = [];\n  const subjectRoles: any[] = Array.isArray(subject.roles) ? subject.roles : [];\n  for (const r of subjectRoles) { if (!roleSet.has(r)) { roleSet.add(r); initialRoles.push(r); } }\n  if (policy.group_union) {\n    const groups: any[] = Array.isArray(subject.groups) ? subject.groups : [];\n    for (const g of groups) { if (!roleSet.has(g)) { roleSet.add(g); initialRoles.push(g); } }\n  }\n  if (policy.inherit_roles) {\n    const pmap = parentMap(payload ? payload.role_parents : null);\n    const queue: string[] = initialRoles.slice();\n    let idx = 0;\n    while (idx < queue.length) {\n      const cur = queue[idx];\n      idx++;\n      const parents = pmap.get(cur);\n      if (parents) {\n        for (const p of parents) {\n          if (!roleSet.has(p)) { roleSet.add(p); queue.push(p); }\n        }\n      }\n    }\n  }\n  const requestedAction: string = payload && payload.action !== undefined && payload.action !== null ? String(payload.action) : '';\n  const resourceId: string = resource && resource.id !== undefined && resource.id !== null ? String(resource.id) : '';\n  const grants: any[] = payload && Array.isArray(payload.grants) ? payload.grants : [];\n  const matched: number[] = [];\n  let hasAllow = false;\n  let hasDeny = false;\n  for (let i = 0; i < grants.length; i++) {\n    const grant: any = grants[i] || {};\n    const groles = getGrantRoles(grant);\n    let roleOk = false;\n    for (const gr of groles) { if (roleSet.has(gr)) { roleOk = true; break; } }\n    if (!roleOk) { continue; }\n    const gaction = getGrantAction(grant);\n    let actionOk = false;\n    if (gaction === '*') {\n      actionOk = !!policy.wildcard_action;\n    } else if (policy.action_casefold) {\n      actionOk = asciiEq(gaction, requestedAction);\n    } else {\n      actionOk = gaction === requestedAction;\n    }\n    if (!actionOk) { continue; }\n    const scope = getGrantScope(grant);\n    if (scope === null || scope === undefined) { continue; }\n    let scopeOk = false;\n    if (policy.resource_scope === 'prefix') {\n      scopeOk = resourceId.startsWith(String(scope));\n    } else {\n      scopeOk = String(scope) === resourceId;\n    }\n    if (!scopeOk) { continue; }\n    const expires = getGrantExpires(grant);\n    if (expires !== null && expires !== undefined) {\n      if (policy.expiry_inclusive) {\n        if (!(now <= expires)) { continue; }\n      } else {\n        if (!(now < expires)) { continue; }\n      }\n    }\n    matched.push(i);\n    if (isDenyGrant(grant)) { hasDeny = true; } else { hasAllow = true; }\n  }\n  if (matched.length === 0) {\n    return make(!!policy.default_allow, 'default', []);\n  }\n  let allowed: boolean;\n  if (policy.deny_wins) {\n    allowed = hasAllow && !hasDeny;\n  } else {\n    allowed = hasAllow;\n  }\n  return make(allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\"action_casefold\": true, \"audit_denials\": true, \"default_allow\": true, \"deny_wins\": true, \"expiry_inclusive\": true, \"group_union\": true, \"inherit_roles\": true, \"owner_bypass\": true, \"resource_scope\": \"exact\", \"suspended_denies\": true, \"token_ttl\": 100, \"wildcard_action\": true};\n", "helpers.ts": "export function asciiLower(s: string): string { return String(s).toLowerCase(); }\nexport function asciiEq(a: string, b: string): boolean { return asciiLower(a) === asciiLower(b); }\nexport function getGrantRoles(grant: any): string[] {\n  if (!grant) { return []; }\n  if (typeof grant.role === 'string') { return [grant.role]; }\n  if (Array.isArray(grant.roles)) { return grant.roles.slice(); }\n  if (typeof grant.roles === 'string') { return [grant.roles]; }\n  if (typeof grant.role_name === 'string') { return [grant.role_name]; }\n  return [];\n}\nexport function getGrantAction(grant: any): string {\n  if (!grant) { return ''; }\n  if (grant.action !== undefined && grant.action !== null) { return String(grant.action); }\n  return '';\n}\nexport function getGrantScope(grant: any): any {\n  if (!grant) { return null; }\n  if (grant.scope !== undefined) { return grant.scope; }\n  if (grant.resource !== undefined && typeof grant.resource === 'string') { return grant.resource; }\n  if (grant.resource_id !== undefined) { return grant.resource_id; }\n  if (grant.resourceId !== undefined) { return grant.resourceId; }\n  if (grant.target !== undefined) { return grant.target; }\n  return null;\n}\nexport function getGrantExpires(grant: any): any {\n  if (!grant) { return null; }\n  if (grant.expires !== undefined) { return grant.expires; }\n  if (grant.expires_at !== undefined) { return grant.expires_at; }\n  if (grant.expiry !== undefined) { return grant.expiry; }\n  return null;\n}\nexport function isDenyGrant(grant: any): boolean {\n  if (!grant) { return false; }\n  const fields: any[] = [grant.effect, grant.type, grant.decision, grant.outcome];\n  for (const f of fields) {\n    if (typeof f === 'string') {\n      const v = f.toLowerCase();\n      if (v === 'deny' || v === 'denied') { return true; }\n      if (v === 'allow' || v === 'allowed') { return false; }\n    }\n  }\n  if (typeof grant.allow === 'boolean') { return !grant.allow; }\n  if (typeof grant.allowed === 'boolean') { return !grant.allowed; }\n  if (typeof grant.deny === 'boolean') { return grant.deny; }\n  if (typeof grant.is_deny === 'boolean') { return grant.is_deny; }\n  if (typeof grant.denied === 'boolean') { return grant.denied; }\n  return false;\n}\nexport function parentMap(roleParents: any): Map<string, string[]> {\n  const map = new Map<string, string[]>();\n  const add = (k: string, v: string): void => {\n    const existing = map.get(k);\n    if (existing) { if (!existing.includes(v)) { existing.push(v); } } else { map.set(k, [v]); }\n  };\n  if (!roleParents) { return map; }\n  if (Array.isArray(roleParents)) {\n    for (const entry of roleParents) {\n      if (Array.isArray(entry) && entry.length >= 2) { add(String(entry[0]), String(entry[1])); }\n      else if (entry && typeof entry === 'object') {\n        const child = entry.role !== undefined ? entry.role : (entry.child !== undefined ? entry.child : entry.from);\n        const parents = entry.parents !== undefined ? entry.parents : (entry.parent !== undefined ? entry.parent : (entry.inherits !== undefined ? entry.inherits : entry.to));\n        if (child !== undefined && parents !== undefined) {\n          if (Array.isArray(parents)) { for (const p of parents) { add(String(child), String(p)); } } else { add(String(child), String(parents)); }\n        }\n      }\n    }\n    return map;\n  }\n  if (typeof roleParents === 'object') {\n    for (const key of Object.keys(roleParents)) {\n      const val = roleParents[key];\n      if (Array.isArray(val)) { for (const p of val) { add(key, String(p)); } }\n      else if (val !== null && val !== undefined) { add(key, String(val)); }\n    }\n  }\n  return map;\n}\n"}}