diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts index 18d80347a9..e9ea1978af 100644 --- a/packages/nc-gui/components.d.ts +++ b/packages/nc-gui/components.d.ts @@ -190,6 +190,8 @@ declare module '@vue/runtime-core' { MdiSort: typeof import('~icons/mdi/sort')['default'] MdiStar: typeof import('~icons/mdi/star')['default'] MdiStarOutline: typeof import('~icons/mdi/star-outline')['default'] + MdiStorefrontOutline: typeof import('~icons/mdi/storefront-outline')['default'] + MdiStorefrontSutline: typeof import('~icons/mdi/storefront-sutline')['default'] MdiTable: typeof import('~icons/mdi/table')['default'] MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default'] MdiTableLarge: typeof import('~icons/mdi/table-large')['default'] diff --git a/packages/nc-gui/components/dashboard/settings/AppStore.vue b/packages/nc-gui/components/dashboard/settings/AppStore.vue index cef21e2448..ec6278c5bc 100644 --- a/packages/nc-gui/components/dashboard/settings/AppStore.vue +++ b/packages/nc-gui/components/dashboard/settings/AppStore.vue @@ -115,7 +115,7 @@ onMounted(async () => { :body-style="{ width: '100%' }" >
- +
Edit @@ -127,7 +127,7 @@ onMounted(async () => {
Reset
- +
Install @@ -185,7 +185,6 @@ onMounted(async () => { .caption { font-size: 0.7rem; - color: #242f3e; } .avatar { diff --git a/packages/nc-gui/components/dashboard/settings/Modal.vue b/packages/nc-gui/components/dashboard/settings/Modal.vue index 52dba591fc..6b775a87bb 100644 --- a/packages/nc-gui/components/dashboard/settings/Modal.vue +++ b/packages/nc-gui/components/dashboard/settings/Modal.vue @@ -77,20 +77,24 @@ const tabsInfo: TabGroup = { $e('c:settings:team-auth') }, }, - appStore: { - // App Store - title: t('title.appStore'), - icon: StoreFrontOutline, - subTabs: { - new: { - title: 'Apps', - body: AppStore, - }, - }, - onClick: () => { - $e('c:settings:appstore') - }, - }, + ...(isUIAllowed('appStore') + ? { + appStore: { + // App Store + title: t('title.appStore'), + icon: StoreFrontOutline, + subTabs: { + new: { + title: 'Apps', + body: AppStore, + }, + }, + onClick: () => { + $e('c:settings:appstore') + }, + }, + } + : {}), projMetaData: { // Project Metadata title: t('title.projMeta'), diff --git a/packages/nc-gui/composables/useUIPermission/index.ts b/packages/nc-gui/composables/useUIPermission/index.ts index ffc2e1e83e..cc2681e7aa 100644 --- a/packages/nc-gui/composables/useUIPermission/index.ts +++ b/packages/nc-gui/composables/useUIPermission/index.ts @@ -11,6 +11,14 @@ const hasPermission = (role: Role | ProjectRole, hasRole: boolean, permission: P if (isString(rolePermission) && rolePermission === '*') return true + if ('include' in rolePermission && rolePermission.include) { + return !!rolePermission.include[permission as keyof typeof rolePermission.include] + } + + if ('exclude' in rolePermission && rolePermission.exclude) { + return !rolePermission.exclude[permission as keyof typeof rolePermission.exclude] + } + return rolePermission[permission as keyof typeof rolePermission] } diff --git a/packages/nc-gui/composables/useUIPermission/rolePermissions.ts b/packages/nc-gui/composables/useUIPermission/rolePermissions.ts index 887553d3e7..eed4e4704d 100644 --- a/packages/nc-gui/composables/useUIPermission/rolePermissions.ts +++ b/packages/nc-gui/composables/useUIPermission/rolePermissions.ts @@ -2,58 +2,81 @@ import { ProjectRole, Role } from '~/lib' const rolePermissions = { // general role permissions + + /** + * Each permission value means the following + * `*` - which is wildcard, means all permissions are allowed + * `include` - which is an object, means only the permissions listed in the object are allowed + * `exclude` - which is an object, means all permissions are allowed except the ones listed in the object + * `undefined` or `{}` - which is the default value, means no permissions are allowed + * */ + /** todo: enable wildcard permission * limited permission due to unexpected behaviour in shared base if opened in same window */ - [Role.Super]: { - projectTheme: true, - }, - [Role.Admin]: {}, - [Role.Guest]: {}, + [Role.Super]: '*', + [Role.Admin]: {} as Record, + [Role.Guest]: {} as Record, [Role.User]: { - projectCreate: true, - projectActions: true, - projectSettings: true, + include: { + projectCreate: true, + projectActions: true, + projectSettings: true, + }, }, // Project role permissions - [ProjectRole.Creator]: '*', - [ProjectRole.Owner]: '*', + [ProjectRole.Creator]: { + exclude: { + appStore: true, + }, + }, + [ProjectRole.Owner]: { + exclude: { + appStore: true, + }, + }, [ProjectRole.Editor]: { - smartSheet: true, - xcDatatableEditable: true, - column: true, - tableAttachment: true, - tableRowUpdate: true, - dataInsert: true, - rowComments: true, - gridViewOptions: true, - sortSync: true, - fieldsSync: true, - gridColUpdate: true, - filterSync: true, - csvImport: true, - apiDocs: true, - projectSettings: true, - newUser: false, + include: { + smartSheet: true, + xcDatatableEditable: true, + column: true, + tableAttachment: true, + tableRowUpdate: true, + dataInsert: true, + rowComments: true, + gridViewOptions: true, + sortSync: true, + fieldsSync: true, + gridColUpdate: true, + filterSync: true, + csvImport: true, + apiDocs: true, + projectSettings: true, + newUser: false, + }, }, [ProjectRole.Commenter]: { - smartSheet: true, - column: true, - rowComments: true, - projectSettings: true, + include: { + smartSheet: true, + column: true, + rowComments: true, + projectSettings: true, + }, }, [ProjectRole.Viewer]: { - smartSheet: true, - column: true, - projectSettings: true, + include: { + smartSheet: true, + column: true, + projectSettings: true, + }, }, } as const -type RolePermissions = Omit +type RolePermissions = Omit -type GetKeys = T extends Record ? keyof T : never +type GetKeys = T extends Record> ? Key : never -export type Permission = RolePermissions[K] extends Record +export type Permission = RolePermissions[K] extends Record ? GetKeys : never diff --git a/packages/nc-gui/layouts/base.vue b/packages/nc-gui/layouts/base.vue index dab22ba952..715f128d16 100644 --- a/packages/nc-gui/layouts/base.vue +++ b/packages/nc-gui/layouts/base.vue @@ -13,6 +13,8 @@ const hasSider = ref(false) const sidebar = ref() +const { isUIAllowed } = useUIPermission() + const logout = () => { signOut() navigateTo('/signin') @@ -87,6 +89,19 @@ hooks.hook('page:finish', () => { + + + +   + + {{ $t('title.appStore') }} + + + diff --git a/packages/nc-gui/middleware/auth.global.ts b/packages/nc-gui/middleware/auth.global.ts index 7932c2df24..81d147fcea 100644 --- a/packages/nc-gui/middleware/auth.global.ts +++ b/packages/nc-gui/middleware/auth.global.ts @@ -1,6 +1,6 @@ import { message } from 'ant-design-vue' import { defineNuxtRouteMiddleware, navigateTo } from '#app' -import { useApi, useGlobal } from '#imports' +import { useApi, useGlobal, useRoles } from '#imports' /** * Global auth middleware @@ -38,6 +38,8 @@ export default defineNuxtRouteMiddleware(async (to, from) => { const { api } = useApi() + const { allRoles } = useRoles() + /** if user isn't signed in and google auth is enabled, try to check if sign-in data is present */ if (!state.signedIn && state.appInfo.value.googleAuthEnabled) await tryGoogleAuth() @@ -68,6 +70,12 @@ export default defineNuxtRouteMiddleware(async (to, from) => { return navigateTo(from.path) } } else { + /** If page is limited to certain users verify the user have the roles */ + if (to.meta.allowedRoles && to.meta.allowedRoles.every((role) => !allRoles.value[role])) { + message.error("You don't have enough permission to access the page.") + return navigateTo('/') + } + /** if users are accessing the projects without having enough permissions, redirect to My Projects page */ if (to.params.projectId && from.params.projectId !== to.params.projectId) { const user = await api.auth.me({ project_id: to?.params?.projectId as string }) diff --git a/packages/nc-gui/nuxt-shim.d.ts b/packages/nc-gui/nuxt-shim.d.ts index e1cc40b10a..1e00d96034 100644 --- a/packages/nc-gui/nuxt-shim.d.ts +++ b/packages/nc-gui/nuxt-shim.d.ts @@ -28,6 +28,7 @@ declare module 'vue-router' { public?: boolean hideHeader?: boolean title?: string + allowedRoles?: Role[] } interface RouteParams { diff --git a/packages/nc-gui/pages/index/apps.vue b/packages/nc-gui/pages/index/apps.vue new file mode 100644 index 0000000000..5b3d81bf2c --- /dev/null +++ b/packages/nc-gui/pages/index/apps.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts index f44674c0f1..b024ef7ab4 100644 --- a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts +++ b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts @@ -57,7 +57,11 @@ export default function (handlerFn, permissionName) { return ( hasRole && projectAcl[name] && - (projectAcl[name] === '*' || projectAcl[name][permissionName]) + (projectAcl[name] === '*' || + (projectAcl[name].exclude && + !projectAcl[name].exclude[permissionName]) || + (projectAcl[name].include && + projectAcl[name].include[permissionName])) ); }); if (!isAllowed) { diff --git a/packages/nocodb/src/lib/utils/projectAcl.ts b/packages/nocodb/src/lib/utils/projectAcl.ts index cf0b1f91b8..21a9e452b8 100644 --- a/packages/nocodb/src/lib/utils/projectAcl.ts +++ b/packages/nocodb/src/lib/utils/projectAcl.ts @@ -1,273 +1,299 @@ export default { - owner: '*', - creator: '*', + owner: { + exclude: { + pluginList: true, + pluginTest: true, + pluginRead: true, + pluginUpdate: true, + isPluginActive: true, + }, + }, + creator: { + exclude: { + pluginList: true, + pluginTest: true, + pluginRead: true, + pluginUpdate: true, + isPluginActive: true, + }, + }, guest: {}, editor: { - hideAllColumns: true, - showAllColumns: true, - auditRowUpdate: true, - passwordChange: true, - // new permissions - // project - projectGet: true, - projectList: true, - projectCost: true, - //table - tableList: true, - tableGet: true, + include: { + hideAllColumns: true, + showAllColumns: true, + auditRowUpdate: true, + passwordChange: true, + // new permissions + // project + projectGet: true, + projectList: true, + projectCost: true, + //table + tableList: true, + tableGet: true, - // data - dataList: true, - dataUpdate: true, - dataDelete: true, - dataInsert: true, - dataRead: true, - dataExist: true, - dataFindOne: true, - dataGroupBy: true, - commentsCount: true, - exportCsv: true, - exportExcel: true, + // data + dataList: true, + dataUpdate: true, + dataDelete: true, + dataInsert: true, + dataRead: true, + dataExist: true, + dataFindOne: true, + dataGroupBy: true, + commentsCount: true, + exportCsv: true, + exportExcel: true, - viewList: true, - columnList: true, - viewColumnUpdate: true, + viewList: true, + columnList: true, + viewColumnUpdate: true, - sortList: true, - sortCreate: true, - sortUpdate: true, - sortDelete: true, + sortList: true, + sortCreate: true, + sortUpdate: true, + sortDelete: true, - filterList: true, - filterCreate: true, - filterUpdate: true, - filterDelete: true, - filterGet: true, - filterChildrenRead: true, + filterList: true, + filterCreate: true, + filterUpdate: true, + filterDelete: true, + filterGet: true, + filterChildrenRead: true, - mmList: true, - hmList: true, - mmExcludedList: true, - hmExcludedList: true, - btExcludedList: true, - commentList: true, - commentRow: true, + mmList: true, + hmList: true, + mmExcludedList: true, + hmExcludedList: true, + btExcludedList: true, + commentList: true, + commentRow: true, - formViewGet: true, - projectInfoGet: true, - gridColumnUpdate: true, - galleryViewGet: true, + formViewGet: true, + projectInfoGet: true, + gridColumnUpdate: true, + galleryViewGet: true, - // old - xcTableAndViewList: true, - xcAuditCreate: true, - xcAttachmentUpload: true, - xcVirtualTableList: true, - rolesGet: true, - tableXcModelGet: true, - xcRelationsGet: true, - xcModelsList: true, - xcViewModelsList: true, - xcProcedureModelsList: true, - xcFunctionModelsList: true, - xcTableModelsList: true, - xcCronList: true, - xcRelationList: true, - tableMetaCreate: true, - tableMetaDelete: true, - tableMetaRecreate: true, - viewMetaCreate: true, - viewMetaDelete: true, - viewMetaRecreate: true, - procedureMetaCreate: true, - procedureMetaDelete: true, - procedureMetaRecreate: true, - functionMetaCreate: true, - functionMetaDelete: true, - functionMetaRecreate: true, + // old + xcTableAndViewList: true, + xcAuditCreate: true, + xcAttachmentUpload: true, + xcVirtualTableList: true, + rolesGet: true, + tableXcModelGet: true, + xcRelationsGet: true, + xcModelsList: true, + xcViewModelsList: true, + xcProcedureModelsList: true, + xcFunctionModelsList: true, + xcTableModelsList: true, + xcCronList: true, + xcRelationList: true, + tableMetaCreate: true, + tableMetaDelete: true, + tableMetaRecreate: true, + viewMetaCreate: true, + viewMetaDelete: true, + viewMetaRecreate: true, + procedureMetaCreate: true, + procedureMetaDelete: true, + procedureMetaRecreate: true, + functionMetaCreate: true, + functionMetaDelete: true, + functionMetaRecreate: true, - tableCreateStatement: true, - tableInsertStatement: true, - tableUpdateStatement: true, - tableSelectStatement: true, - tableDeleteStatement: true, + tableCreateStatement: true, + tableInsertStatement: true, + tableUpdateStatement: true, + tableSelectStatement: true, + tableDeleteStatement: true, - functionList: true, - sequenceList: true, - procedureList: true, - triggerList: true, - relationList: true, - relationListAll: true, - indexList: true, - list: true, - viewRead: true, - functionRead: true, - procedureRead: true, + functionList: true, + sequenceList: true, + procedureList: true, + triggerList: true, + relationList: true, + relationListAll: true, + indexList: true, + list: true, + viewRead: true, + functionRead: true, + procedureRead: true, - getKnexDataTypes: true, - DB_PROJECT_OPEN_BY_WEB: true, - PROJECT_READ_BY_WEB: true, - projectGenerateBackend: true, - projectGenerateBackendGql: true, - projectGetTsPolicyPath: true, - projectGetPolicyPath: true, - projectGetGqlPolicyPath: true, - handleApiCall: true, - executeRawQuery: true, - projectHasDb: true, - testConnection: true, - projectChangeEnv: true, + getKnexDataTypes: true, + DB_PROJECT_OPEN_BY_WEB: true, + PROJECT_READ_BY_WEB: true, + projectGenerateBackend: true, + projectGenerateBackendGql: true, + projectGetTsPolicyPath: true, + projectGetPolicyPath: true, + projectGetGqlPolicyPath: true, + handleApiCall: true, + executeRawQuery: true, + projectHasDb: true, + testConnection: true, + projectChangeEnv: true, - xcRoutesPolicyAllGet: true, - grpcProtoDownloadZip: true, + xcRoutesPolicyAllGet: true, + grpcProtoDownloadZip: true, - xcModelRowAuditAndCommentList: true, - xcAuditCommentInsert: true, - xcAuditModelCommentsCount: true, - xcExportAsCsv: true, + xcModelRowAuditAndCommentList: true, + xcAuditCommentInsert: true, + xcAuditModelCommentsCount: true, + xcExportAsCsv: true, - bulkDataInsert: true, - bulkDataUpdate: true, - bulkDataUpdateAll: true, - bulkDataDelete: true, - bulkDataDeleteAll: true, - relationDataRemove: true, - relationDataAdd: true, - dataCount: true, - upload: true, - uploadViaURL: true, + bulkDataInsert: true, + bulkDataUpdate: true, + bulkDataUpdateAll: true, + bulkDataDelete: true, + bulkDataDeleteAll: true, + relationDataRemove: true, + relationDataAdd: true, + dataCount: true, + upload: true, + uploadViaURL: true, + }, }, commenter: { - formViewGet: true, - passwordChange: true, - // project - projectGet: true, - exportCsv: true, - exportExcel: true, + include: { + formViewGet: true, + passwordChange: true, + // project + projectGet: true, + exportCsv: true, + exportExcel: true, - //table - tableGet: true, - // sort & filter - sortList: true, - viewList: true, - columnList: true, + //table + tableGet: true, + // sort & filter + sortList: true, + viewList: true, + columnList: true, - mmList: true, - hmList: true, - commentList: true, - commentRow: true, - projectInfoGet: true, + mmList: true, + hmList: true, + commentList: true, + commentRow: true, + projectInfoGet: true, - // data - dataList: true, - dataRead: true, - dataExist: true, - dataFindOne: true, - dataGroupBy: true, - commentsCount: true, + // data + dataList: true, + dataRead: true, + dataExist: true, + dataFindOne: true, + dataGroupBy: true, + commentsCount: true, - galleryViewGet: true, + galleryViewGet: true, - xcTableAndViewList: true, - xcVirtualTableList: true, - projectList: true, - projectCost: true, - PROJECT_READ_BY_WEB: true, + xcTableAndViewList: true, + xcVirtualTableList: true, + projectList: true, + projectCost: true, + PROJECT_READ_BY_WEB: true, - tableXcModelGet: true, - xcRelationList: true, - tableList: true, - functionList: true, - sequenceList: true, - procedureList: true, - triggerList: true, - relationList: true, - relationListAll: true, - indexList: true, - list: true, + tableXcModelGet: true, + xcRelationList: true, + tableList: true, + functionList: true, + sequenceList: true, + procedureList: true, + triggerList: true, + relationList: true, + relationListAll: true, + indexList: true, + list: true, - xcModelRowAuditAndCommentList: true, - xcAuditCommentInsert: true, - xcAuditModelCommentsCount: true, - xcExportAsCsv: true, - dataCount: true, + xcModelRowAuditAndCommentList: true, + xcAuditCommentInsert: true, + xcAuditModelCommentsCount: true, + xcExportAsCsv: true, + dataCount: true, + }, }, viewer: { - formViewGet: true, - passwordChange: true, - // project - projectGet: true, - //table - tableGet: true, - // data - dataList: true, - dataRead: true, - dataExist: true, - dataFindOne: true, - dataGroupBy: true, - commentsCount: true, - exportCsv: true, - exportExcel: true, + include: { + formViewGet: true, + passwordChange: true, + // project + projectGet: true, + //table + tableGet: true, + // data + dataList: true, + dataRead: true, + dataExist: true, + dataFindOne: true, + dataGroupBy: true, + commentsCount: true, + exportCsv: true, + exportExcel: true, - // sort & filter - sortList: true, - filterList: true, - projectInfoGet: true, + // sort & filter + sortList: true, + filterList: true, + projectInfoGet: true, - galleryViewGet: true, + galleryViewGet: true, - mmList: true, - hmList: true, - commentList: true, - commentRow: false, + mmList: true, + hmList: true, + commentList: true, + commentRow: false, - xcTableAndViewList: true, - xcVirtualTableList: true, - projectList: true, - projectCost: true, - PROJECT_READ_BY_WEB: true, + xcTableAndViewList: true, + xcVirtualTableList: true, + projectList: true, + projectCost: true, + PROJECT_READ_BY_WEB: true, - tableXcModelGet: true, - xcRelationList: true, - tableList: true, - viewList: true, - functionList: true, - sequenceList: true, - procedureList: true, - columnList: true, - triggerList: true, - relationList: true, - relationListAll: true, - indexList: true, - list: true, - xcExportAsCsv: true, - dataCount: true + tableXcModelGet: true, + xcRelationList: true, + tableList: true, + viewList: true, + functionList: true, + sequenceList: true, + procedureList: true, + columnList: true, + triggerList: true, + relationList: true, + relationListAll: true, + indexList: true, + list: true, + xcExportAsCsv: true, + dataCount: true, + }, }, user_new: { - passwordChange: true, - projectList: true, + include: { + passwordChange: true, + projectList: true, + }, }, super: '*', user: { - upload: true, - uploadViaURL: true, - passwordChange: true, - pluginList: true, - pluginRead: true, - pluginTest: true, - isPluginActive: true, - pluginUpdate: true, - projectCreate: true, - projectList: true, - projectCost: true, - handleAxiosCall: true, - testConnection: true, - projectCreateByWeb: true, - projectCreateByWebWithXCDB: true, - xcPluginRead: true, - xcMetaTablesImportZipToLocalFsAndDb: true, - xcMetaTablesExportDbToZip: true, - auditRowUpdate: true, + include: { + upload: true, + uploadViaURL: true, + passwordChange: true, + pluginList: true, + pluginRead: true, + pluginTest: true, + isPluginActive: true, + pluginUpdate: true, + projectCreate: true, + projectList: true, + projectCost: true, + handleAxiosCall: true, + testConnection: true, + projectCreateByWeb: true, + projectCreateByWebWithXCDB: true, + xcPluginRead: true, + xcMetaTablesImportZipToLocalFsAndDb: true, + xcMetaTablesExportDbToZip: true, + auditRowUpdate: true, + }, }, }; diff --git a/scripts/cypress/integration/common/5a_user_role.js b/scripts/cypress/integration/common/5a_user_role.js index d3fff9733a..14938cc4ba 100644 --- a/scripts/cypress/integration/common/5a_user_role.js +++ b/scripts/cypress/integration/common/5a_user_role.js @@ -184,13 +184,17 @@ export const genTest = (apiType, dbType) => { it(`[${roles[roleType].name}] Left navigation menu, New User add`, () => { // project configuration settings // - _advSettings(roleType, "userRole"); + if (roleType !== "owner") { + _advSettings(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Access control`, () => { // Access control validation // - _accessControl(roleType, "userRole"); + if (roleType !== "owner") { + _accessControl(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Schema: create table, add/modify/delete column`, () => { @@ -198,14 +202,18 @@ export const genTest = (apiType, dbType) => { // - Add/delete table // - Add/Update/delete column // - _editSchema(roleType, "userRole"); + if (roleType !== "owner") { + _editSchema(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Data: add/modify/delete row, update cell contents`, () => { // Table data related validations // - Add/delete/modify row // - _editData(roleType, "userRole"); + if (roleType !== "owner") { + _editData(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Comments: view/add`, () => { @@ -213,19 +221,27 @@ export const genTest = (apiType, dbType) => { // Viewer: only allowed to read // Everyone else: read &/ update // - _editComment(roleType, "userRole"); + if (roleType !== "owner") { + _editComment(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Right navigation menu, share view`, () => { // right navigation menu bar // Editor/Viewer/Commenter : can only view 'existing' views // Rest: can create/edit - _viewMenu(roleType, "userRole"); + if (roleType !== "owner") { + _viewMenu(roleType, "userRole"); + } }); it(`[${roles[roleType].name}] Download files`, () => { // to be fixed - if (roleType === "commenter" || roleType === "viewer") { + if ( + roleType === "commenter" || + roleType === "viewer" || + roleType === "owner" + ) { } else { // viewer & commenter doesn't contain hideField option in ncv2 // #ID, City, LastUpdate, City => Address, Country <= City, + @@ -259,11 +275,17 @@ export const genTest = (apiType, dbType) => { mainPage.unhideField("LastUpdate"); } }); + + it(`[${roles[roleType].name}] App store accessibility`, () => { + cy.visit("/#/apps").then(r =>{ + cy.toastWait('You don\'t have enough permission to access the page.') + }) + }); }); }; // skip owner validation as rest of the cases pretty much cover the same - // roleValidation('owner') + // roleValidation("owner"); roleValidation("creator"); roleValidation("editor"); roleValidation("commenter"); diff --git a/scripts/cypress/integration/common/5c_super_user_role.js b/scripts/cypress/integration/common/5c_super_user_role.js new file mode 100644 index 0000000000..fe72773dc0 --- /dev/null +++ b/scripts/cypress/integration/common/5c_super_user_role.js @@ -0,0 +1,85 @@ +import { loginPage } from '../../support/page_objects/navigation'; +import { roles } from '../../support/page_objects/projectConstants'; + +export const genTest = (apiType, dbType) => { + describe(`${apiType.toUpperCase()} api - Super user test`, () => { + before(() => { + loginPage.signIn(roles.owner.credentials); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); + + after(() => { + }); + + + it(`Open App store page and check slack app`, () => { + + cy.visit('/#/apps').then(win => { + cy.get('.nc-app-store-title').should('exist'); + cy.get('.nc-app-store-card-Slack').should('exist'); + + // install slack app + cy.get('.nc-app-store-card-Slack .install-btn') + .invoke('attr', 'style', 'right: 10px') + + cy.get('.nc-app-store-card-Slack .install-btn .nc-app-store-card-install') + .click(); + + cy.getActiveModal('.nc-modal-plugin-install') + .find('[placeholder="Channel Name"]') + .type('Test channel') + + cy.getActiveModal('.nc-modal-plugin-install') + .find('[placeholder="Webhook URL"]') + .type('http://test.com') + + + cy.getActiveModal('.nc-modal-plugin-install') + .find('button:contains("Save")') + .click() + + cy.toastWait('Successfully installed') + + cy.get('.nc-app-store-card-Slack .install-btn .nc-app-store-card-install').should('not.exist'); + + + // update slack app config + cy.get('.nc-app-store-card-Slack .install-btn .nc-app-store-card-edit').should('exist').click() + cy.getActiveModal('.nc-modal-plugin-install') + .should('exist') + .find('[placeholder="Channel Name"]') + .should('have.value', 'Test channel') + .clear() + .type('Test channel 2') + + cy.getActiveModal('.nc-modal-plugin-install') + .get('button:contains("Save")') + .click() + + + cy.toastWait('Successfully installed') + + + // reset slack app + cy.get('.nc-app-store-card-Slack .install-btn .nc-app-store-card-reset').should('exist').click() + + cy.getActiveModal('.nc-modal-plugin-uninstall') + .should('exist') + .find('button:contains("Confirm")') + .click() + + cy.toastWait('Plugin uninstalled successfully') + + }); + + }); + }); +} diff --git a/scripts/cypress/integration/spec/roleValidation.spec.js b/scripts/cypress/integration/spec/roleValidation.spec.js index 406bc2f7aa..72acb68583 100644 --- a/scripts/cypress/integration/spec/roleValidation.spec.js +++ b/scripts/cypress/integration/spec/roleValidation.spec.js @@ -1,5 +1,5 @@ -import { mainPage, settingsPage } from "../../support/page_objects/mainPage"; -import { roles } from "../../support/page_objects/projectConstants"; +import { mainPage, settingsPage } from '../../support/page_objects/mainPage'; +import { roles } from '../../support/page_objects/projectConstants'; // Left hand navigation bar, validation for // 1. Audit menu @@ -9,61 +9,62 @@ import { roles } from "../../support/page_objects/projectConstants"; export function _advSettings(roleType, mode) { cy.log(roleType, mode); - if (mode === "baseShare") { + if (mode === 'baseShare') { // open modal - cy.get(".nc-project-menu").should("exist").click(); - cy.getActiveMenu(".nc-dropdown-project-menu") + cy.get('.nc-project-menu').should('exist').click(); + cy.getActiveMenu('.nc-dropdown-project-menu') .find(`[data-menu-id="language"]`) - .should("exist"); + .should('exist'); // click again to close modal - cy.get(".nc-project-menu").should("exist").click(); + cy.get('.nc-project-menu').should('exist').click(); return; } let validationString = - true == roles[roleType].validations.advSettings ? "exist" : "not.exist"; + true == roles[roleType].validations.advSettings ? 'exist' : 'not.exist'; // cy.get(".nc-team-settings").should(validationString); - cy.get(".nc-project-menu").should("exist").click(); - cy.getActiveMenu(".nc-dropdown-project-menu") + cy.get('.nc-project-menu').should('exist').click(); + cy.getActiveMenu('.nc-dropdown-project-menu') .find(`[data-menu-id="preview-as"]`) .should(validationString); - cy.getActiveMenu(".nc-dropdown-project-menu") + cy.getActiveMenu('.nc-dropdown-project-menu') .find(`[data-menu-id="teamAndSettings"]:visible`) .should(validationString); if (true === roles[roleType].validations.advSettings) { - cy.getActiveMenu(".nc-dropdown-project-menu") + cy.getActiveMenu('.nc-dropdown-project-menu') .find(`[data-menu-id="teamAndSettings"]:visible`) .should(validationString) .click(); - cy.get(`[data-menu-id="teamAndAuth"]`).should("exist"); - cy.get(`[data-menu-id="appStore"]`).should("exist"); - cy.get(`[data-menu-id="projMetaData"]`).should("exist"); - cy.get(`[data-menu-id="audit"]`).should("exist"); + cy.get(`[data-menu-id="teamAndAuth"]`).should('exist'); + if (roleType === 'owner') + cy.get(`[data-menu-id="appStore"]`).should('exist'); + cy.get(`[data-menu-id="projMetaData"]`).should('exist'); + cy.get(`[data-menu-id="audit"]`).should('exist'); settingsPage.closeMenu(); } else { - cy.get(".nc-project-menu").should("exist").click(); + cy.get('.nc-project-menu').should('exist').click(); } // float menu in preview mode - if ("preview" === mode) { - cy.get(".nc-floating-preview-btn").should("exist"); - cy.get(".nc-floating-preview-btn") + if ('preview' === mode) { + cy.get('.nc-floating-preview-btn').should('exist'); + cy.get('.nc-floating-preview-btn') .find(`[type="radio"][value="${roles[roleType].name}"]`) - .should("be.checked"); + .should('be.checked'); } // cy.get("body").click("bottomRight"); } export function _editSchema(roleType, mode) { - let columnName = "City"; + let columnName = 'City'; let validationString = - true === roles[roleType].validations.editSchema ? "exist" : "not.exist"; + true === roles[roleType].validations.editSchema ? 'exist' : 'not.exist'; cy.openTableTab(columnName, 25); @@ -71,18 +72,18 @@ export function _editSchema(roleType, mode) { cy.get(`.nc-add-new-table`).should(validationString); // delete table option - cy.get(`.nc-project-tree-tbl-City`).should("exist").rightclick(); - cy.get(".ant-dropdown-content:visible").should(validationString); + cy.get(`.nc-project-tree-tbl-City`).should('exist').rightclick(); + cy.get('.ant-dropdown-content:visible').should(validationString); - if (validationString === "exist") { - cy.getActiveMenu(".nc-dropdown-tree-view-context-menu") + if (validationString === 'exist') { + cy.getActiveMenu('.nc-dropdown-tree-view-context-menu') .find('[role="menuitem"]') - .contains("Delete") - .should("exist"); - cy.getActiveMenu(".nc-dropdown-tree-view-context-menu") + .contains('Delete') + .should('exist'); + cy.getActiveMenu('.nc-dropdown-tree-view-context-menu') .find('[role="menuitem"]') - .contains("Rename") - .should("exist"); + .contains('Rename') + .should('exist'); // click on a cell to close table context menu mainPage.getCell(columnName, 3).click(); @@ -90,104 +91,104 @@ export function _editSchema(roleType, mode) { // add new column option // - cy.get(".nc-column-add").should(validationString); + cy.get('.nc-column-add').should(validationString); // update column (edit/ delete menu) - cy.get(".nc-ui-dt-dropdown").should(validationString); - - if (validationString === "exist") { - cy.get(".nc-import-menu").should("exist").click(); - cy.getActiveMenu(".nc-dropdown-import-menu").should("exist"); - cy.getActiveMenu(".nc-dropdown-import-menu") - .find(".ant-dropdown-menu-item") - .contains("Airtable"); - cy.getActiveMenu(".nc-dropdown-import-menu") - .find(".ant-dropdown-menu-item") - .contains("CSV file"); - cy.getActiveMenu(".nc-dropdown-import-menu") - .find(".ant-dropdown-menu-item") - .contains("JSON file"); - cy.getActiveMenu(".nc-dropdown-import-menu") - .find(".ant-dropdown-menu-item") - .contains("Microsoft Excel"); + cy.get('.nc-ui-dt-dropdown').should(validationString); + + if (validationString === 'exist') { + cy.get('.nc-import-menu').should('exist').click(); + cy.getActiveMenu('.nc-dropdown-import-menu').should('exist'); + cy.getActiveMenu('.nc-dropdown-import-menu') + .find('.ant-dropdown-menu-item') + .contains('Airtable'); + cy.getActiveMenu('.nc-dropdown-import-menu') + .find('.ant-dropdown-menu-item') + .contains('CSV file'); + cy.getActiveMenu('.nc-dropdown-import-menu') + .find('.ant-dropdown-menu-item') + .contains('JSON file'); + cy.getActiveMenu('.nc-dropdown-import-menu') + .find('.ant-dropdown-menu-item') + .contains('Microsoft Excel'); } } export function _editData(roleType, mode) { - let columnName = "City"; + let columnName = 'City'; let validationString = - true === roles[roleType].validations.editData ? "exist" : "not.exist"; + true === roles[roleType].validations.editData ? 'exist' : 'not.exist'; cy.openTableTab(columnName, 25); // add row button - cy.get(".nc-add-new-row-btn:visible").should(validationString); + cy.get('.nc-add-new-row-btn:visible').should(validationString); // add button at bottom of page mainPage.getCell(columnName, 25).scrollIntoView(); - cy.get(".nc-grid-add-new-cell:visible").should(validationString); + cy.get('.nc-grid-add-new-cell:visible').should(validationString); // update row option (right click) // - mainPage.getCell("City", 5).rightclick(); + mainPage.getCell('City', 5).rightclick(); cy.wait(100); - cy.get(".ant-dropdown-content:visible").should(validationString); + cy.get('.ant-dropdown-content:visible').should(validationString); - if (validationString === "exist") { + if (validationString === 'exist') { // right click options will exist (only for 'exist' case) // - cy.getActiveMenu(".nc-dropdown-grid-context-menu") - .contains("Insert New Row") + cy.getActiveMenu('.nc-dropdown-grid-context-menu') + .contains('Insert New Row') .should(validationString); - cy.getActiveMenu(".nc-dropdown-grid-context-menu") - .contains("Clear cell") + cy.getActiveMenu('.nc-dropdown-grid-context-menu') + .contains('Clear cell') .should(validationString); - cy.getActiveMenu(".nc-dropdown-grid-context-menu") - .contains("Delete Row") + cy.getActiveMenu('.nc-dropdown-grid-context-menu') + .contains('Delete Row') .should(validationString); - cy.getActiveMenu(".nc-dropdown-grid-context-menu") - .contains("Delete Selected Rows") + cy.getActiveMenu('.nc-dropdown-grid-context-menu') + .contains('Delete Selected Rows') .should(validationString); // cy.get("body").type("{esc}"); - mainPage.getCell("City", 13).click(); + mainPage.getCell('City', 13).click(); // update cell contents option using row expander should be enabled // mainPage .getRow(1) - .find(".nc-row-no") - .should("exist") + .find('.nc-row-no') + .should('exist') .eq(0) - .trigger("mouseover", { force: true }); - cy.get(".nc-row-expand").should("exist").eq(10).click({ force: true }); - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find("button") - .contains("Save row") - .should("exist"); - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find("button") - .contains("Cancel") - .should("exist") + .trigger('mouseover', { force: true }); + cy.get('.nc-row-expand').should('exist').eq(10).click({ force: true }); + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('button') + .contains('Save row') + .should('exist'); + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('button') + .contains('Cancel') + .should('exist') .click(); } else { // update cell contents option using row expander should be disabled // - cy.get(".nc-row-expand").should("exist").eq(10).click({ force: true }); - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find("button:disabled") - .contains("Save row") - .should("exist"); - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find("button") - .contains("Cancel") - .should("exist") + cy.get('.nc-row-expand').should('exist').eq(10).click({ force: true }); + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('button:disabled') + .contains('Save row') + .should('exist'); + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('button') + .contains('Cancel') + .should('exist') .click(); } // double click cell entries to edit // - mainPage.getCell("City", 5).dblclick().find("input").should(validationString); + mainPage.getCell('City', 5).dblclick().find('input').should(validationString); } // read &/ update comment @@ -195,11 +196,11 @@ export function _editData(roleType, mode) { // Everyone else: read &/ update // export function _editComment(roleType, mode) { - let columnName = "City"; + let columnName = 'City'; let validationString = true === roles[roleType].validations.editComment - ? "Comment added successfully" - : "Not allowed"; + ? 'Comment added successfully' + : 'Not allowed'; cy.openTableTab(columnName, 25); @@ -207,7 +208,7 @@ export function _editComment(roleType, mode) { // click on comment icon & type comment // - cy.get(".nc-row-expand").should("exist").eq(10).click({ force: true }); + cy.get('.nc-row-expand').should('exist').eq(10).click({ force: true }); // Expected response: // Viewer: Not able to see comment option @@ -216,32 +217,32 @@ export function _editComment(roleType, mode) { // cy.wait(3000); - if ("viewer" === roleType) { - cy.getActiveDrawer(".nc-drawer-expanded-form") - .should("exist") - .find(".nc-toggle-comments") - .should("not.exist"); + if ('viewer' === roleType) { + cy.getActiveDrawer('.nc-drawer-expanded-form') + .should('exist') + .find('.nc-toggle-comments') + .should('not.exist'); } else { - cy.getActiveDrawer(".nc-drawer-expanded-form") - .should("exist") - .find(".nc-toggle-comments") - .should("exist") + cy.getActiveDrawer('.nc-drawer-expanded-form') + .should('exist') + .find('.nc-toggle-comments') + .should('exist') .click(); - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find(".nc-comment-box") - .should("exist") - .type("Comment-1{enter}"); + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('.nc-comment-box') + .should('exist') + .type('Comment-1{enter}'); // cy.toastWait('Comment added successfully') - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find(".nc-toggle-comments") + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('.nc-toggle-comments') .click(); } - cy.getActiveDrawer(".nc-drawer-expanded-form") - .find("button") - .contains("Cancel") - .should("exist") + cy.getActiveDrawer('.nc-drawer-expanded-form') + .find('button') + .contains('Cancel') + .should('exist') .click(); } @@ -249,7 +250,7 @@ export function _editComment(roleType, mode) { // Editor/Viewer/Commenter : can only view 'existing' views // Rest: can create/edit export function _viewMenu(roleType, mode) { - let columnName = "City"; + let columnName = 'City'; // Lock, Download, Upload let menuWithSubmenuCount = 3; @@ -260,16 +261,16 @@ export function _viewMenu(roleType, mode) { cy.openTableTab(columnName, 25); let validationString = - true === roles[roleType].validations.shareView ? "exist" : "not.exist"; + true === roles[roleType].validations.shareView ? 'exist' : 'not.exist'; - if (roleType === "editor") { + if (roleType === 'editor') { // Download / Upload CSV menuWithSubmenuCount = 2; // Get API Snippet and ERD menuWithoutSubmenuCount = 2; // ERD - if (mode === "baseShare") menuWithoutSubmenuCount = 1; - } else if (roleType === "commenter" || roleType === "viewer") { + if (mode === 'baseShare') menuWithoutSubmenuCount = 1; + } else if (roleType === 'commenter' || roleType === 'viewer') { // Download CSV & Download excel menuWithSubmenuCount = 0; // Get API Snippet and ERD @@ -277,7 +278,7 @@ export function _viewMenu(roleType, mode) { } // view list field (default GRID view) - cy.get(`.nc-view-item`).should("exist"); + cy.get(`.nc-view-item`).should('exist'); // view create option, exists only for owner/ creator cy.get(`.nc-create-grid-view`).should(validationString); @@ -287,47 +288,47 @@ export function _viewMenu(roleType, mode) { // share view permissions are role specific // actions menu (more), only download csv should be visible for non-previlaged users - cy.get(".nc-actions-menu-btn").click(); - cy.getActiveMenu(".nc-dropdown-actions-menu") - .find(".ant-dropdown-menu-submenu:visible") - .should("have.length", menuWithSubmenuCount); - cy.getActiveMenu(".nc-dropdown-actions-menu") - .find(".ant-dropdown-menu-item:visible") - .should("have.length", menuWithoutSubmenuCount); + cy.get('.nc-actions-menu-btn').click(); + cy.getActiveMenu('.nc-dropdown-actions-menu') + .find('.ant-dropdown-menu-submenu:visible') + .should('have.length', menuWithSubmenuCount); + cy.getActiveMenu('.nc-dropdown-actions-menu') + .find('.ant-dropdown-menu-item:visible') + .should('have.length', menuWithoutSubmenuCount); // click again to close menu - cy.get(".nc-actions-menu-btn").click(); + cy.get('.nc-actions-menu-btn').click(); } export function _topRightMenu(roleType, mode) { // kludge; download csv menu persists until clicked - let columnName = "City"; + let columnName = 'City'; // cy.closeTableTab(columnName); // cy.openTableTab(columnName, 25); let validationString = - true == roles[roleType].validations.shareView ? "exist" : "not.exist"; + true == roles[roleType].validations.shareView ? 'exist' : 'not.exist'; cy.get(`.nc-share-base`).should(validationString); - cy.get(".nc-menu-translate").should("exist"); - cy.get(".nc-menu-accounts").should("exist"); + cy.get('.nc-menu-translate').should('exist'); + cy.get('.nc-menu-accounts').should('exist'); } // Access control list // export function disableTableAccess(tbl, role) { const cls = `.nc-acl-${tbl}-${role}-chkbox`; - cy.get(cls).find("input").should("be.checked").click({ force: true }); - cy.get(cls).find("input").should("not.be.checked"); + cy.get(cls).find('input').should('be.checked').click({ force: true }); + cy.get(cls).find('input').should('not.be.checked'); } export function enableTableAccess(tbl, role) { const cls = `.nc-acl-${tbl}-${role}-chkbox`; - cy.get(cls).find("input").should("not.be.checked").click({ force: true }); - cy.get(cls).find("input").should("be.checked"); + cy.get(cls).find('input').should('not.be.checked').click({ force: true }); + cy.get(cls).find('input').should('be.checked'); } export function _accessControl(roleType, previewMode) { - let validationString = roleType === "creator" ? "exist" : "not.exist"; + let validationString = roleType === 'creator' ? 'exist' : 'not.exist'; cy.get(`.nc-project-tree-tbl-Language`).should(validationString); cy.get(`.nc-project-tree-tbl-CustomerList`).should(validationString); diff --git a/scripts/cypress/integration/test/pg-restRoles.js b/scripts/cypress/integration/test/pg-restRoles.js index 194d22e051..fa868a3df1 100644 --- a/scripts/cypress/integration/test/pg-restRoles.js +++ b/scripts/cypress/integration/test/pg-restRoles.js @@ -2,6 +2,7 @@ let t0 = require("./explicitLogin"); let t01 = require("../common/00_pre_configurations"); let t5a = require("../common/5a_user_role"); let t5b = require("../common/5b_preview_role"); +let t5c = require("../common/5c_super_user_role"); const { setCurrentMode, } = require("../../support/page_objects/projectConstants"); @@ -12,6 +13,7 @@ const nocoTestSuite = (apiType, dbType) => { t5a.genTest(apiType, dbType); // t5b.genTest(apiType, dbType); + t5c.genTest(apiType, dbType); }; nocoTestSuite("rest", "postgres"); diff --git a/scripts/cypress/integration/test/restRoles.js b/scripts/cypress/integration/test/restRoles.js index 69a80b009a..b636dff4ec 100644 --- a/scripts/cypress/integration/test/restRoles.js +++ b/scripts/cypress/integration/test/restRoles.js @@ -2,6 +2,7 @@ let t0 = require("./explicitLogin"); let t01 = require("../common/00_pre_configurations"); let t5a = require("../common/5a_user_role"); let t5b = require("../common/5b_preview_role"); +let t5c = require("../common/5c_super_user_role"); const { setCurrentMode, } = require("../../support/page_objects/projectConstants"); @@ -12,6 +13,8 @@ const nocoTestSuite = (apiType, dbType) => { t5a.genTest(apiType, dbType); // t5b.genTest(apiType, dbType); + t5c.genTest(apiType, dbType); + }; nocoTestSuite("rest", "mysql"); diff --git a/scripts/cypress/integration/test/xcdb-restRoles.js b/scripts/cypress/integration/test/xcdb-restRoles.js index 985978fc6d..fd6edd531d 100644 --- a/scripts/cypress/integration/test/xcdb-restRoles.js +++ b/scripts/cypress/integration/test/xcdb-restRoles.js @@ -2,6 +2,7 @@ let t0 = require("./explicitLogin"); let t01 = require("../common/00_pre_configurations"); let t5a = require("../common/5a_user_role"); let t5b = require("../common/5b_preview_role"); +let t5c = require("../common/5c_super_user_role"); const { setCurrentMode, } = require("../../support/page_objects/projectConstants"); @@ -12,6 +13,7 @@ const nocoTestSuite = (apiType, dbType) => { t5a.genTest(apiType, dbType); // t5b.genTest(apiType, dbType); + t5c.genTest(apiType, dbType); }; nocoTestSuite("rest", "xcdb"); diff --git a/scripts/cypress/support/commands.js b/scripts/cypress/support/commands.js index de6139aa1d..ab89891b1a 100644 --- a/scripts/cypress/support/commands.js +++ b/scripts/cypress/support/commands.js @@ -479,7 +479,7 @@ Cypress.Commands.add("signOut", () => { cy.get(".nc-menu-accounts", { timeout: 30000 }).should("exist").click(); cy.getActiveMenu(".nc-dropdown-user-accounts-menu") .find(".ant-dropdown-menu-item") - .eq(1) + .last() .click(); // cy.wait(5000);