diff --git a/.github/workflows/pullRequestsCommandCypress.yml b/.github/workflows/pullRequestsCommandCypress.yml index f16ac3cc457..712e3e661d0 100644 --- a/.github/workflows/pullRequestsCommandCypress.yml +++ b/.github/workflows/pullRequestsCommandCypress.yml @@ -3,9 +3,9 @@ # and run "github-actions-wac build" (or "ghawac build") to regenerate this file. # For more information, run "github-actions-wac --help". name: Pull Requests Command - Cypress -'on': issue_comment +"on": issue_comment env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" AWS_REGION: eu-central-1 jobs: checkComment: @@ -21,9 +21,9 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} command: cypress - reaction: 'true' + reaction: "true" reaction-type: eyes - allow-edits: 'false' + allow-edits: "false" permission-level: write - name: Create comment uses: peter-evans/create-or-update-comment@v2 @@ -35,7 +35,7 @@ jobs: github.run_id }})). :sparkles: runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false validateWorkflows: name: Validate workflows @@ -51,7 +51,7 @@ jobs: needs: checkComment runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false baseBranch: needs: checkComment @@ -72,7 +72,7 @@ jobs: baseRefName -q .baseRefName)" >> $GITHUB_OUTPUT runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false constants: needs: baseBranch @@ -97,7 +97,7 @@ jobs: vars.RANDOM_CACHE_KEY_SUFFIX }}" >> $GITHUB_OUTPUT runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false build: name: Build @@ -136,7 +136,7 @@ jobs: path: ${{ needs.baseBranch.outputs.base-branch }}/.webiny/cached-packages key: ${{ needs.constants.outputs.run-cache-key }} env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false e2e-wby-cms-ddb-constants: needs: @@ -172,7 +172,7 @@ jobs: github.run_id }}_ddb" >> $GITHUB_OUTPUT runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false e2e-wby-cms-ddb-project-setup: needs: @@ -184,7 +184,7 @@ jobs: cypress-config: ${{ steps.save-cypress-config.outputs.cypress-config }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} @@ -284,23 +284,26 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY - - name: Create Cypress config + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project + working-directory: ${{ needs.baseBranch.outputs.base-branch }} - name: Save Cypress config id: save-cypress-config - working-directory: ${{ needs.baseBranch.outputs.base-branch }} run: >- echo "cypress-config=$(cat cypress-tests/cypress.config.ts | tr -d '\t\n\r')" >> $GITHUB_OUTPUT - - name: Cypress - run installation wizard test working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Cypress - run installation wizard test run: >- yarn cy:run --browser chrome --spec "cypress/e2e/adminInstallation/**/*.cy.js" + working-directory: ${{ needs.baseBranch.outputs.base-branch }} runs-on: ubuntu-latest permissions: id-token: write @@ -325,7 +328,7 @@ jobs: }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} @@ -401,7 +404,7 @@ jobs: github.run_id }}_ddb-es" >> $GITHUB_OUTPUT runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false e2e-wby-cms-ddb-es-project-setup: needs: @@ -413,7 +416,7 @@ jobs: cypress-config: ${{ steps.save-cypress-config.outputs.cypress-config }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} @@ -516,23 +519,26 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY - - name: Create Cypress config + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project + working-directory: ${{ needs.baseBranch.outputs.base-branch }} - name: Save Cypress config id: save-cypress-config - working-directory: ${{ needs.baseBranch.outputs.base-branch }} run: >- echo "cypress-config=$(cat cypress-tests/cypress.config.ts | tr -d '\t\n\r')" >> $GITHUB_OUTPUT - - name: Cypress - run installation wizard test working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Cypress - run installation wizard test run: >- yarn cy:run --browser chrome --spec "cypress/e2e/adminInstallation/**/*.cy.js" + working-directory: ${{ needs.baseBranch.outputs.base-branch }} runs-on: ubuntu-latest permissions: id-token: write @@ -558,7 +564,7 @@ jobs: }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} @@ -638,7 +644,7 @@ jobs: github.run_id }}_ddb-os" >> $GITHUB_OUTPUT runs-on: ubuntu-latest env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false e2e-wby-cms-ddb-os-project-setup: needs: @@ -650,7 +656,7 @@ jobs: cypress-config: ${{ steps.save-cypress-config.outputs.cypress-config }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} @@ -753,23 +759,26 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY - - name: Create Cypress config + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project + working-directory: ${{ needs.baseBranch.outputs.base-branch }} - name: Save Cypress config id: save-cypress-config - working-directory: ${{ needs.baseBranch.outputs.base-branch }} run: >- echo "cypress-config=$(cat cypress-tests/cypress.config.ts | tr -d '\t\n\r')" >> $GITHUB_OUTPUT - - name: Cypress - run installation wizard test working-directory: ${{ needs.baseBranch.outputs.base-branch }} + - name: Cypress - run installation wizard test run: >- yarn cy:run --browser chrome --spec "cypress/e2e/adminInstallation/**/*.cy.js" + working-directory: ${{ needs.baseBranch.outputs.base-branch }} runs-on: ubuntu-latest permissions: id-token: write @@ -795,7 +804,7 @@ jobs: }} environment: next env: - NODE_OPTIONS: '--max_old_space_size=4096' + NODE_OPTIONS: "--max_old_space_size=4096" YARN_ENABLE_IMMUTABLE_INSTALLS: false CYPRESS_MAILOSAUR_API_KEY: ${{ secrets.CYPRESS_MAILOSAUR_API_KEY }} PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }} diff --git a/.github/workflows/pushDev.yml b/.github/workflows/pushDev.yml index be0906c0e82..69080233898 100644 --- a/.github/workflows/pushDev.yml +++ b/.github/workflows/pushDev.yml @@ -588,9 +588,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: dev - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: dev @@ -798,9 +801,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: dev - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: dev @@ -1013,9 +1019,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: dev - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: dev diff --git a/.github/workflows/pushNext.yml b/.github/workflows/pushNext.yml index ca036b0bd1f..ad9cf44efbc 100644 --- a/.github/workflows/pushNext.yml +++ b/.github/workflows/pushNext.yml @@ -588,9 +588,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: next - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: next @@ -798,9 +801,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: next - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: next @@ -1013,9 +1019,12 @@ jobs: - name: Deploy Website working-directory: new-webiny-project run: yarn webiny deploy apps/website --env dev - - name: Instance Info - working-directory: new-webiny-project - run: yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY + - name: Deployment Summary + run: >- + node + .github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js + '../new-webiny-project' >> $GITHUB_STEP_SUMMARY + working-directory: next - name: Create Cypress config run: yarn setup-cypress --projectFolder ../new-webiny-project working-directory: next diff --git a/.github/workflows/wac/pullRequestsCommandCypress.wac.ts b/.github/workflows/wac/pullRequestsCommandCypress.wac.ts index 4cdc9a649d4..7642b36bd6b 100644 --- a/.github/workflows/wac/pullRequestsCommandCypress.wac.ts +++ b/.github/workflows/wac/pullRequestsCommandCypress.wac.ts @@ -5,9 +5,16 @@ import { createYarnCacheSteps, createInstallBuildSteps, createGlobalBuildCacheSteps, - createRunBuildCacheSteps + createRunBuildCacheSteps, + withCommonParams } from "./steps"; -import { NODE_OPTIONS, NODE_VERSION, BUILD_PACKAGES_RUNNER, AWS_REGION } from "./utils"; +import { + NODE_OPTIONS, + NODE_VERSION, + BUILD_PACKAGES_RUNNER, + AWS_REGION, + runNodeScript +} from "./utils"; import { createJob, createValidateWorkflowsJob } from "./jobs"; // Will print "next" or "dev". Important for caching (via actions/cache). @@ -147,22 +154,33 @@ const createCypressJobs = (dbSetup: string) => { } }, ...createDeployWebinySteps({ workingDirectory: DIR_TEST_PROJECT }), - { - name: "Create Cypress config", - "working-directory": DIR_WEBINY_JS, - run: `yarn setup-cypress --projectFolder ../${DIR_TEST_PROJECT}` - }, - { - name: "Save Cypress config", - id: "save-cypress-config", - "working-directory": DIR_WEBINY_JS, - run: "echo \"cypress-config=$(cat cypress-tests/cypress.config.ts | tr -d '\\t\\n\\r')\" >> $GITHUB_OUTPUT" - }, - { - name: "Cypress - run installation wizard test", - "working-directory": DIR_WEBINY_JS, - run: 'yarn cy:run --browser chrome --spec "cypress/e2e/adminInstallation/**/*.cy.js"' - } + ...withCommonParams( + [ + { + name: "Deployment Summary", + run: `${runNodeScript( + "printDeploymentSummary", + `../${DIR_TEST_PROJECT}` + )} >> $GITHUB_STEP_SUMMARY` + }, + { + name: "Create Cypress config", + run: `yarn setup-cypress --projectFolder ../${DIR_TEST_PROJECT}` + }, + { + name: "Save Cypress config", + id: "save-cypress-config", + run: "echo \"cypress-config=$(cat cypress-tests/cypress.config.ts | tr -d '\\t\\n\\r')\" >> $GITHUB_OUTPUT" + }, + { + name: "Cypress - run installation wizard test", + run: 'yarn cy:run --browser chrome --spec "cypress/e2e/adminInstallation/**/*.cy.js"' + } + ], + { + "working-directory": DIR_WEBINY_JS + } + ) ] }); diff --git a/.github/workflows/wac/push.wac.ts b/.github/workflows/wac/push.wac.ts index 504758d830f..b90b33d6ce5 100644 --- a/.github/workflows/wac/push.wac.ts +++ b/.github/workflows/wac/push.wac.ts @@ -3,7 +3,8 @@ import { AWS_REGION, BUILD_PACKAGES_RUNNER, listPackagesWithJestTests, - NODE_VERSION + NODE_VERSION, + runNodeScript } from "./utils"; import { createJob } from "./jobs"; import { @@ -12,14 +13,10 @@ import { createInstallBuildSteps, createRunBuildCacheSteps, createSetupVerdaccioSteps, - createYarnCacheSteps + createYarnCacheSteps, + withCommonParams } from "./steps"; -const withCommonParams = ( - steps: NonNullable, - commonParams: Record -) => steps.map(step => ({ ...step, ...commonParams })); - const createPushWorkflow = (branchName: string) => { const ucFirstBranchName = branchName.charAt(0).toUpperCase() + branchName.slice(1); @@ -157,6 +154,13 @@ const createPushWorkflow = (branchName: string) => { ...createDeployWebinySteps({ workingDirectory: DIR_TEST_PROJECT }), ...withCommonParams( [ + { + name: "Deployment Summary", + run: `${runNodeScript( + "printDeploymentSummary", + `../${DIR_TEST_PROJECT}` + )} >> $GITHUB_STEP_SUMMARY` + }, { name: "Create Cypress config", run: `yarn setup-cypress --projectFolder ../${DIR_TEST_PROJECT}` diff --git a/.github/workflows/wac/steps/createDeployWebinySteps.ts b/.github/workflows/wac/steps/createDeployWebinySteps.ts index baf83f29b24..208fd429e76 100644 --- a/.github/workflows/wac/steps/createDeployWebinySteps.ts +++ b/.github/workflows/wac/steps/createDeployWebinySteps.ts @@ -19,11 +19,6 @@ export const createDeployWebinySteps = ({ workingDirectory = "dev" } = {}) => { name: "Deploy Website", "working-directory": workingDirectory, run: "yarn webiny deploy apps/website --env dev" - }, - { - name: "Instance Info", - "working-directory": workingDirectory, - run: "yarn webiny info --env dev >> $GITHUB_STEP_SUMMARY" } ]; }; diff --git a/.github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js b/.github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js new file mode 100644 index 00000000000..c28ef511e3f --- /dev/null +++ b/.github/workflows/wac/utils/runNodeScripts/printDeploymentSummary.js @@ -0,0 +1,24 @@ +// This script can only be run if we previously checked out the project and installed all dependencies. +const { getStackOutput } = require("@webiny/cli-plugin-deploy-pulumi/utils"); + +const args = process.argv.slice(2); // Removes the first two elements +const [cwd] = args; + +const adminStackOutput = getStackOutput({ + folder: "apps/admin", + env: "dev", + cwd +}); + +const websiteStackOutput = getStackOutput({ + folder: "apps/website", + env: "dev", + cwd +}); + +console.log(`### Deployment Summary +| App | URL | +|-|----| +| Admin Area | [${adminStackOutput.appUrl}](${adminStackOutput.appUrl}) | +| Website | [${websiteStackOutput.appUrl}](${websiteStackOutput.appUrl}) | +`); diff --git a/packages/api-security-cognito/src/createCognito.ts b/packages/api-security-cognito/src/createCognito.ts index bdebf90019d..952f75e3aec 100644 --- a/packages/api-security-cognito/src/createCognito.ts +++ b/packages/api-security-cognito/src/createCognito.ts @@ -5,7 +5,7 @@ import { Config as CognitoConfig, TokenData } from "@webiny/api-cognito-authenticator"; -import { createGroupAuthorizer } from "~/createGroupAuthorizer"; +import { createGroupsTeamsAuthorizerHandler } from "@webiny/api-security"; import { CoreContext } from "~/types"; import { createAdminUsersHooks } from "./createAdminUsersHooks"; import adminUsersGqlPlugins from "./graphql/user.gql"; @@ -24,7 +24,9 @@ interface GetPermissionsParams { interface Config extends CognitoConfig { identityType: string; + getIdentity?(params: GetIdentityParams): TIdentity; + getPermissions?(params: GetPermissionsParams): Promise; } @@ -33,9 +35,26 @@ export interface CognitoTokenData extends TokenData { family_name: string; email: string; "custom:id": string; + [key: string]: any; } +const mustAddGroupsTeamsAuthorizer = (identity: SecurityIdentity) => { + if ("group" in identity) { + return true; + } + + if ("groups" in identity) { + return true; + } + + if ("team" in identity) { + return true; + } + + return "teams" in identity; +}; + export const createCognito = < TContext extends CoreContext = CoreContext, TToken extends CognitoTokenData = CognitoTokenData, @@ -77,10 +96,9 @@ export const createCognito = < context }); - if (customIdentity.group) { - context.security.addAuthorizer( - createGroupAuthorizer(context, customIdentity.group) - ); + if (mustAddGroupsTeamsAuthorizer(customIdentity)) { + const authorizer = createGroupsTeamsAuthorizerHandler(config, context); + context.security.addAuthorizer(authorizer); } return customIdentity; diff --git a/packages/api-security-cognito/src/createGroupAuthorizer.ts b/packages/api-security-cognito/src/createGroupAuthorizer.ts deleted file mode 100644 index 7e6d1e8d9ce..00000000000 --- a/packages/api-security-cognito/src/createGroupAuthorizer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getPermissionsFromSecurityGroupsForLocale } from "@webiny/api-security"; -import { CoreContext } from "~/types"; - -export const createGroupAuthorizer = (context: CoreContext, groupSlug: string) => { - return async () => { - return context.security.withoutAuthorization(async () => { - const tenant = context.tenancy.getCurrentTenant(); - const locale = context.i18n?.getContentLocale(); - - if (!locale) { - return null; - } - - const group = await context.security.getGroup({ - where: { slug: groupSlug, tenant: tenant.id } - }); - - if (group) { - return getPermissionsFromSecurityGroupsForLocale([group], locale.code); - } - - return null; - }); - }; -}; diff --git a/packages/api-security-so-ddb/src/index.ts b/packages/api-security-so-ddb/src/index.ts index 2796b745228..c900f2e9261 100644 --- a/packages/api-security-so-ddb/src/index.ts +++ b/packages/api-security-so-ddb/src/index.ts @@ -469,7 +469,7 @@ export const createStorageOperations = ( } if (Array.isArray(slug_in)) { - return items.filter(item => slug_in.includes(item.id)); + return items.filter(item => slug_in.includes(item.slug)); } return items; }, diff --git a/packages/api-security/src/utils/createGroupsTeamsAuthorizer.ts b/packages/api-security/src/utils/createGroupsTeamsAuthorizer.ts index bddfbdba150..caea648ea24 100644 --- a/packages/api-security/src/utils/createGroupsTeamsAuthorizer.ts +++ b/packages/api-security/src/utils/createGroupsTeamsAuthorizer.ts @@ -7,78 +7,88 @@ import { export type { GroupsTeamsAuthorizerConfig }; -export const createGroupsTeamsAuthorizer = ( - config: GroupsTeamsAuthorizerConfig +export const createGroupsTeamsAuthorizerHandler = < + TContext extends SecurityContext = SecurityContext +>( + config: GroupsTeamsAuthorizerConfig, + context: TContext ) => { - return new ContextPlugin(context => { + return async () => { const { security, tenancy } = context; - security.addAuthorizer(async () => { - const identity = security.getIdentity(); - if (!identity) { - return null; - } + const identity = security.getIdentity(); + if (!identity) { + return null; + } - // If `identityType` is specified, we'll only execute this authorizer for a matching identity. - if (config.identityType && identity.type !== config.identityType) { - return null; - } + // If `identityType` is specified, we'll only execute this authorizer for a matching identity. + if (config.identityType && identity.type !== config.identityType) { + return null; + } - // @ts-expect-error Check `packages/api-security/src/plugins/tenantLinkAuthorization.ts:23`. - const locale = context.i18n?.getContentLocale(); - if (!locale) { - return null; - } + // @ts-expect-error Check `packages/api-security/src/plugins/tenantLinkAuthorization.ts:23`. + const locale = context.i18n?.getContentLocale(); + if (!locale) { + return null; + } - if (config.canAccessTenant) { - const canAccessTenant = await config.canAccessTenant(context); - if (!canAccessTenant) { - return []; - } + if (config.canAccessTenant) { + const canAccessTenant = await config.canAccessTenant(context); + if (!canAccessTenant) { + return []; } + } - const currentTenantPermissions = await listPermissionsFromGroupsAndTeams({ - config, - context, - identity, - localeCode: locale.code - }); + const currentTenantPermissions = await listPermissionsFromGroupsAndTeams({ + config, + context, + identity, + localeCode: locale.code + }); - if (Array.isArray(currentTenantPermissions)) { - return currentTenantPermissions; - } + if (Array.isArray(currentTenantPermissions)) { + return currentTenantPermissions; + } - // If no security groups were found, it could be due to an identity accessing a sub-tenant. In this case, - // let's try loading permissions from the parent tenant. Note that this will work well for flat tenant - // hierarchy where there's a `root` tenant and 1 level of sibling sub-tenants. For multi-level hierarchy, - // the best approach is to code a plugin with the desired permissions-fetching logic. - if (config.inheritGroupsFromParentTenant === false) { - return null; - } + // If no security groups were found, it could be due to an identity accessing a sub-tenant. In this case, + // let's try loading permissions from the parent tenant. Note that this will work well for flat tenant + // hierarchy where there's a `root` tenant and 1 level of sibling sub-tenants. For multi-level hierarchy, + // the best approach is to code a plugin with the desired permissions-fetching logic. + if (config.inheritGroupsFromParentTenant === false) { + return null; + } - const parentTenantId = context.tenancy.getCurrentTenant().parent; - if (!parentTenantId) { - return null; - } + const parentTenantId = context.tenancy.getCurrentTenant().parent; + if (!parentTenantId) { + return null; + } - const parentTenant = await tenancy.getTenantById(parentTenantId); - if (!parentTenant) { - return null; - } + const parentTenant = await tenancy.getTenantById(parentTenantId); + if (!parentTenant) { + return null; + } - const parentTenantPermissions = await tenancy.withTenant(parentTenant, async () => { - return listPermissionsFromGroupsAndTeams({ - config, - context, - identity, - localeCode: locale.code - }); + const parentTenantPermissions = await tenancy.withTenant(parentTenant, async () => { + return listPermissionsFromGroupsAndTeams({ + config, + context, + identity, + localeCode: locale.code }); + }); - if (Array.isArray(parentTenantPermissions)) { - return parentTenantPermissions; - } + if (Array.isArray(parentTenantPermissions)) { + return parentTenantPermissions; + } - return null; - }); + return null; + }; +}; + +export const createGroupsTeamsAuthorizer = ( + config: GroupsTeamsAuthorizerConfig +) => { + return new ContextPlugin(context => { + const gcc = createGroupsTeamsAuthorizerHandler(config, context); + context.security.addAuthorizer(gcc); }); }; diff --git a/packages/api-security/src/utils/createGroupsTeamsAuthorizer/listPermissionsFromGroupsAndTeams.ts b/packages/api-security/src/utils/createGroupsTeamsAuthorizer/listPermissionsFromGroupsAndTeams.ts index 6859668ffa3..86e31d8c0c8 100644 --- a/packages/api-security/src/utils/createGroupsTeamsAuthorizer/listPermissionsFromGroupsAndTeams.ts +++ b/packages/api-security/src/utils/createGroupsTeamsAuthorizer/listPermissionsFromGroupsAndTeams.ts @@ -1,5 +1,5 @@ import { getPermissionsFromSecurityGroupsForLocale } from "../getPermissionsFromSecurityGroupsForLocale"; -import { SecurityContext } from "~/types"; +import { SecurityContext, SecurityRole } from "~/types"; import { Identity } from "@webiny/api-authentication/types"; export type GroupSlug = string | undefined; @@ -63,6 +63,24 @@ export const listPermissionsFromGroupsAndTeams = async < groupSlugs.push(...identity.groups); } + const filteredGroupSlugs = groupSlugs.filter(Boolean) as string[]; + const dedupedGroupSlugs = Array.from(new Set(filteredGroupSlugs)); + + const loadedGroups: SecurityRole[] = []; + + if (dedupedGroupSlugs.length > 0) { + // Load groups coming from teams. + const loadedGroupsBySlugs = await security.withoutAuthorization(() => { + return security.listGroups({ + where: { slug_in: dedupedGroupSlugs } + }); + }); + + if (loadedGroupsBySlugs.length > 0) { + loadedGroups.push(...loadedGroupsBySlugs); + } + } + if (wcp.canUseTeams()) { // Load groups coming from teams. if (identity.team) { @@ -83,26 +101,33 @@ export const listPermissionsFromGroupsAndTeams = async < }); }); - const groupSlugsFromTeams = loadedTeams.map(team => team.groups).flat(); - groupSlugs.push(...groupSlugsFromTeams); - } - } - - const filteredGroupSlugs = groupSlugs.filter(Boolean) as string[]; - const dedupedGroupSlugs = Array.from(new Set(filteredGroupSlugs)); + // Upon returning group IDs from teams, we're also filtering out groups that were already loaded. + // Also note that `team.groups` contains group IDs, not slugs. Hence, we need to load groups by IDs. + const groupIdsFromTeams = loadedTeams + .map(team => team.groups) + .flat() + .filter(groupId => { + const alreadyLoaded = loadedGroups.find(group => group.id === groupId); + return !alreadyLoaded; + }); - if (dedupedGroupSlugs.length > 0) { - // Load groups coming from teams. - const loadedGroups = await security.withoutAuthorization(() => { - return security.listGroups({ - where: { slug_in: dedupedGroupSlugs } - }); - }); + if (groupIdsFromTeams.length > 0) { + const loadedGroupsFromTeams = await security.withoutAuthorization(() => { + return security.listGroups({ + where: { id_in: groupIdsFromTeams } + }); + }); - if (loadedGroups.length > 0) { - return getPermissionsFromSecurityGroupsForLocale(loadedGroups, localeCode); + if (loadedGroupsFromTeams.length > 0) { + loadedGroups.push(...loadedGroupsFromTeams); + } + } } } + if (loadedGroups.length > 0) { + return getPermissionsFromSecurityGroupsForLocale(loadedGroups, localeCode); + } + return null; };