From 7ddaf6479f934af59832269f06bc2f4c144aad17 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 09:54:38 +0200 Subject: [PATCH 1/7] Clean unused pipelines --- .../workflows/release-singleimage-test.yml | 72 ------------------- package.json | 1 - 2 files changed, 73 deletions(-) delete mode 100644 .github/workflows/release-singleimage-test.yml diff --git a/.github/workflows/release-singleimage-test.yml b/.github/workflows/release-singleimage-test.yml deleted file mode 100644 index c3a14226ce..0000000000 --- a/.github/workflows/release-singleimage-test.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Test - -on: - workflow_dispatch: - -env: - CI: true - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - REGISTRY_URL: registry.hub.docker.com - NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} -jobs: - build: - name: "build" - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x] - steps: - - name: "Checkout" - uses: actions/checkout@v4 - with: - submodules: true - token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: "yarn" - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - name: Setup Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - name: Run Yarn - run: yarn - - name: Run Yarn Build - run: yarn build --scope @budibase/server --scope @budibase/worker - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_API_KEY }} - - name: Get the latest release version - id: version - run: | - release_version=$(cat lerna.json | jq -r '.version') - echo $release_version - echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV - - name: Tag and release Budibase service docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - pull: true - platforms: linux/amd64,linux/arm64 - build-args: BUDIBASE_VERSION=0.0.0+test - tags: budibase/budibase-test:test - file: ./hosting/single/Dockerfile.v2 - cache-from: type=registry,ref=budibase/budibase-test:test - cache-to: type=inline - - name: Tag and release Budibase Azure App Service docker image - uses: docker/build-push-action@v2 - with: - context: . - push: true - platforms: linux/amd64 - build-args: | - TARGETBUILD=aas - BUDIBASE_VERSION=0.0.0+test - tags: budibase/budibase-test:aas - file: ./hosting/single/Dockerfile.v2 diff --git a/package.json b/package.json index 100a306a35..d3f4903e6c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "build:sdk": "lerna run --stream build:sdk", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "release": "lerna publish from-package --yes --force-publish --no-git-tag-version --no-push --no-git-reset", - "release:develop": "yarn release --dist-tag develop", "restore": "yarn run clean && yarn && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", From 389848efbefe4e37d3bf397d16fda5ad04e9abdc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 11:05:07 +0200 Subject: [PATCH 2/7] Add update versions v2 --- scripts/updateWorkepaceVersions.V2.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 scripts/updateWorkepaceVersions.V2.sh diff --git a/scripts/updateWorkepaceVersions.V2.sh b/scripts/updateWorkepaceVersions.V2.sh new file mode 100755 index 0000000000..634bcbcfb0 --- /dev/null +++ b/scripts/updateWorkepaceVersions.V2.sh @@ -0,0 +1,8 @@ +#!/bin/bash +version=$1 +echo "Setting version $version" +yarn lerna exec "yarn version --no-git-tag-version --new-version=$version" +echo "Updating dependencies" +node scripts/syncLocalDependencies.js $version +echo "Syncing yarn workspace" +yarn From 980e223aef9e9aab8545b5339a9776cce75b7a82 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 11:24:02 +0200 Subject: [PATCH 3/7] Fix typo --- ...pdateWorkepaceVersions.V2.sh => updateWorkspaceVersions.V2.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{updateWorkepaceVersions.V2.sh => updateWorkspaceVersions.V2.sh} (100%) diff --git a/scripts/updateWorkepaceVersions.V2.sh b/scripts/updateWorkspaceVersions.V2.sh similarity index 100% rename from scripts/updateWorkepaceVersions.V2.sh rename to scripts/updateWorkspaceVersions.V2.sh From 5909b4a7c723c8e13fc735e7844fcf00331f8b0b Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 26 Oct 2023 11:24:43 +0100 Subject: [PATCH 4/7] Add template for grid blocks (#12086) * wip * linting --------- Co-authored-by: Mihail Hadzhiev <102024164+MihailHadzhiev2022@users.noreply.github.com> --- .../store/screenTemplates/rowListScreen.js | 22 ++++++++++++++---- .../NewScreen/CreateScreenModal.svelte | 8 +++++-- .../NewScreen/DatasourceModal.svelte | 6 ++++- .../design/_components/NewScreen/grid.png | Bin 0 -> 24819 bytes .../design/_components/NewScreen/index.svelte | 11 +++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index b17bd99e10..59bcd0d5e8 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -2,14 +2,14 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" -export default function (datasources) { +export default function (datasources, mode = "table") { if (!Array.isArray(datasources)) { return [] } return datasources.map(datasource => { return { name: `${datasource.label} - List`, - create: () => createScreen(datasource), + create: () => createScreen(datasource, mode), id: ROW_LIST_TEMPLATE, resourceId: datasource.resourceId, } @@ -40,10 +40,24 @@ const generateTableBlock = datasource => { return tableBlock } -const createScreen = datasource => { +const generateGridBlock = datasource => { + const gridBlock = new Component("@budibase/standard-components/gridblock") + gridBlock + .customProps({ + table: datasource, + }) + .instanceName(`${datasource.label} - Grid block`) + return gridBlock +} + +const createScreen = (datasource, mode) => { return new Screen() .route(rowListUrl(datasource)) .instanceName(`${datasource.label} - List`) - .addChild(generateTableBlock(datasource)) + .addChild( + mode === "table" + ? generateTableBlock(datasource) + : generateGridBlock(datasource) + ) .json() } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index 9a96242b30..92ed3dcfc7 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -12,6 +12,7 @@ import { capitalise } from "helpers" import { goto } from "@roxi/routify" + let mode let pendingScreen // Modal refs @@ -100,14 +101,15 @@ } // Handler for NewScreenModal - export const show = mode => { + export const show = newMode => { + mode = newMode selectedTemplates = null blankScreenUrl = null screenMode = mode pendingScreen = null screenAccessRole = Roles.BASIC - if (mode === "table") { + if (mode === "table" || mode === "grid") { datasourceModal.show() } else if (mode === "blank") { let templates = getTemplates($tables.list) @@ -123,6 +125,7 @@ // Handler for DatasourceModal confirmation, move to screen access select const confirmScreenDatasources = async ({ templates }) => { + console.log(templates) selectedTemplates = templates screenAccessRoleModal.show() } @@ -177,6 +180,7 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte index a866cd23d4..731c60a406 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte @@ -7,6 +7,7 @@ import rowListScreen from "builderStore/store/screenTemplates/rowListScreen" import DatasourceTemplateRow from "./DatasourceTemplateRow.svelte" + export let mode export let onCancel export let onConfirm export let initialScreens = [] @@ -24,7 +25,10 @@ screen => screen.resourceId !== resourceId ) } else { - selectedScreens = [...selectedScreens, rowListScreen([datasource])[0]] + selectedScreens = [ + ...selectedScreens, + rowListScreen([datasource], mode)[0], + ] } } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..c3efa30a67d733434fade938263e4aba9fd7cdee GIT binary patch literal 24819 zcmeF2^K&Loxc0NLZQHhujjfGsdxK|VZfx7e6Wbfxwr#!nzID!jaekPZs+sDUuIZ`n z>;7CL3KO$}rD9phyHMe_&zC(uBi@p; zpX62_{m(=0o8h$7HfmC975vH_%!#LESVN^s5=$b5ter^`V8h(6r2d>}Ae-oOjVn3d zXqxs})V6x>pi*NsicRA!yYA<(=RWI*`X&iWj$6Qojz=vUQxPZquRyPVKPB}N!|T8{ zI2NQOldnj>+Nf^B8VV3R7VfSbQFMVGQ76uOJD!34`l*=t6K(VsRd!3iQG3mtHwwzb zO0DIL2%&VNn;1YBIMxjkc98qJ|GG57ay~15c|GQ;L>uKQgN(>{Ax4)h&YS`xdD05a z4u+m-X?abL|8bdHie;XLvv}mm0d{KX(m0wR_(C^3<6{il9Y< z7K(}dbyWQKN5vB=N34;o-hZ-_=CkHQQlNUJu+$@D1Yu)?~ zIVGY1Rx`LrIhV{8SHXuc3~j~|zev^vT7rd^3XvnGjUB<73UN9>xU$)k7KsF)M=?% zd7<6w3dTY{oFvDZ+)#cIWOa3?91sZUsE9dd%U~qXt4ArmGq%~pV4o(+_l`sGU0hZp zPQzvb$1(iaE7fVKq4t=%uzg0g#PAVO=H?gGLK?7Yxs|k6VL%AJGjLlk>0&`zip;h? zOOyXm0e#enG4{#6V1}g{mYP9nt00N~3-fNr^mB^^vV8m4QL8MoUqH)aaJ)m&K7u&! z;GA1lX{~KTF^46cHwO|I28+-yL@Oae3TRm;UuM7Kcbp`^y#3*PrMibL-%CSy8E{`d zQslx`9x*%AqD$Y^ngllLtTK0$NptMWyqz7=dOkc~N0%@Rt$2<)gS~`^emv*7-cTL>=M^3b zoOWGrwNKr2U+imsQZkYKt1HM|96R+RTV!dzU1TYE1ckb6{=Lo^x{XDKP@lSB$(M}P zwbWYTfbWPJ@+hvgnlid3woo!^%kcmWnq9U1rjd9H!++Q4x;-9caOx%@e_<1myngf} z_(HHcQ&YqbcEA7%+eRa}4%)>2%X!3Gi-m5-|MkYR$T((-WRe6I=NANCVLnW=Fa*J= z)`Ho=LG88R<4CW#IieZ8O5L3vsm)5h;3UBT&Oorka9$Mb@U=&Y>O>j_RdTK5Mv-Di zk5*lf=}hW+)bZEfxY6~Yo2HNv`!Iq#Aqx_@m|PM42kU%`z-c+WtmopL;5u==pX05$ zx02z-hM-3QuJ3$efpR~wUE~1Oj57Ix;1IhedSP5A^7pUH&{05yuQ z`mob)1QD&fC2XrZ1MKmB=5zRQ!DfNSj`qc|RS*ORQ%hLEzT0-YoL~0`Wh2C$9&Sl= zRc8B1x^+9{%VT-D!s%j?Z780pg)#PJV{ruDDA?Vv`K^1~6jZe87j*=)5sWfS2VD^A z>bk)NuXJt32u%|vx_lIzsR90lT1j{N(Jf}c&^B%k0Nr1h)-;l7PHZawEZL`Ygz!+-^>Z-8R)T zvdU4eX!PbJmgEPLNEH&BnnCL?v}((1DqY9{GmDh9qVyH=m~2ispFJ5QZ~fwJ@sr;8uB?H$TIFuFZ81G zV!J+2JhXHnl6(JU@Bf7w3S~l^`AZRQGDU!EVn2;EGGbw@ZnqP=xG6vPj%?SIqItcj zxt%Zf7V4P@yuxQN?J55`cN@c<3*>6n++>*pvH`YD7IjSyy+3g)3s1z*)cJ8hx4DSK z75)|+uh|k-Vds_P2*q+h(uHRw;QS;KCiBJb;Qd(02C>E2x$J2-;;n+-Wo^y}pdIOH zSi9O7U4m}R>rlp5`Jj9Vpw>k;C#y_<#V`@|x> za%jWt^e1CPK+qz=*uGkqv)|>@AY})4y5T6fu6C+JIiDugqzu~$-uS-ogsLmN{dil6 zlcg3)^durb8F)DH86hxDA&u!Bt1WNO0$7D>UUF!YTIBbn8Q2J|>XXNZduLyRIOohS zs>t=%9=Q+RNavCKJtzk?^Bm-Bo3S$AohcgaBPMDRtnXmN&C$7A*(-K92dEjO0roTJ zGQLJHs5t(%*>PcqFfjah=gn>Ci(+M;nwvkby4y`G8u4MksyaLhwObB`?X zy2%*rYjc;**q^WNx%9Xgk?!>acy#u;Paa`fOGPDr8MHbJEgV8J?pYf zaNL4mPE3d6dv`(*h~lf-o6o|#o+)(Db*c}}_?uG)sNnj?`g3MDA8s{HQy3knY*fXa zo5l0>+XZNo>8U3_{$$l%`D@uFSmMo zB(HiTs}K0@Hol9hM{kJp1*eol|jjXit^P+gU#W0}K6r1)Ri|h}5PNVl_ zCyPw~C^i#2R=Gy(>TYG=>~xl#Mh|7_SaK8lMfjfx#=+s&j5p*M*-uZ*mhfxVcm$J` zpSiG%hMcJ{v1woR?gF|;Z3l=@X4+$!s5{vsqs1pJ{A;u!^I(LX6pciCZSuWm$s3I0 zRhM{BmavrtlkKa<*?1~z1$kK8t_p|1KsWtf4+-JWN#Q~eQT1fRaR|CO z(rQM50-QFdCp>i3*Uf=(5!o8EP6&B?h;4VnN_E+k^^RMxVQgSF`Vq&G5%!zcg-{@Q z?@%!o%*tDur7mwkL>b!AWVLeX_7L2$Z!g~QSi2y!4bR}oKsk~aYk~HmAk;(J zWotu|RegRQ>h_3NDQ8lSo^>u5Ot7;5VZP^OUC|aywU&u?n9Glt^I__|$H(Q`aa1Sq z2HF9n=46B#%jzLR+X7i5L52Z?CAnLXcoT2zu|k#(ILbu;_2dFpnKe5$sIN=gSg8I5 zM5|aM?BjH6LQ3PIxSBqe6JEdVMuuZqhfKY*0Gr)isTha8e?n<$v6tXuz6UEY^4;V0 zpY~(ym(5#;8^2m_I2^K?IlZI2Ax=D|8aSO^wgDNh5gwmDv|nOz0}*b?;#%!XvJ0sd zFg?D*)~8=L&&C)$Hf1pNtKB#dUSW!e4@#(RCI#x(5W7EWKc;nIt(y79VKdc~jxVEA zlZWaB!NsPc;grVHASjNm-9UKEUuiC!Pf_cB)l1*oOW`8GprA+lt~Pm^olZ0SlQ+>m zWFvaZWjknrQ*}P6r&2A{Zbwz!#aHg5nlT;5UnJc3(8P#YA0PIePF)Z^&dwYN6wh(k z(`JY8hF?lqgec6;=ZUcTl>3=xJkJ0ky0ttR)cY32^2>*c{iX_4Ty~y{*j)81Fs8wt z4yOpwiF-=o{HZ>|iIfOwfWnx|2C6HIL!0MM7K32_13Ss`jO7TNAk27rXaivT4>~T! zq~_6N1?ZqC+%-`nO?KA;1nJrK=5jaX2{6Bxe&Ri|IBt!^bi zonO%t=0v#d-v%>Q8TIfXc3qOcXp7CnTz&f_;(@mX_o(R_CnR0 zAAT4F-k=|X`kKQ?DlE3^fRhDKyC^KP*ovGPIR2&$npfgTwkiE95^dh&Pr8XO0}0|* zrhD|H5B-`M5_3L=u_8@lb!m&br#bGLD3^}RoK7U@4>03_MW@fh|3wTxzN>q*!;uLF z6~|*AGtgiO%NZu`O0e-pbQpJ)psjtTr><%G%s%i58*@h@+T0&q=o$cjWG!Z!G9?rZ zCU|BPsk`oUw$Jda32LxMi=Ra-RLR1{7gbgPjUgnkr3vPdAr&K5sW2H4M>g13h9|jz z@ar*eJjIf|IO#Z z+czZ7HVskS$5)u-Lf>TO;;yqx0_KHwpAty6LuLM`E`#G-M%e9I_z9w(-{3CM^LjIi zCO*SnfyIri=IH7a{jAZ9x14+X612MJI%wc=zHuhD-`U1jNyNLo@)F+MW7__hFK?r( z!5S4@_vs7G!NF71$^-XAR(fZ>Z;2si2!6F2CJtKqEO=!)qtBn=^lZvdT`SNvd~3O* zyc2e^Q-NnYnozhL_`{F0@{;W)tF!Lv@`h?*vf}u&0Jx#j|O0f@?C&1(6

