Browse Source

Merge pull request #3796 from nocodb/feat/3781-app-store

Feat: Limit app store access to super admin
pull/3807/head
navi 2 years ago committed by GitHub
parent
commit
4dc021a737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components.d.ts
  2. 5
      packages/nc-gui/components/dashboard/settings/AppStore.vue
  3. 32
      packages/nc-gui/components/dashboard/settings/Modal.vue
  4. 8
      packages/nc-gui/composables/useUIPermission/index.ts
  5. 95
      packages/nc-gui/composables/useUIPermission/rolePermissions.ts
  6. 15
      packages/nc-gui/layouts/base.vue
  7. 10
      packages/nc-gui/middleware/auth.global.ts
  8. 1
      packages/nc-gui/nuxt-shim.d.ts
  9. 19
      packages/nc-gui/pages/index/apps.vue
  10. 6
      packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts
  11. 492
      packages/nocodb/src/lib/utils/projectAcl.ts
  12. 38
      scripts/cypress/integration/common/5a_user_role.js
  13. 85
      scripts/cypress/integration/common/5c_super_user_role.js
  14. 269
      scripts/cypress/integration/spec/roleValidation.spec.js
  15. 2
      scripts/cypress/integration/test/pg-restRoles.js
  16. 3
      scripts/cypress/integration/test/restRoles.js
  17. 2
      scripts/cypress/integration/test/xcdb-restRoles.js
  18. 2
      scripts/cypress/support/commands.js

2
packages/nc-gui/components.d.ts vendored

@ -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']

5
packages/nc-gui/components/dashboard/settings/AppStore.vue

@ -115,7 +115,7 @@ onMounted(async () => {
:body-style="{ width: '100%' }"
>
<div class="install-btn flex flex-row justify-end space-x-1">
<a-button v-if="app.parsedInput" size="small" outlined @click="showInstallPluginModal(app)">
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
<MdiEditIcon class="pr-0.5" :height="12" />
Edit
@ -127,7 +127,7 @@ onMounted(async () => {
<div class="flex ml-0.5">Reset</div>
</div>
</a-button>
<a-button v-else size="small" outlined type="primary" ghost @click="showInstallPluginModal(app)">
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
<MdiPlusIcon />
Install
@ -185,7 +185,6 @@ onMounted(async () => {
.caption {
font-size: 0.7rem;
color: #242f3e;
}
.avatar {

32
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'),

8
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]
}

95
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<string, boolean>,
[Role.Guest]: {} as Record<string, boolean>,
[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<typeof rolePermissions, 'creator' | 'owner' | 'guest' | 'admin'>
type RolePermissions = Omit<typeof rolePermissions, 'guest' | 'admin' | 'super'>
type GetKeys<T> = T extends Record<string, any> ? keyof T : never
type GetKeys<T> = T extends Record<any, Record<infer Key, boolean>> ? Key : never
export type Permission<K extends keyof RolePermissions = keyof RolePermissions> = RolePermissions[K] extends Record<string, any>
export type Permission<K extends keyof RolePermissions = keyof RolePermissions> = RolePermissions[K] extends Record<any, any>
? GetKeys<RolePermissions[K]>
: never

15
packages/nc-gui/layouts/base.vue

@ -13,6 +13,8 @@ const hasSider = ref(false)
const sidebar = ref<HTMLDivElement>()
const { isUIAllowed } = useUIPermission()
const logout = () => {
signOut()
navigateTo('/signin')
@ -87,6 +89,19 @@ hooks.hook('page:finish', () => {
</nuxt-link>
</a-menu-item>
<a-menu-divider class="!m-0" />
<a-menu-item v-if="isUIAllowed('appStore')" key="0" class="!rounded-t">
<nuxt-link
v-e="['c:settings:appstore', { page: true }]"
class="nc-project-menu-item group !no-underline"
to="/apps"
>
<MdiStorefrontOutline class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary">{{ $t('title.appStore') }}</span>
</nuxt-link>
</a-menu-item>
<a-menu-divider class="!m-0" />
<a-menu-item key="1" class="!rounded-b group">

10
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 })

1
packages/nc-gui/nuxt-shim.d.ts vendored

@ -28,6 +28,7 @@ declare module 'vue-router' {
public?: boolean
hideHeader?: boolean
title?: string
allowedRoles?: Role[]
}
interface RouteParams {

19
packages/nc-gui/pages/index/apps.vue

@ -0,0 +1,19 @@
<script lang="ts" setup>
import AppStore from '~/components/dashboard/settings/AppStore.vue'
import { Role } from '~/lib'
definePageMeta({
requiresAuth: true,
allowedRoles: [Role.Super],
title: 'title.appStore',
})
</script>
<template>
<div class="p-10 h-full overflow-auto">
<h1 class="text-3xl text-center mb-11 nc-app-store-title">{{ $t('title.appStore') }}</h1>
<AppStore />
</div>
</template>
<style scoped></style>

6
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) {

492
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,
},
},
};

38
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");

85
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')
});
});
});
}

269
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);

2
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");

3
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");

2
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");

2
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);

Loading…
Cancel
Save