{"version":3,"file":"ProjectMembershipResolver.cjs","sources":["../../../../../packages/engine-http/src/content/ProjectMembershipResolver.ts"],"sourcesContent":["import { MembershipMatcher } from '@contember/engine-tenant-api'\nimport * as Typesafe from '@contember/typesafe'\nimport { Acl } from '@contember/schema'\nimport { HttpErrorResponse } from '../common'\nimport { ProjectMembershipFetcher } from './ProjectMembershipFetcher'\nimport { MembershipResolver, ParsedMembership } from '@contember/schema-utils'\n\nconst assumeMembershipHeader = 'x-contember-assume-membership'\n\ninterface HttpRequest {\n\tbody?: unknown\n\tget(header: string): string\n}\n\nexport class ProjectMembershipResolver {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly projectMembershipFetcher: ProjectMembershipFetcher,\n\t) {\n\t}\n\n\tpublic async resolveMemberships({ request, acl, projectSlug, identity }: {\n\t\tacl: Acl.Schema\n\t\trequest: HttpRequest\n\t\tprojectSlug: string\n\t\tidentity: { identityId: string; personId?: string; roles?: readonly string[] }\n\t}): Promise<{ effective: readonly ParsedMembership[]; fetched: readonly Acl.Membership[] }> {\n\n\t\tconst explicitMemberships = await this.projectMembershipFetcher.fetchMemberships(projectSlug, {\n\t\t\tid: identity.identityId,\n\t\t\troles: identity.roles,\n\t\t})\n\n\t\tconst implicitRoles = Object.entries(acl.roles).filter(([, role]) => role.implicit).map(([name]) => name)\n\n\t\tconst throwNotAllowed = () => {\n\t\t\tconst errorMessage = this.debug\n\t\t\t\t? `You are not allowed to access project ${projectSlug}`\n\t\t\t\t: `Project ${projectSlug} NOT found`\n\t\t\tthrow new HttpErrorResponse(404, errorMessage)\n\t\t}\n\n\t\tif (explicitMemberships.length === 0 && implicitRoles.length === 0) {\n\t\t\tthrowNotAllowed()\n\t\t}\n\n\t\tconst membershipResolver = new MembershipResolver()\n\n\t\tconst assumedMemberships = this.readAssumedMemberships(request)\n\t\tif (assumedMemberships !== null) {\n\n\t\t\tif (assumedMemberships.length === 0) {\n\t\t\t\tthrowNotAllowed()\n\t\t\t}\n\n\t\t\tconst parsedMemberships = membershipResolver.resolve(acl, assumedMemberships, identity)\n\t\t\tif (parsedMemberships.errors.length > 0) {\n\t\t\t\tthrow new HttpErrorResponse(\n\t\t\t\t\t400,\n\t\t\t\t\t`Invalid memberships in ${assumeMembershipHeader} header:\\n` +\n\t\t\t\t\tparsedMemberships.errors.map(it => JSON.stringify(it)).join('\\n'),\n\t\t\t\t)\n\t\t\t}\n\t\t\tthis.verifyAssumedRoles(explicitMemberships, acl, assumedMemberships)\n\n\t\t\treturn { effective: parsedMemberships.memberships, fetched: explicitMemberships }\n\t\t}\n\n\n\t\tconst explicitProjectRoles = explicitMemberships.map(it => it.role)\n\t\tconst implicitRolesToAssign = implicitRoles.filter(it => !explicitProjectRoles.includes(it))\n\n\t\treturn {\n\t\t\teffective: [\n\t\t\t\t// intentionally ignoring validation errors of stored memberships\n\t\t\t\t...membershipResolver.resolve(acl, explicitMemberships, identity).memberships,\n\t\t\t\t...implicitRolesToAssign.map(it => ({ role: it, variables: [] })),\n\t\t\t],\n\t\t\tfetched: explicitMemberships,\n\t\t}\n\t}\n\n\tprivate readAssumedMemberships(req: HttpRequest): null | readonly Acl.Membership[] {\n\t\tconst value = this.readAssumeMembershipJson(req)\n\t\tif (value === null) {\n\t\t\treturn null\n\t\t}\n\t\ttry {\n\t\t\treturn assumeMembershipValueType(value).memberships\n\t\t} catch (e: any) {\n\t\t\tthrow new HttpErrorResponse(400, `Invalid format of \"assume membership\": ${e.message}`)\n\t\t}\n\t}\n\n\tprivate readAssumeMembershipJson(req: HttpRequest): unknown {\n\t\tif (typeof req.body === 'object' && req.body !== null && 'assumeMembership' in req.body) {\n\t\t\treturn (req.body as any).assumeMembership\n\t\t}\n\t\tconst value = req.get(assumeMembershipHeader).trim()\n\t\treturn value !== '' ? JSON.parse(value) : null\n\t}\n\n\tprivate verifyAssumedRoles(explicitMemberships: readonly Acl.Membership[], acl: Acl.Schema, assumedMemberships: readonly Acl.Membership[]) {\n\t\tconst membershipMatcher = new MembershipMatcher(explicitMemberships.map(it => ({\n\t\t\t...it,\n\t\t\tmatchRule: acl.roles[it.role].content?.assumeMembership ?? {},\n\t\t})))\n\n\t\tfor (const assumed of assumedMemberships) {\n\t\t\tif (!membershipMatcher.matches(assumed)) {\n\t\t\t\tthrow new HttpErrorResponse(403, `You are not allow to assume membership ${JSON.stringify(assumed)}`)\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst assumeMembershipValueType = Typesafe.object({\n\tmemberships: Typesafe.array(\n\t\tTypesafe.object({\n\t\t\trole: Typesafe.string,\n\t\t\tvariables: Typesafe.array(\n\t\t\t\tTypesafe.object({\n\t\t\t\t\tname: Typesafe.string,\n\t\t\t\t\tvalues: Typesafe.array(Typesafe.string),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t),\n})\n"],"names":["HttpErrorResponse","MembershipResolver","MembershipMatcher","Typesafe"],"mappings":"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"}