K!sjL$Lv!Wkn)K>4}0WK}T!0MY-`Gz%=`-%HF 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 y4eyTF0000View, edit and delete rows on a table + +

createScreenModal.show("grid")}> +
+ +
+
+ Grid + View and manipulate rows on a grid +
+
From 8db0ff79a6c6d8b5061b1e55a5aa30a6ab0af07e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 12:44:36 +0200 Subject: [PATCH 5/7] Increase yarn timeouts --- packages/server/Dockerfile.v2 | 4 ++-- packages/worker/Dockerfile.v2 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/Dockerfile.v2 b/packages/server/Dockerfile.v2 index 881c21299e..f737570fcd 100644 --- a/packages/server/Dockerfile.v2 +++ b/packages/server/Dockerfile.v2 @@ -44,7 +44,7 @@ RUN chmod +x ./scripts/removeWorkspaceDependencies.sh WORKDIR /string-templates COPY packages/string-templates/package.json package.json RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 COPY packages/string-templates . @@ -57,7 +57,7 @@ COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies. RUN chmod +x ./scripts/removeWorkspaceDependencies.sh RUN ./scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true \ +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 \ # Remove unneeded data from file system to reduce image size && yarn cache clean && apt-get remove -y --purge --auto-remove g++ make python jq \ && rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp diff --git a/packages/worker/Dockerfile.v2 b/packages/worker/Dockerfile.v2 index a8be432827..4706ca155a 100644 --- a/packages/worker/Dockerfile.v2 +++ b/packages/worker/Dockerfile.v2 @@ -19,7 +19,7 @@ RUN chmod +x ./scripts/removeWorkspaceDependencies.sh WORKDIR /string-templates COPY packages/string-templates/package.json package.json RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 COPY packages/string-templates . @@ -30,7 +30,7 @@ RUN cd ../string-templates && yarn link && cd - && yarn link @budibase/string-te RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 # Remove unneeded data from file system to reduce image size RUN apk del .gyp \ && yarn cache clean From 44f9c64ed72b87c8a62e6b4754f107e1545bf07a Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 26 Oct 2023 13:06:25 +0100 Subject: [PATCH 6/7] Prevent the key user being used in rest queries (#12072) * Add warning about unusable user binding * linting * remove unnecessary safe nav operators * change regex to capture property access of user binding --- .../integration/RestQueryViewer.svelte | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/builder/src/components/integration/RestQueryViewer.svelte b/packages/builder/src/components/integration/RestQueryViewer.svelte index 254f65fcaf..e6913b0953 100644 --- a/packages/builder/src/components/integration/RestQueryViewer.svelte +++ b/packages/builder/src/components/integration/RestQueryViewer.svelte @@ -196,8 +196,36 @@ } } + const validateQuery = async () => { + const forbiddenBindings = /{{\s?user(\.(\w|\$)*\s?|\s?)}}/g + const bindingError = new Error( + "'user' is a protected binding and cannot be used" + ) + + if (forbiddenBindings.test(url)) { + throw bindingError + } + + if (forbiddenBindings.test(query.fields.requestBody ?? "")) { + throw bindingError + } + + Object.values(requestBindings).forEach(bindingValue => { + if (forbiddenBindings.test(bindingValue)) { + throw bindingError + } + }) + + Object.values(query.fields.headers).forEach(headerValue => { + if (forbiddenBindings.test(headerValue)) { + throw bindingError + } + }) + } + async function runQuery() { try { + await validateQuery() response = await queries.preview(buildQuery()) if (response.rows.length === 0) { notifications.info("Request did not return any data") From 9616e8e5519cc59ca7dabd286a4d97190eecdb9c Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:28:12 +0100 Subject: [PATCH 7/7] Custom role navigation links (#12056) * Refactor routing unit tests * Filter out higher level roles in preview * unit test * Refactor --------- Co-authored-by: Michael Drury --- packages/server/src/api/controllers/role.ts | 24 +++++++++++++++++- .../server/src/api/routes/tests/role.spec.js | 20 +++++++++++++++ .../src/api/routes/tests/routing.spec.js | 25 ++++++++----------- packages/server/src/constants/screens.ts | 14 ++++++++--- .../server/src/tests/utilities/structures.ts | 19 ++++++++++++-- 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index ed23009706..3697bbe925 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -1,4 +1,10 @@ -import { context, db as dbCore, events, roles } from "@budibase/backend-core" +import { + context, + db as dbCore, + events, + roles, + Header, +} from "@budibase/backend-core" import { getUserMetadataParams, InternalTables } from "../../db/utils" import { Database, Role, UserCtx, UserRoles } from "@budibase/types" import { sdk as sharedSdk } from "@budibase/shared-core" @@ -143,4 +149,20 @@ export async function accessible(ctx: UserCtx) { } else { ctx.body = await roles.getUserRoleIdHierarchy(roleId!) } + + // If a custom role is provided in the header, filter out higher level roles + const roleHeader = ctx.header?.[Header.PREVIEW_ROLE] as string + if (roleHeader && !Object.keys(roles.BUILTIN_ROLE_IDS).includes(roleHeader)) { + const inherits = (await roles.getRole(roleHeader))?.inherits + const orderedRoles = ctx.body.reverse() + let filteredRoles = [roleHeader] + for (let role of orderedRoles) { + filteredRoles = [role, ...filteredRoles] + if (role === inherits) { + break + } + } + filteredRoles.pop() + ctx.body = [roleHeader, ...filteredRoles] + } } diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js index c8e383d5ed..d133a69d64 100644 --- a/packages/server/src/api/routes/tests/role.spec.js +++ b/packages/server/src/api/routes/tests/role.spec.js @@ -158,5 +158,25 @@ describe("/roles", () => { expect(res.body.length).toBe(1) expect(res.body[0]).toBe("PUBLIC") }) + + it("should not fetch higher level accessible roles when a custom role header is provided", async () => { + await createRole({ + name: `CUSTOM_ROLE`, + inherits: roles.BUILTIN_ROLE_IDS.BASIC, + permissionId: permissions.BuiltinPermissionID.READ_ONLY, + version: "name", + }) + const res = await request + .get("/api/roles/accessible") + .set({ + ...config.defaultHeaders(), + "x-budibase-role": "CUSTOM_ROLE" + }) + .expect(200) + expect(res.body.length).toBe(3) + expect(res.body[0]).toBe("CUSTOM_ROLE") + expect(res.body[1]).toBe("BASIC") + expect(res.body[2]).toBe("PUBLIC") + }) }) }) diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index ff6d7aba1d..4076f4879c 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -1,5 +1,5 @@ const setup = require("./utilities") -const { basicScreen } = setup.structures +const { basicScreen, powerScreen } = setup.structures const { checkBuilderEndpoint, runInProd } = require("./utilities/TestFunctions") const { roles } = require("@budibase/backend-core") const { BUILTIN_ROLE_IDS } = roles @@ -12,19 +12,14 @@ const route = "/test" describe("/routing", () => { let request = setup.getRequest() let config = setup.getConfig() - let screen, screen2 + let basic, power afterAll(setup.afterAll) beforeAll(async () => { await config.init() - screen = basicScreen() - screen.routing.route = route - screen = await config.createScreen(screen) - screen2 = basicScreen() - screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER - screen2.routing.route = route - screen2 = await config.createScreen(screen2) + basic = await config.createScreen(basicScreen(route)) + power = await config.createScreen(powerScreen(route)) await config.publish() }) @@ -61,8 +56,8 @@ describe("/routing", () => { expect(res.body.routes[route]).toEqual({ subpaths: { [route]: { - screenId: screen._id, - roleId: screen.routing.roleId + screenId: basic._id, + roleId: basic.routing.roleId } } }) @@ -80,8 +75,8 @@ describe("/routing", () => { expect(res.body.routes[route]).toEqual({ subpaths: { [route]: { - screenId: screen2._id, - roleId: screen2.routing.roleId + screenId: power._id, + roleId: power.routing.roleId } } }) @@ -101,8 +96,8 @@ describe("/routing", () => { expect(res.body.routes).toBeDefined() expect(res.body.routes[route].subpaths[route]).toBeDefined() const subpath = res.body.routes[route].subpaths[route] - expect(subpath.screens[screen2.routing.roleId]).toEqual(screen2._id) - expect(subpath.screens[screen.routing.roleId]).toEqual(screen._id) + expect(subpath.screens[power.routing.roleId]).toEqual(power._id) + expect(subpath.screens[basic.routing.roleId]).toEqual(basic._id) }) it("make sure it is a builder only endpoint", async () => { diff --git a/packages/server/src/constants/screens.ts b/packages/server/src/constants/screens.ts index 23e36a65b8..6c88b0f957 100644 --- a/packages/server/src/constants/screens.ts +++ b/packages/server/src/constants/screens.ts @@ -1,7 +1,15 @@ import { roles } from "@budibase/backend-core" import { BASE_LAYOUT_PROP_IDS } from "./layouts" -export function createHomeScreen() { +export function createHomeScreen( + config: { + roleId: string + route: string + } = { + roleId: roles.BUILTIN_ROLE_IDS.BASIC, + route: "/", + } +) { return { description: "", url: "", @@ -40,8 +48,8 @@ export function createHomeScreen() { gap: "M", }, routing: { - route: "/", - roleId: roles.BUILTIN_ROLE_IDS.BASIC, + route: config.route, + roleId: config.roleId, }, name: "home-screen", } diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index d3e92ea34d..6d236062a8 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -20,6 +20,7 @@ import { SourceName, Table, } from "@budibase/types" +const { BUILTIN_ROLE_IDS } = roles export function basicTable(): Table { return { @@ -322,8 +323,22 @@ export function basicUser(role: string) { } } -export function basicScreen() { - return createHomeScreen() +export function basicScreen(route: string = "/") { + return createHomeScreen({ + roleId: BUILTIN_ROLE_IDS.BASIC, + route, + }) +} + +export function powerScreen(route: string = "/") { + return createHomeScreen({ + roleId: BUILTIN_ROLE_IDS.POWER, + route, + }) +} + +export function customScreen(config: { roleId: string; route: string }) { + return createHomeScreen(config) } export function basicLayout() {