mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
Merge branch 'main' into fix/hide-modules-tab
This commit is contained in:
commit
0d7ff2d1f3
68 changed files with 1743 additions and 658 deletions
|
|
@ -40,7 +40,7 @@ export const groupsSelector = {
|
|||
resourceLabel: '[data-cy="resource-label"]',
|
||||
allAppsRadio: '[data-cy="all-apps-radio"]',
|
||||
allAppsLabel: '[data-cy="all-apps-label"]',
|
||||
allAppsHelperText: '[data-cy="all-apps-info-text"]',
|
||||
allAppsHelperText: '[data-cy="this-will-select-all-apps-in-the-workspace-including-any-new-apps-created-info-text"]',
|
||||
customradio: '[data-cy="custom-radio"]',
|
||||
customLabel: '[data-cy="custom-label"]',
|
||||
customHelperText: '[data-cy="custom-info-text"]',
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export const groupsText = {
|
|||
allAppsLabel: 'All apps',
|
||||
allAppsHelperText: 'This will select all apps in the workspace including any new apps created',
|
||||
customLabel: 'Custom',
|
||||
customHelperText: 'Select specific applications you want to add to the group',
|
||||
customHelperText: 'Select specific apps you want to add to the group',
|
||||
updateButtonText: 'Update',
|
||||
addButtonText: 'Add',
|
||||
userRole: 'User role',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"savechanges": "Save changes",
|
||||
"execute": "Execute",
|
||||
"Build": "Build",
|
||||
"back": "Back",
|
||||
"edit": "Edit",
|
||||
"search": "Search",
|
||||
|
|
|
|||
|
|
@ -282,9 +282,9 @@ class AppComponent extends React.Component {
|
|||
exact
|
||||
path="/:workspaceId/workflows/*"
|
||||
element={
|
||||
<AdminRoute {...this.props}>
|
||||
<PrivateRoute>
|
||||
<Workflows switchDarkMode={this.switchDarkMode} darkMode={this.darkMode} />
|
||||
</AdminRoute>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class HomePageComponent extends React.Component {
|
|||
id: currentSession?.current_user.id,
|
||||
organization_id: currentSession?.current_organization_id,
|
||||
},
|
||||
shouldRedirect: false,
|
||||
users: null,
|
||||
isLoading: true,
|
||||
creatingApp: false,
|
||||
|
|
@ -131,6 +132,13 @@ class HomePageComponent extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.appType === 'workflow') {
|
||||
if (!this.canViewWorkflow()) {
|
||||
toast.error('You do not have permission to view workflows');
|
||||
this.setState({ shouldRedirect: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
fetchAndSetWindowTitle({ page: pageTitles.DASHBOARD });
|
||||
this.fetchApps(1, this.state.currentFolder.id);
|
||||
this.fetchFolders();
|
||||
|
|
@ -148,7 +156,7 @@ class HomePageComponent extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.appType != this.props.appType) {
|
||||
this.fetchFolders();
|
||||
this.fetchApps(1);
|
||||
|
|
@ -156,6 +164,10 @@ class HomePageComponent extends React.Component {
|
|||
if (Object.keys(this.props.featureAccess).length && !this.state.featureAccess) {
|
||||
this.setState({ featureAccess: this.props.featureAccess, featuresLoaded: this.props.featuresLoaded });
|
||||
}
|
||||
if (this.state.shouldRedirect && !prevState.shouldRedirect) {
|
||||
const workspaceId = getWorkspaceId();
|
||||
this.props.navigate(`/${workspaceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
fetchFeatureAccesss = () => {
|
||||
|
|
@ -450,37 +462,70 @@ class HomePageComponent extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
canViewWorkflow = () => {
|
||||
return this.canUserPerform(this.state.currentUser, 'view');
|
||||
};
|
||||
|
||||
canUserPerform(user, action, app) {
|
||||
if (authenticationService.currentSessionValue?.super_admin) return true;
|
||||
const currentSession = authenticationService.currentSessionValue;
|
||||
const appPermission = currentSession.app_group_permissions;
|
||||
const canUpdateApp =
|
||||
appPermission && (appPermission.is_all_editable || appPermission.editable_apps_id.includes(app?.id));
|
||||
const canReadApp =
|
||||
(appPermission && canUpdateApp) ||
|
||||
appPermission.is_all_viewable ||
|
||||
appPermission.viewable_apps_id.includes(app?.id);
|
||||
let permissionGrant;
|
||||
const { user_permissions, app_group_permissions, workflow_group_permissions, super_admin, admin } = currentSession;
|
||||
|
||||
switch (action) {
|
||||
case 'create':
|
||||
permissionGrant = currentSession.user_permissions.app_create;
|
||||
break;
|
||||
case 'read':
|
||||
permissionGrant = this.isUserOwnerOfApp(user, app) || canReadApp;
|
||||
break;
|
||||
case 'update':
|
||||
permissionGrant = canUpdateApp || this.isUserOwnerOfApp(user, app);
|
||||
break;
|
||||
case 'delete':
|
||||
permissionGrant = currentSession.user_permissions.app_delete || this.isUserOwnerOfApp(user, app);
|
||||
break;
|
||||
default:
|
||||
permissionGrant = false;
|
||||
break;
|
||||
if (super_admin) return true;
|
||||
|
||||
if (this.props.appType === 'workflow') {
|
||||
const canCreateWorkflow = admin || user_permissions?.workflow_create;
|
||||
const canUpdateWorkflow =
|
||||
workflow_group_permissions?.is_all_editable ||
|
||||
workflow_group_permissions?.editable_workflows_id?.includes(app?.id);
|
||||
const canExecuteWorkflow =
|
||||
canUpdateWorkflow ||
|
||||
workflow_group_permissions?.is_all_executable ||
|
||||
workflow_group_permissions?.executable_workflows_id?.includes(app?.id);
|
||||
const canDeleteWorkflow = user_permissions?.workflow_delete || admin;
|
||||
|
||||
switch (action) {
|
||||
case 'create':
|
||||
return canCreateWorkflow;
|
||||
case 'read':
|
||||
return canCreateWorkflow || canUpdateWorkflow || canDeleteWorkflow || canExecuteWorkflow;
|
||||
case 'update':
|
||||
return canUpdateWorkflow;
|
||||
case 'delete':
|
||||
return canDeleteWorkflow;
|
||||
case 'view':
|
||||
return (
|
||||
canCreateWorkflow ||
|
||||
canUpdateWorkflow ||
|
||||
canDeleteWorkflow ||
|
||||
canExecuteWorkflow ||
|
||||
workflow_group_permissions?.editable_workflows_id?.length > 0 ||
|
||||
workflow_group_permissions?.executable_workflows_id?.length > 0
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const canUpdateApp =
|
||||
app_group_permissions &&
|
||||
(app_group_permissions.is_all_editable || app_group_permissions.editable_apps_id.includes(app?.id));
|
||||
const canReadApp =
|
||||
(app_group_permissions && canUpdateApp) ||
|
||||
app_group_permissions.is_all_viewable ||
|
||||
app_group_permissions.viewable_apps_id.includes(app?.id);
|
||||
|
||||
switch (action) {
|
||||
case 'create':
|
||||
return user_permissions.app_create;
|
||||
case 'read':
|
||||
return this.isUserOwnerOfApp(user, app) || canReadApp;
|
||||
case 'update':
|
||||
return canUpdateApp || this.isUserOwnerOfApp(user, app);
|
||||
case 'delete':
|
||||
return user_permissions.app_delete || this.isUserOwnerOfApp(user, app);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return permissionGrant;
|
||||
}
|
||||
|
||||
isUserOwnerOfApp(user, app) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const currentSessionSubject = new BehaviorSubject({
|
|||
group_permissions: null,
|
||||
app_group_permissions: null,
|
||||
data_source_group_permissions: null,
|
||||
workflow_group_permissions: null,
|
||||
role: null,
|
||||
organizations: [],
|
||||
isUserLoggingIn: false,
|
||||
|
|
|
|||
|
|
@ -10661,7 +10661,6 @@ tbody {
|
|||
.manage-groups-body {
|
||||
padding: 12px 12px 10px 12px;
|
||||
font-size: 12px;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 300px);
|
||||
|
||||
.group-users-list-container {
|
||||
|
|
@ -10882,6 +10881,7 @@ tbody {
|
|||
}
|
||||
|
||||
.permission-body {
|
||||
|
||||
.form-check {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,46 @@ import Select, { components } from 'react-select';
|
|||
import { FilterPreview } from '@/_components';
|
||||
import './appSelect.theme.scss';
|
||||
|
||||
export const RESOURCE_TYPE = {
|
||||
APPS: 'app',
|
||||
DATA_SOURCES: 'data_source',
|
||||
WORKFLOWS: 'workflow',
|
||||
};
|
||||
|
||||
export const getResourceTypeConfig = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return {
|
||||
placeholder: 'Select apps..',
|
||||
noOptionsMessage: 'No apps found',
|
||||
icon: 'apps',
|
||||
};
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return {
|
||||
placeholder: 'Select data sources..',
|
||||
noOptionsMessage: 'No data sources found',
|
||||
icon: 'datasource',
|
||||
};
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return {
|
||||
placeholder: 'Select workflows..',
|
||||
noOptionsMessage: 'No workflows found',
|
||||
icon: 'workflows',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
placeholder: 'Select resources..',
|
||||
noOptionsMessage: 'No resources found',
|
||||
icon: 'apps',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function AppsSelect(props) {
|
||||
const navigate = useNavigate();
|
||||
const workspaceId = getWorkspaceId();
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const resourceConfig = getResourceTypeConfig(props.resourceType);
|
||||
|
||||
//Will be used when workspace routing settings have been merged
|
||||
const Menu = (props) => {
|
||||
|
|
@ -188,8 +224,8 @@ export function AppsSelect(props) {
|
|||
}}
|
||||
options={[props.allowSelectAll ? props.allOption : null, ...props.options]}
|
||||
styles={selectStyles}
|
||||
placeholder={props.resourceType === 'Apps' ? 'Select apps..' : 'Select data sources..'}
|
||||
noOptionsMessage={() => 'No apps found'}
|
||||
placeholder={resourceConfig.placeholder}
|
||||
noOptionsMessage={() => resourceConfig.noOptionsMessage}
|
||||
/>
|
||||
// </div>
|
||||
);
|
||||
|
|
@ -201,4 +237,5 @@ AppsSelect.defaultProps = {
|
|||
value: '*',
|
||||
isAllField: true,
|
||||
},
|
||||
resourceType: RESOURCE_TYPE.APPS,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import AddResourcePermissionsMenu from './components/AddResourcePermissionsMenu'
|
|||
import { ConfirmDialog } from '@/_components';
|
||||
import AddEditResourcePermissionsModal from './components/AddEditResourceModal/AddEditResourcePermissionsModal';
|
||||
import DataSourceResourcePermissions from './components/DataSourceResourcePermission';
|
||||
import WorkflowResourcePermissions from './components/WorkflowResourcePermission';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
import { RESOURCE_TYPE, APP_TYPES, RESOURCE_NAME_MAPPING } from '../..';
|
||||
|
||||
class BaseManageGranularAccess extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -24,7 +26,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
errors: {},
|
||||
values: {},
|
||||
customSelected: true,
|
||||
selectedApps: [],
|
||||
selectedResources: [],
|
||||
type: null,
|
||||
newPermissionName: null,
|
||||
initialPermissionState: {
|
||||
|
|
@ -47,13 +49,11 @@ class BaseManageGranularAccess extends React.Component {
|
|||
updateType: '',
|
||||
deleteConfirmationModal: false,
|
||||
deletingPermissions: false,
|
||||
|
||||
initialPermissionStateDs: {
|
||||
canUse: false,
|
||||
canView: false,
|
||||
},
|
||||
selectedDs: [],
|
||||
resourceType: '',
|
||||
resourceType: null,
|
||||
hasChanges: false,
|
||||
initialState: {
|
||||
type: 'app',
|
||||
|
|
@ -66,8 +66,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
canUse: false,
|
||||
canConfigure: false,
|
||||
},
|
||||
selectedDs: [],
|
||||
selectedApps: [],
|
||||
selectedResources: [],
|
||||
isAll: true,
|
||||
newPermissionName: null,
|
||||
},
|
||||
|
|
@ -86,15 +85,27 @@ class BaseManageGranularAccess extends React.Component {
|
|||
groupPermissionV2Service
|
||||
.fetchAddableApps()
|
||||
.then((data) => {
|
||||
const addableApps = data.map((app) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
});
|
||||
const addableApps = data
|
||||
.filter((app) => app.type === APP_TYPES.FRONT_END)
|
||||
.map((app) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
});
|
||||
const addableWorkflows = data
|
||||
.filter((app) => app.type === APP_TYPES.WORKFLOW)
|
||||
.map((app) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
});
|
||||
this.setState({
|
||||
addableApps,
|
||||
addableWorkflows,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -137,19 +148,15 @@ class BaseManageGranularAccess extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
getSelectedResources = () => {
|
||||
return this.state.selectedResources;
|
||||
};
|
||||
|
||||
createGranularPermissions = () => {
|
||||
const {
|
||||
initialPermissionState,
|
||||
initialPermissionStateDs,
|
||||
isAll,
|
||||
newPermissionName,
|
||||
isCustom,
|
||||
selectedApps,
|
||||
selectedDs,
|
||||
resourceType,
|
||||
} = this.state;
|
||||
const type = resourceType === 'Apps' ? 'app' : 'data_source';
|
||||
const selectedResource = type == 'app' ? selectedApps : selectedDs;
|
||||
const { initialPermissionState, initialPermissionStateDs, isAll, newPermissionName, isCustom, resourceType } =
|
||||
this.state;
|
||||
const type = resourceType;
|
||||
const selectedResource = this.getSelectedResources();
|
||||
if (isCustom && selectedResource.length == 0) {
|
||||
toast.error('Please select the resources to continue');
|
||||
return;
|
||||
|
|
@ -157,7 +164,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
const resourcesToAdd = selectedResource
|
||||
.filter((res) => !res?.isAllField)
|
||||
.map((option) => {
|
||||
if (type === 'app') {
|
||||
if (type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS) {
|
||||
return {
|
||||
appId: option.value,
|
||||
};
|
||||
|
|
@ -173,8 +180,8 @@ class BaseManageGranularAccess extends React.Component {
|
|||
groupId: this.props.groupPermissionId,
|
||||
isAll: isAll,
|
||||
createResourcePermissionObject: {
|
||||
...(type == 'app' && initialPermissionState),
|
||||
...(type == 'data_source' && { action: initialPermissionStateDs }),
|
||||
...((type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS) && initialPermissionState),
|
||||
...(type == RESOURCE_TYPE.DATA_SOURCES && { action: initialPermissionStateDs }),
|
||||
resourcesToAdd: resourcesToAdd,
|
||||
},
|
||||
};
|
||||
|
|
@ -226,8 +233,8 @@ class BaseManageGranularAccess extends React.Component {
|
|||
canUse: dataSourcesGroupPermission?.canUse,
|
||||
canConfigure: dataSourcesGroupPermission?.canConfigure,
|
||||
},
|
||||
resourceType: 'Data sources',
|
||||
selectedDs:
|
||||
resourceType: RESOURCE_TYPE.DATA_SOURCES,
|
||||
selectedResources:
|
||||
currentDs?.length > 0
|
||||
? currentDs?.map(({ dataSource }) => {
|
||||
return {
|
||||
|
|
@ -238,14 +245,14 @@ class BaseManageGranularAccess extends React.Component {
|
|||
})
|
||||
: [],
|
||||
initialState: {
|
||||
type: 'data_source',
|
||||
type: RESOURCE_TYPE.DATA_SOURCES,
|
||||
initialPermissionStateDs: {
|
||||
canUse: dataSourcesGroupPermission?.canUse,
|
||||
canConfigure: dataSourcesGroupPermission?.canConfigure,
|
||||
},
|
||||
isAll: !!granularPermission.isAll,
|
||||
newPermissionName: granularPermission?.name,
|
||||
selectedDs:
|
||||
selectedResources:
|
||||
currentDs?.length > 0
|
||||
? currentDs?.map(({ dataSource }) => {
|
||||
return {
|
||||
|
|
@ -257,30 +264,32 @@ class BaseManageGranularAccess extends React.Component {
|
|||
: [],
|
||||
},
|
||||
});
|
||||
} else if (granularPermission.type === 'app') {
|
||||
} else if (granularPermission.type === RESOURCE_TYPE.APPS || granularPermission.type === RESOURCE_TYPE.WORKFLOWS) {
|
||||
const currentApps = granularPermission?.appsGroupPermissions?.groupApps;
|
||||
const appsGroupPermission = granularPermission?.appsGroupPermissions;
|
||||
const selectedResources =
|
||||
currentApps?.length > 0
|
||||
? currentApps?.map(({ app }) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
this.setState({
|
||||
...fixedState,
|
||||
modalTitle: `Edit app permissions`,
|
||||
resourceType: 'Apps',
|
||||
modalTitle: `Edit ${granularPermission.type} permissions`,
|
||||
resourceType: granularPermission.type,
|
||||
initialPermissionState: {
|
||||
canEdit: appsGroupPermission.canEdit,
|
||||
canView: appsGroupPermission.canView,
|
||||
hideFromDashboard: appsGroupPermission.hideFromDashboard,
|
||||
},
|
||||
selectedApps:
|
||||
currentApps?.length > 0
|
||||
? currentApps?.map(({ app }) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
selectedResources: selectedResources,
|
||||
initialState: {
|
||||
type: 'app',
|
||||
type: granularPermission.type,
|
||||
initialPermissionState: {
|
||||
canEdit: appsGroupPermission?.canEdit,
|
||||
canView: appsGroupPermission?.canView,
|
||||
|
|
@ -288,21 +297,68 @@ class BaseManageGranularAccess extends React.Component {
|
|||
},
|
||||
isAll: !!granularPermission.isAll,
|
||||
newPermissionName: granularPermission?.name,
|
||||
selectedApps:
|
||||
currentApps?.length > 0
|
||||
? currentApps?.map(({ app }) => {
|
||||
return {
|
||||
name: app.name,
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
selectedResources: selectedResources,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderResourcePermissions = (props) => {
|
||||
const { permissions, currentGroupPermission, isBasicPlan, index } = props;
|
||||
const { type } = permissions;
|
||||
|
||||
switch (type) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return (
|
||||
<AppResourcePermissions
|
||||
updateOnlyGranularPermissions={this.updateOnlyGranularPermissions}
|
||||
permissions={permissions}
|
||||
currentGroupPermission={currentGroupPermission}
|
||||
openEditPermissionModal={this.openEditPermissionModal}
|
||||
isBasicPlan={isBasicPlan}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return (
|
||||
<DataSourceResourcePermissions
|
||||
updateOnlyGranularPermissions={this.updateOnlyGranularPermissions}
|
||||
permissions={permissions}
|
||||
currentGroupPermission={currentGroupPermission}
|
||||
openEditPermissionModal={this.openEditPermissionModal}
|
||||
isBasicPlan={isBasicPlan}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return (
|
||||
<WorkflowResourcePermissions
|
||||
updateOnlyGranularPermissions={this.updateOnlyGranularPermissions}
|
||||
permissions={permissions}
|
||||
currentGroupPermission={currentGroupPermission}
|
||||
openEditPermissionModal={this.openEditPermissionModal}
|
||||
isBasicPlan={isBasicPlan}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
getAddableResources = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return this.state.addableApps;
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return this.state.addableDs;
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return this.state.addableWorkflows;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
updateOnlyGranularPermissions = (permission, actions = {}, allowRoleChange) => {
|
||||
const body = {
|
||||
actions: actions,
|
||||
|
|
@ -340,32 +396,25 @@ class BaseManageGranularAccess extends React.Component {
|
|||
};
|
||||
|
||||
updateGranularPermissions = (allowRoleChange) => {
|
||||
const {
|
||||
currentEditingPermissions,
|
||||
selectedApps,
|
||||
selectedDs,
|
||||
newPermissionName,
|
||||
isAll,
|
||||
initialPermissionState,
|
||||
initialPermissionStateDs,
|
||||
} = this.state;
|
||||
const { currentEditingPermissions, newPermissionName, isAll, initialPermissionState, initialPermissionStateDs } =
|
||||
this.state;
|
||||
const type = currentEditingPermissions.type;
|
||||
const currentResource =
|
||||
type === 'app'
|
||||
type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS
|
||||
? currentEditingPermissions?.appsGroupPermissions?.groupApps?.map((app) => {
|
||||
return app.app.id;
|
||||
})
|
||||
: currentEditingPermissions?.dataSourcesGroupPermission?.groupDataSources?.map((ds) => {
|
||||
return ds.dataSource.id;
|
||||
});
|
||||
const selectedResourceEnitities = type === 'app' ? selectedApps : selectedDs;
|
||||
const selectedResourceEnitities = this.getSelectedResources();
|
||||
const selectedResource = selectedResourceEnitities
|
||||
.filter((res) => !res?.isAllField)
|
||||
?.map((resource) => resource.value);
|
||||
const resourcesToAdd = selectedResource
|
||||
?.filter((item) => !currentResource.includes(item))
|
||||
.map((id) => {
|
||||
if (type === 'app')
|
||||
if (type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS)
|
||||
return {
|
||||
appId: id,
|
||||
};
|
||||
|
|
@ -377,7 +426,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
});
|
||||
const resourceItemsToDelete = currentResource?.filter((item) => !selectedResource?.includes(item));
|
||||
const groupResToDelete =
|
||||
type === 'app'
|
||||
type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS
|
||||
? currentEditingPermissions?.appsGroupPermissions?.groupApps?.filter((groupApp) =>
|
||||
resourceItemsToDelete?.includes(groupApp.appId)
|
||||
)
|
||||
|
|
@ -392,7 +441,10 @@ class BaseManageGranularAccess extends React.Component {
|
|||
const body = {
|
||||
name: newPermissionName,
|
||||
isAll: isAll,
|
||||
actions: type === 'app' ? initialPermissionState : initialPermissionStateDs,
|
||||
actions:
|
||||
type === RESOURCE_TYPE.APPS || type === RESOURCE_TYPE.WORKFLOWS
|
||||
? initialPermissionState
|
||||
: initialPermissionStateDs,
|
||||
resourcesToAdd,
|
||||
resourcesToDelete,
|
||||
allowRoleChange,
|
||||
|
|
@ -457,7 +509,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
|
||||
openAddPermissionModal = (resourceType) => {
|
||||
this.setState((prevState) => ({
|
||||
modalTitle: `Add ${resourceType?.toLowerCase()} permissions`,
|
||||
modalTitle: `Add ${RESOURCE_NAME_MAPPING[resourceType].toLowerCase()} permissions`,
|
||||
resourceType,
|
||||
showAddPermissionModal: true,
|
||||
initialPermissionState: { ...prevState.initialPermissionState, canView: true },
|
||||
|
|
@ -484,22 +536,14 @@ class BaseManageGranularAccess extends React.Component {
|
|||
canUse: false,
|
||||
canConfigure: false,
|
||||
},
|
||||
selectedDs: [],
|
||||
selectedApps: [],
|
||||
selectedResources: [],
|
||||
resourceType: '',
|
||||
hasChanges: false,
|
||||
});
|
||||
};
|
||||
|
||||
setSelectedApps = (values) => {
|
||||
this.setState({ selectedApps: values }, () => {
|
||||
const hasChanges = this.hasStateChanged(this.state);
|
||||
this.setState({ hasChanges });
|
||||
});
|
||||
};
|
||||
|
||||
setSelectedDs = (values) => {
|
||||
this.setState({ selectedDs: values }, () => {
|
||||
setSelectedResources = (values) => {
|
||||
this.setState({ selectedResources: values }, () => {
|
||||
const hasChanges = this.hasStateChanged(this.state);
|
||||
this.setState({ hasChanges });
|
||||
});
|
||||
|
|
@ -525,15 +569,13 @@ class BaseManageGranularAccess extends React.Component {
|
|||
hasStateChanged = (newState) => {
|
||||
const { type } = this.state.initialState;
|
||||
|
||||
const selectedItems =
|
||||
type === 'data_source' ? this.state.initialState?.selectedDs : this.state.initialState?.selectedApps;
|
||||
|
||||
const newSelectedItems = type === 'data_source' ? newState.selectedDs : newState.selectedApps;
|
||||
const selectedItems = this.state.initialState?.selectedResources;
|
||||
const newSelectedItems = newState.selectedResources;
|
||||
const newPermissionState =
|
||||
type === 'data_source' ? newState.initialPermissionStateDs : newState.initialPermissionState;
|
||||
type === RESOURCE_TYPE.DATA_SOURCES ? newState.initialPermissionStateDs : newState.initialPermissionState;
|
||||
|
||||
const permissionStateChanged =
|
||||
type === 'data_source'
|
||||
type === RESOURCE_TYPE.DATA_SOURCES
|
||||
? this.state.initialState.initialPermissionStateDs?.canUse !== newPermissionState?.canUse ||
|
||||
this.state.initialPermissionStateDs?.canConfigure !== newPermissionState?.canConfigure
|
||||
: this.state.initialState.initialPermissionState?.canEdit !== newPermissionState?.canEdit ||
|
||||
|
|
@ -574,11 +616,9 @@ class BaseManageGranularAccess extends React.Component {
|
|||
render() {
|
||||
const {
|
||||
showAddPermissionModal,
|
||||
selectedApps,
|
||||
isCustom,
|
||||
granularPermissions,
|
||||
isLoading,
|
||||
addableApps,
|
||||
modalTitle,
|
||||
modalType,
|
||||
newPermissionName,
|
||||
|
|
@ -589,7 +629,6 @@ class BaseManageGranularAccess extends React.Component {
|
|||
deleteConfirmationModal,
|
||||
deletingPermissions,
|
||||
resourceType,
|
||||
selectedDs,
|
||||
hasChanges,
|
||||
} = this.state;
|
||||
|
||||
|
|
@ -601,7 +640,7 @@ class BaseManageGranularAccess extends React.Component {
|
|||
const showPermissionInfo = currentGroupPermission.name == 'admin' || currentGroupPermission.name == 'end-user';
|
||||
const addPermissionTooltipMessage = !newPermissionName
|
||||
? 'Please input permissions name'
|
||||
: isCustom && selectedApps.length === 0
|
||||
: isCustom && this.getSelectedResources().length === 0
|
||||
? 'Please select apps or select all apps option'
|
||||
: '';
|
||||
const isBasicPlan = this.props.isBasicPlan;
|
||||
|
|
@ -632,63 +671,73 @@ class BaseManageGranularAccess extends React.Component {
|
|||
darkMode={this.props.darkMode}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<AddEditResourcePermissionsModal
|
||||
handleClose={this.closeAddPermissionModal}
|
||||
handleConfirm={
|
||||
modalType === 'add'
|
||||
? this.createGranularPermissions
|
||||
: () => {
|
||||
this.updateGranularPermissions();
|
||||
}
|
||||
}
|
||||
updateParentState={this.updateState}
|
||||
resourceType={resourceType}
|
||||
currentState={this.state}
|
||||
show={showAddPermissionModal}
|
||||
title={
|
||||
<div className="my-3 permission-manager-title" data-cy="modal-title">
|
||||
<span className="font-weight-500">
|
||||
<SolidIcon name={resourceType == 'Apps' ? 'apps' : 'datasource'} fill="var(--slate8)" />
|
||||
</span>
|
||||
<div className="tj-text-md font-weight-500 modal-name" data-cy="modal-title">
|
||||
{modalTitle}
|
||||
</div>
|
||||
{modalType === 'edit' && !isRoleGroup && (
|
||||
<div className="delete-icon-cont">
|
||||
<ButtonSolid
|
||||
leftIcon="delete"
|
||||
iconWidth="15px"
|
||||
className="icon-class"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
deleteConfirmationModal: true,
|
||||
showAddPermissionModal: false,
|
||||
});
|
||||
}}
|
||||
data-cy="delete-button"
|
||||
{showAddPermissionModal && (
|
||||
<AddEditResourcePermissionsModal
|
||||
handleClose={this.closeAddPermissionModal}
|
||||
handleConfirm={
|
||||
modalType === 'add'
|
||||
? this.createGranularPermissions
|
||||
: () => {
|
||||
this.updateGranularPermissions();
|
||||
}
|
||||
}
|
||||
updateParentState={this.updateState}
|
||||
resourceType={resourceType}
|
||||
currentState={this.state}
|
||||
show={showAddPermissionModal}
|
||||
title={
|
||||
<div className="my-3 permission-manager-title" data-cy="modal-title">
|
||||
<span className="font-weight-500">
|
||||
<SolidIcon
|
||||
name={
|
||||
resourceType === RESOURCE_TYPE.APPS
|
||||
? 'apps'
|
||||
: resourceType === RESOURCE_TYPE.WORKFLOWS
|
||||
? 'workflows'
|
||||
: 'datasource'
|
||||
}
|
||||
fill="var(--slate8)"
|
||||
/>
|
||||
</span>
|
||||
<div className="tj-text-md font-weight-500 modal-name" data-cy="modal-title">
|
||||
{modalTitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
confirmBtnProps={{
|
||||
title: `${modalType === 'edit' ? 'Update' : 'Add'}`,
|
||||
iconLeft: 'plus',
|
||||
disabled:
|
||||
(modalType === 'add' && !newPermissionName) ||
|
||||
(modalType === 'edit' && !hasChanges) ||
|
||||
(isCustom && selectedApps.length === 0 && resourceType === 'Apps') ||
|
||||
(isCustom && selectedDs.length === 0 && resourceType === 'Data Sources'),
|
||||
tooltipMessage: addPermissionTooltipMessage,
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableEditUpdate}
|
||||
selectedApps={resourceType === 'Apps' ? selectedApps : selectedDs}
|
||||
setSelectedApps={resourceType === 'Apps' ? this.setSelectedApps : this.setSelectedDs}
|
||||
addableApps={resourceType === 'Apps' ? addableApps : addableDs}
|
||||
darkMode={this.props.darkMode}
|
||||
groupName={currentGroupPermission.name}
|
||||
/>
|
||||
{modalType === 'edit' && !isRoleGroup && (
|
||||
<div className="delete-icon-cont">
|
||||
<ButtonSolid
|
||||
leftIcon="delete"
|
||||
iconWidth="15px"
|
||||
className="icon-class"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
deleteConfirmationModal: true,
|
||||
showAddPermissionModal: false,
|
||||
});
|
||||
}}
|
||||
data-cy="delete-button"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
confirmBtnProps={{
|
||||
title: `${modalType === 'edit' ? 'Update' : 'Add'}`,
|
||||
iconLeft: 'plus',
|
||||
disabled:
|
||||
(modalType === 'add' && !newPermissionName) ||
|
||||
(modalType === 'edit' && !hasChanges) ||
|
||||
(isCustom && this.getSelectedResources().length === 0),
|
||||
tooltipMessage: addPermissionTooltipMessage,
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableEditUpdate}
|
||||
selectedApps={this.getSelectedResources()}
|
||||
setSelectedApps={(values) => this.setSelectedResources(values)}
|
||||
addableApps={this.getAddableResources(resourceType)}
|
||||
darkMode={this.props.darkMode}
|
||||
groupName={currentGroupPermission.name}
|
||||
/>
|
||||
)}
|
||||
{!granularPermissions.length && !isLoading ? (
|
||||
<div className="empty-container">
|
||||
<div className="icon-container" data-cy="empty-page-svg">
|
||||
|
|
@ -735,28 +784,12 @@ class BaseManageGranularAccess extends React.Component {
|
|||
) : (
|
||||
<>
|
||||
{granularPermissions.map((permissions, index) => {
|
||||
if (permissions.type === 'app')
|
||||
return (
|
||||
<AppResourcePermissions
|
||||
updateOnlyGranularPermissions={this.updateOnlyGranularPermissions}
|
||||
permissions={permissions}
|
||||
currentGroupPermission={currentGroupPermission}
|
||||
openEditPermissionModal={this.openEditPermissionModal}
|
||||
isBasicPlan={isBasicPlan}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<DataSourceResourcePermissions
|
||||
updateOnlyGranularPermissions={this.updateOnlyGranularPermissions}
|
||||
permissions={permissions}
|
||||
currentGroupPermission={currentGroupPermission}
|
||||
openEditPermissionModal={this.openEditPermissionModal}
|
||||
isBasicPlan={isBasicPlan}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
return this.renderResourcePermissions({
|
||||
permissions,
|
||||
currentGroupPermission,
|
||||
isBasicPlan,
|
||||
index,
|
||||
});
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { AppsSelect } from '@/_ui/Modal/AppsSelect';
|
|||
import AppPermissionsActions from './AppPermissionActionContainer';
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import DsPermissionsActions from './DataSourcPermissionActionContainer';
|
||||
import WorkflowPermissionsActions from './WorkflowPermissionActionContainer';
|
||||
import { RESOURCE_TYPE } from '../../../../index';
|
||||
|
||||
function AddEditResourcePermissionsModal({
|
||||
handleClose,
|
||||
|
|
@ -28,11 +30,138 @@ function AddEditResourcePermissionsModal({
|
|||
const initialPermissionStateDs = currentState?.initialPermissionStateDs;
|
||||
const errors = currentState?.errors;
|
||||
const isAll = currentState?.isAll;
|
||||
const allResourceText =
|
||||
resourceType === 'Apps'
|
||||
? 'This will select all apps in the workspace including any new apps created'
|
||||
: 'This will select all data sources in the workspace including any new connections created';
|
||||
const allResourceTitle = resourceType === 'Apps' ? 'All apps' : 'All data sources';
|
||||
const getAllResourceText = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return 'This will select all apps in the workspace including any new apps created';
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return 'This will select all workflows in the workspace including any new workflows created';
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return 'This will select all data sources in the workspace including any new connections created';
|
||||
}
|
||||
};
|
||||
|
||||
const RESOURCE_NAME_MAPPING = {
|
||||
[RESOURCE_TYPE.APPS]: 'apps',
|
||||
[RESOURCE_TYPE.WORKFLOWS]: 'workflows',
|
||||
[RESOURCE_TYPE.DATA_SOURCES]: 'data sources',
|
||||
};
|
||||
|
||||
const getAllResourceLabel = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return 'All apps';
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return 'All workflows';
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return 'All data sources';
|
||||
default:
|
||||
return 'All resources';
|
||||
}
|
||||
};
|
||||
|
||||
const renderPermissionActions = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return (
|
||||
<AppPermissionsActions
|
||||
handleClickEdit={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canEdit: !prevState.initialPermissionState.canEdit,
|
||||
canView: prevState.initialPermissionState.canEdit,
|
||||
...(!prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleClickView={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canView: !prevState.initialPermissionState.canView,
|
||||
canEdit: prevState.initialPermissionState.canView,
|
||||
...(prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleHideFromDashboard={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
hideFromDashboard: !prevState.initialPermissionState.hideFromDashboard,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableBuilderLevelUpdate}
|
||||
initialPermissionState={initialPermissionState}
|
||||
/>
|
||||
);
|
||||
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return (
|
||||
<WorkflowPermissionsActions
|
||||
handleClickEdit={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canEdit: !prevState.initialPermissionState.canEdit,
|
||||
canView: prevState.initialPermissionState.canEdit,
|
||||
...(!prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleClickView={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canView: !prevState.initialPermissionState.canView,
|
||||
canEdit: prevState.initialPermissionState.canView,
|
||||
...(prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleHideFromDashboard={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
hideFromDashboard: !prevState.initialPermissionState.hideFromDashboard,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableBuilderLevelUpdate}
|
||||
initialPermissionState={initialPermissionState}
|
||||
/>
|
||||
);
|
||||
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
default:
|
||||
return (
|
||||
<DsPermissionsActions
|
||||
handleClickConfigure={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionStateDs: {
|
||||
...prevState.initialPermissionStateDs,
|
||||
canConfigure: !prevState.initialPermissionStateDs.canConfigure,
|
||||
canUse: !!prevState.initialPermissionStateDs.canConfigure,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleClickUse={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionStateDs: {
|
||||
...prevState.initialPermissionStateDs,
|
||||
canUse: !prevState.initialPermissionStateDs.canUse,
|
||||
canConfigure: !!prevState.initialPermissionStateDs.canUse,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableBuilderLevelUpdate}
|
||||
initialPermissionStateDs={initialPermissionStateDs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModalBase
|
||||
size="md"
|
||||
|
|
@ -79,46 +208,7 @@ function AddEditResourcePermissionsModal({
|
|||
<label className="form-label bold-text" data-cy="permission-label">
|
||||
Permission
|
||||
</label>
|
||||
{resourceType === 'Apps' ? (
|
||||
<AppPermissionsActions
|
||||
handleClickEdit={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canEdit: !prevState.initialPermissionState.canEdit,
|
||||
canView: prevState.initialPermissionState.canEdit,
|
||||
...(!prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleClickView={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...prevState.initialPermissionState,
|
||||
canView: !prevState.initialPermissionState.canView,
|
||||
canEdit: prevState.initialPermissionState.canView,
|
||||
...(prevState.initialPermissionState.canEdit && { hideFromDashboard: false }),
|
||||
},
|
||||
}));
|
||||
}}
|
||||
handleHideFromDashboard={() => {
|
||||
updateParentState((prevState) => ({
|
||||
initialPermissionState: {
|
||||
...initialPermissionState,
|
||||
hideFromDashboard: !prevState.initialPermissionState.hideFromDashboard,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
disableBuilderLevelUpdate={disableBuilderLevelUpdate}
|
||||
initialPermissionState={initialPermissionState}
|
||||
/>
|
||||
) : (
|
||||
<DsPermissionsActions
|
||||
updateParentState={updateParentState}
|
||||
disableBuilderLevelUpdate={disableBuilderLevelUpdate}
|
||||
initialPermissionStateDs={initialPermissionStateDs}
|
||||
/>
|
||||
)}
|
||||
{renderPermissionActions(resourceType)}
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
|
|
@ -139,15 +229,15 @@ function AddEditResourcePermissionsModal({
|
|||
<div>
|
||||
<span
|
||||
className="form-check-label"
|
||||
data-cy={`${allResourceTitle.toLowerCase().replace(/\s+/g, '-')}-label`}
|
||||
data-cy={`${getAllResourceLabel(resourceType).toLowerCase().replace(/\s+/g, '-')}-label`}
|
||||
>
|
||||
{allResourceTitle}
|
||||
{getAllResourceLabel(resourceType)}
|
||||
</span>
|
||||
<span
|
||||
className="tj-text-xsm"
|
||||
data-cy={`${allResourceTitle.toLowerCase().replace(/\s+/g, '-')}-info-text`}
|
||||
data-cy={`${getAllResourceText(resourceType).toLowerCase().replace(/\s+/g, '-')}-info-text`}
|
||||
>
|
||||
{allResourceText}
|
||||
{getAllResourceText(resourceType)}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
|
@ -167,7 +257,9 @@ function AddEditResourcePermissionsModal({
|
|||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
disabled={addableApps.length === 0 || disableBuilderLevelUpdate || groupName === 'builder'}
|
||||
disabled={
|
||||
!addableApps || addableApps?.length === 0 || disableBuilderLevelUpdate || groupName === 'builder'
|
||||
}
|
||||
checked={isCustom}
|
||||
onClick={() => {
|
||||
!isCustom &&
|
||||
|
|
@ -188,8 +280,7 @@ function AddEditResourcePermissionsModal({
|
|||
style={{ color: disableBuilderLevelUpdate ? 'var(--text-disabled)' : '' }}
|
||||
data-cy="custom-info-text"
|
||||
>
|
||||
Select specific {resourceType === 'Apps' ? 'applications' : 'data sources'} you want to add to the
|
||||
group
|
||||
Select specific {RESOURCE_NAME_MAPPING[resourceType]} you want to add to the group
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
function WorkflowPermissionActionContainer() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default withEditionSpecificComponent(WorkflowPermissionActionContainer, 'WorkspaceSettings');
|
||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import '../../../resources/styles/group-permissions.styles.scss';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import { RESOURCE_TYPE } from '../../../index';
|
||||
|
||||
function AddResourcePermissionsMenu({
|
||||
openAddPermissionModal,
|
||||
|
|
@ -10,6 +11,25 @@ function AddResourcePermissionsMenu({
|
|||
darkMode,
|
||||
isBasicPlan,
|
||||
}) {
|
||||
const selectResourceIcon = (resourceType) => {
|
||||
switch (resourceType) {
|
||||
case RESOURCE_TYPE.APPS:
|
||||
return 'apps';
|
||||
case RESOURCE_TYPE.WORKFLOWS:
|
||||
return 'workflows';
|
||||
case RESOURCE_TYPE.DATA_SOURCES:
|
||||
return 'datasource';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const resourceNameMapping = {
|
||||
[RESOURCE_TYPE.APPS]: 'Apps',
|
||||
[RESOURCE_TYPE.WORKFLOWS]: 'Workflows',
|
||||
[RESOURCE_TYPE.DATA_SOURCES]: 'Data source',
|
||||
};
|
||||
|
||||
return resourcesOptions.length > 1 ? (
|
||||
<OverlayTrigger
|
||||
rootClose={true}
|
||||
|
|
@ -25,17 +45,17 @@ function AddResourcePermissionsMenu({
|
|||
iconWidth="17"
|
||||
fill="var(--slate9)"
|
||||
className="apps-remove-btn permission-type remove-decoration tj-text-xsm font-weight-600 remove-disabled-bg"
|
||||
leftIcon={resource === 'Apps' ? 'apps' : 'datasource'}
|
||||
leftIcon={selectResourceIcon(resource)}
|
||||
onClick={() => {
|
||||
openAddPermissionModal(resource);
|
||||
}}
|
||||
disabled={currentGroupPermission.name === 'end-user' && resource === 'Data Sources'}
|
||||
disabled={currentGroupPermission.name === 'end-user' && resource === RESOURCE_TYPE.DATA_SOURCES}
|
||||
>
|
||||
<OverlayTrigger
|
||||
key={index}
|
||||
placement="right"
|
||||
overlay={
|
||||
currentGroupPermission.name === 'end-user' && resource === 'Data Sources' ? (
|
||||
currentGroupPermission.name === 'end-user' && resource === RESOURCE_TYPE.DATA_SOURCES ? (
|
||||
<Tooltip id={`tooltip-${index}`} style={{ maxWidth: '120px' }}>
|
||||
End-user cannot access data sources
|
||||
</Tooltip>
|
||||
|
|
@ -44,7 +64,7 @@ function AddResourcePermissionsMenu({
|
|||
)
|
||||
}
|
||||
>
|
||||
<span>{resource === 'Data Sources' ? 'Data source' : resource}</span>
|
||||
<span>{resourceNameMapping[resource]}</span>
|
||||
</OverlayTrigger>
|
||||
</ButtonSolid>
|
||||
))}
|
||||
|
|
@ -75,7 +95,7 @@ function AddResourcePermissionsMenu({
|
|||
leftIcon="plus"
|
||||
disabled={currentGroupPermission.name === 'admin' || isBasicPlan}
|
||||
onClick={() => {
|
||||
openAddPermissionModal('Apps');
|
||||
openAddPermissionModal(RESOURCE_TYPE.APPS);
|
||||
}}
|
||||
data-cy="add-apps-buton"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
function WorkflowResourcePermissions() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default withEditionSpecificComponent(WorkflowResourcePermissions, 'WorkspaceSettings');
|
||||
|
|
@ -21,6 +21,7 @@ import ChangeRoleModal from '../ChangeRoleModal';
|
|||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import Avatar from '@/_ui/Avatar';
|
||||
import DataSourcePermissionsUI from '../DataSourcePermissionsUI';
|
||||
import WorkflowPermissionsUI from '../WorkflowPermissionsUI';
|
||||
|
||||
class BaseManageGroupPermissionResources extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -876,7 +877,7 @@ class BaseManageGroupPermissionResources extends React.Component {
|
|||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="permission-body">
|
||||
<div className={`${showPermissionInfo ? 'permissions-body-one' : 'permissions-body-two'}`}>
|
||||
{isLoadingGroup ? (
|
||||
<tr>
|
||||
<td className="col-auto">
|
||||
|
|
@ -958,6 +959,13 @@ class BaseManageGroupPermissionResources extends React.Component {
|
|||
</div>
|
||||
{/* //App till here */}
|
||||
</div>
|
||||
{/* Worklfow Permission */}
|
||||
<WorkflowPermissionsUI
|
||||
groupPermission={groupPermission}
|
||||
disablePermissionUpdate={disablePermissionUpdate}
|
||||
updateGroupPermission={this.updateGroupPermission}
|
||||
updateState={this.updateParamState}
|
||||
/>
|
||||
|
||||
{/* Data source */}
|
||||
<DataSourcePermissionsUI
|
||||
|
|
|
|||
|
|
@ -141,7 +141,21 @@
|
|||
}
|
||||
|
||||
|
||||
.permission-body {
|
||||
.permissions-body-one {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 320px - 100px);
|
||||
min-height: calc(100vh - 320px - 100px);
|
||||
|
||||
.tj-text-xxsm {
|
||||
color: var(--slate11)
|
||||
}
|
||||
}
|
||||
|
||||
.permissions-body-two {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 260px - 100px);
|
||||
min-height: calc(100vh - 260px - 100px);
|
||||
|
||||
.tj-text-xxsm {
|
||||
color: var(--slate11)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -827,6 +827,7 @@ class BaseManageGroupPermissions extends React.Component {
|
|||
value: group.name,
|
||||
};
|
||||
})}
|
||||
workflowEnabled={false}
|
||||
featureAccess={featureAccess}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
const WorkflowPermissionsUI = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default withEditionSpecificComponent(WorkflowPermissionsUI, 'WorkspaceSettings');
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './WorkflowPermissionsUI.jsx';
|
||||
|
|
@ -1 +1,18 @@
|
|||
export { default } from './ManageGroupPermissionsPage';
|
||||
|
||||
export const RESOURCE_TYPE = {
|
||||
APPS: 'app',
|
||||
DATA_SOURCES: 'data_source',
|
||||
WORKFLOWS: 'workflow',
|
||||
};
|
||||
|
||||
export const APP_TYPES = {
|
||||
FRONT_END: 'front-end',
|
||||
WORKFLOW: 'workflow',
|
||||
};
|
||||
|
||||
export const RESOURCE_NAME_MAPPING = {
|
||||
[RESOURCE_TYPE.APPS]: 'Apps',
|
||||
[RESOURCE_TYPE.DATA_SOURCES]: 'Data Sources',
|
||||
[RESOURCE_TYPE.WORKFLOWS]: 'Workflows',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkflowPermissionsInGroupPermissions1746705301652 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE permission_groups
|
||||
ADD COLUMN workflow_create BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN workflow_delete BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE permission_groups
|
||||
DROP COLUMN workflow_delete,
|
||||
DROP COLUMN workflow_create;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkflowTypeInResourceType1746705371665 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TYPE "resource_type" ADD VALUE IF NOT EXISTS 'workflow';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAppTypeInAppsGroupPermissions1746705448788 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'app_type') THEN
|
||||
CREATE TYPE "app_type" AS ENUM ('front-end', 'workflow');
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
ALTER TABLE "apps_group_permissions"
|
||||
ADD COLUMN "app_type" "app_type" NOT NULL DEFAULT 'front-end';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "apps_group_permissions"
|
||||
DROP COLUMN "app_type";
|
||||
DROP TYPE IF EXISTS "app_type";
|
||||
`);
|
||||
}
|
||||
}
|
||||
2
server/package-lock.json
generated
2
server/package-lock.json
generated
|
|
@ -20645,4 +20645,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,20 @@ import { GroupApps } from './group_apps.entity';
|
|||
import { AppGroupPermission } from './app_group_permission.entity';
|
||||
import { AiConversation } from './ai_conversation.entity';
|
||||
import { Organization } from './organization.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Entity({ name: 'apps' })
|
||||
export class App extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'type' })
|
||||
type: string = 'front-end';
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'enum',
|
||||
enum: APP_TYPES,
|
||||
default: APP_TYPES.FRONT_END,
|
||||
})
|
||||
type: APP_TYPES;
|
||||
|
||||
@Column({ name: 'name' })
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { User } from './user.entity';
|
|||
import { AppVersion } from './app_version.entity';
|
||||
import { GroupPermission } from './group_permission.entity';
|
||||
import { AppGroupPermission } from './app_group_permission.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Entity({ name: 'apps' })
|
||||
export class AppBase extends BaseEntity {
|
||||
|
|
@ -24,8 +25,13 @@ export class AppBase extends BaseEntity {
|
|||
@Column({ name: 'name' })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'type' })
|
||||
type: string = 'front-end';
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'enum',
|
||||
enum: APP_TYPES,
|
||||
default: APP_TYPES.FRONT_END,
|
||||
})
|
||||
type: APP_TYPES;
|
||||
|
||||
@Column({ name: 'slug', unique: true })
|
||||
slug: string;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from 'typeorm';
|
||||
import { GranularPermissions } from './granular_permissions.entity';
|
||||
import { GroupApps } from './group_apps.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Entity({ name: 'apps_group_permissions' })
|
||||
export class AppsGroupPermissions extends BaseEntity {
|
||||
|
|
@ -22,6 +23,13 @@ export class AppsGroupPermissions extends BaseEntity {
|
|||
@Column({ name: 'granular_permission_id', unique: true })
|
||||
granularPermissionId: string;
|
||||
|
||||
@Column({
|
||||
name: 'app_type',
|
||||
type: 'enum',
|
||||
enum: APP_TYPES,
|
||||
})
|
||||
appType: APP_TYPES;
|
||||
|
||||
@Column({ name: 'can_edit', nullable: false, default: false })
|
||||
canEdit: boolean;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export class GroupApps extends BaseEntity {
|
|||
@JoinColumn({ name: 'app_id' })
|
||||
app: App;
|
||||
|
||||
@ManyToOne(() => AppsGroupPermissions, (appsPermissions) => appsPermissions.id)
|
||||
@ManyToOne(() => AppsGroupPermissions, (appsPermissions) => appsPermissions.groupApps)
|
||||
@JoinColumn({ name: 'apps_group_permissions_id' })
|
||||
appsPermissions: AppsGroupPermissions;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ export class GroupPermissions extends BaseEntity {
|
|||
@Column({ name: 'app_delete', default: false })
|
||||
appDelete: boolean;
|
||||
|
||||
@Column({ name: 'workflow_create', default: false })
|
||||
workflowCreate: boolean;
|
||||
|
||||
@Column({ name: 'workflow_delete', default: false })
|
||||
workflowDelete: boolean;
|
||||
|
||||
@Column({ name: 'folder_crud', default: false })
|
||||
folderCRUD: boolean;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { UserAppsPermissions, UserDataSourcePermissions, UserPermissions } from './types';
|
||||
import { UserAppsPermissions, UserDataSourcePermissions, UserPermissions, UserWorkflowPermissions } from './types';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
|
||||
isSuperAdmin: false,
|
||||
|
|
@ -8,6 +9,8 @@ export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
|
|||
isEndUser: false,
|
||||
appCreate: false,
|
||||
appDelete: false,
|
||||
workflowCreate: false,
|
||||
workflowDelete: false,
|
||||
dataSourceCreate: false,
|
||||
dataSourceDelete: false,
|
||||
folderCRUD: false,
|
||||
|
|
@ -21,8 +24,19 @@ export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
|
|||
hiddenAppsId: [],
|
||||
hideAll: false,
|
||||
},
|
||||
[MODULES.WORKFLOWS]: {
|
||||
editableWorkflowsId: [],
|
||||
isAllEditable: false,
|
||||
executableWorkflowsId: [],
|
||||
isAllExecutable: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const RESOURCE_TO_APP_TYPE_MAP = {
|
||||
[MODULES.APP]: APP_TYPES.FRONT_END,
|
||||
[MODULES.WORKFLOWS]: APP_TYPES.WORKFLOW,
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_USER_APPS_PERMISSIONS: UserAppsPermissions = {
|
||||
editableAppsId: [],
|
||||
isAllEditable: false,
|
||||
|
|
@ -32,6 +46,13 @@ export const DEFAULT_USER_APPS_PERMISSIONS: UserAppsPermissions = {
|
|||
hideAll: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_USER_WORKFLOW_PERMISSIONS: UserWorkflowPermissions = {
|
||||
editableWorkflowsId: [],
|
||||
isAllEditable: false,
|
||||
executableWorkflowsId: [],
|
||||
isAllExecutable: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_USER_DATA_SOURCE_PERMISSIONS: UserDataSourcePermissions = {
|
||||
usableDataSourcesId: [],
|
||||
isAllUsable: false,
|
||||
|
|
|
|||
|
|
@ -53,12 +53,14 @@ export class AbilityService extends IAbilityService {
|
|||
folderCRUD: acc.folderCRUD || group.folderCRUD,
|
||||
orgConstantCRUD: acc.orgConstantCRUD || group.orgConstantCRUD,
|
||||
orgVariableCRUD: acc.orgVariableCRUD,
|
||||
workflowCreate: acc.workflowCreate || group.workflowCreate,
|
||||
workflowDelete: acc.workflowDelete || group.workflowDelete,
|
||||
};
|
||||
}, DEFAULT_USER_PERMISSIONS);
|
||||
|
||||
userPermissions.isAdmin = adminGroup;
|
||||
userPermissions.isSuperAdmin = false;
|
||||
|
||||
|
||||
if (!adminGroup) {
|
||||
const isBuilder = await this.abilityUtilService.isBuilder(user);
|
||||
if (isBuilder) {
|
||||
|
|
@ -84,8 +86,8 @@ export class AbilityService extends IAbilityService {
|
|||
dsGranularPermissions
|
||||
);
|
||||
|
||||
if(userPermissions.isBuilder) {
|
||||
/* in community edition. builder can use the datasources */
|
||||
if (userPermissions.isBuilder) {
|
||||
/* in community edition. builder can use the datasources */
|
||||
userPermissions[MODULES.GLOBAL_DATA_SOURCE].isAllUsable = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ export interface UserPermissions {
|
|||
isEndUser: boolean;
|
||||
appCreate: boolean;
|
||||
appDelete: boolean;
|
||||
workflowCreate: boolean;
|
||||
workflowDelete: boolean;
|
||||
dataSourceCreate: boolean;
|
||||
dataSourceDelete: boolean;
|
||||
folderCRUD: boolean;
|
||||
|
|
@ -24,6 +26,13 @@ export interface UserPermissions {
|
|||
orgVariableCRUD: boolean;
|
||||
[MODULES.APP]?: UserAppsPermissions;
|
||||
[MODULES.GLOBAL_DATA_SOURCE]?: UserDataSourcePermissions;
|
||||
[MODULES.WORKFLOWS]?: UserWorkflowPermissions;
|
||||
}
|
||||
export interface UserWorkflowPermissions {
|
||||
editableWorkflowsId: string[];
|
||||
isAllEditable: boolean;
|
||||
executableWorkflowsId: string[];
|
||||
isAllExecutable: boolean;
|
||||
}
|
||||
|
||||
export interface UserAppsPermissions {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,79 @@ import { AppBase } from '@entities/app_base.entity';
|
|||
import { User } from '@entities/user.entity';
|
||||
import { dbTransactionWrap } from '@helpers/database.helper';
|
||||
import { USER_ROLE } from '@modules/group-permissions/constants';
|
||||
import { DEFAULT_USER_APPS_PERMISSIONS } from './constants';
|
||||
import { DEFAULT_USER_APPS_PERMISSIONS, RESOURCE_TO_APP_TYPE_MAP } from './constants';
|
||||
import { RolesRepository } from '@modules/roles/repository';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class AbilityUtilService {
|
||||
constructor(private readonly roleRepository: RolesRepository) {}
|
||||
|
||||
private getAppTypeConditions(resourcesList: ResourcesItem[]): { conditions: string[]; params: Record<string, any> } {
|
||||
const conditions: string[] = [];
|
||||
const params: Record<string, any> = {};
|
||||
let paramIndex = 0;
|
||||
|
||||
// Get unique resource types from the list
|
||||
const resourceTypes = Array.from(new Set(resourcesList.map((item) => item.resource)));
|
||||
|
||||
resourceTypes.forEach((resourceType) => {
|
||||
const appType = RESOURCE_TO_APP_TYPE_MAP[resourceType];
|
||||
if (appType) {
|
||||
const paramName = `appType${paramIndex}`;
|
||||
conditions.push(`appsGroupPermissions.appType = :${paramName}`);
|
||||
params[paramName] = appType;
|
||||
paramIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
return { conditions, params };
|
||||
}
|
||||
|
||||
private addAppsAndWorkflowPermissionsTOQuery(
|
||||
query: SelectQueryBuilder<GroupPermissions>,
|
||||
resourcesList?: ResourcesItem[]
|
||||
) {
|
||||
query
|
||||
.leftJoin('granularPermissions.appsGroupPermissions', 'appsGroupPermissions')
|
||||
.leftJoin('appsGroupPermissions.groupApps', 'groupApps')
|
||||
.addSelect([
|
||||
'groupApps.appId',
|
||||
'appsGroupPermissions.canEdit',
|
||||
'appsGroupPermissions.canView',
|
||||
'appsGroupPermissions.hideFromDashboard',
|
||||
'appsGroupPermissions.appType',
|
||||
]);
|
||||
|
||||
const resourceIdList = Array.from(
|
||||
new Set(resourcesList?.filter((item) => item?.resourceId).map((item) => item.resourceId))
|
||||
);
|
||||
|
||||
if (resourceIdList?.length) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
resourceIdList.forEach((resourceId, index) => {
|
||||
if (index === 0) {
|
||||
const { conditions, params } = this.getAppTypeConditions(resourcesList);
|
||||
|
||||
// Combine conditions with OR if multiple types are present
|
||||
const typeCondition = conditions.length > 1 ? `(${conditions.join(' OR ')})` : conditions[0];
|
||||
|
||||
qb.where(`(${typeCondition}) AND groupApps.appId = :resourceId`, {
|
||||
resourceId,
|
||||
...params,
|
||||
})
|
||||
.orWhere('granularPermissions.isAll = true')
|
||||
.orWhere('groupApps.id IS NULL');
|
||||
} else {
|
||||
qb.orWhere('groupApps.appId = :resourceId', { resourceId });
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getUserPermissionsQuery(
|
||||
userId: string,
|
||||
resourcePermissionObject: ResourcePermissionQueryObject,
|
||||
|
|
@ -36,10 +103,13 @@ export class AbilityUtilService {
|
|||
}
|
||||
|
||||
if (resources?.length) {
|
||||
const appsResourcesList = resources.filter((item) => item.resource === MODULES.APP);
|
||||
const appsAndWorkflowResourcesList = resources.filter(
|
||||
(item) => item.resource === MODULES.APP || item.resource === MODULES.WORKFLOWS
|
||||
);
|
||||
const dataSourcesResourcesList = resources.filter((item) => item.resource === MODULES.GLOBAL_DATA_SOURCE);
|
||||
if (appsResourcesList?.length) {
|
||||
this.addAppsPermissionsTOQuery(query, appsResourcesList);
|
||||
|
||||
if (appsAndWorkflowResourcesList?.length) {
|
||||
this.addAppsAndWorkflowPermissionsTOQuery(query, appsAndWorkflowResourcesList);
|
||||
}
|
||||
if (dataSourcesResourcesList?.length) {
|
||||
this.addDataSourcesPermissionsTOQuery(query, dataSourcesResourcesList);
|
||||
|
|
@ -49,36 +119,6 @@ export class AbilityUtilService {
|
|||
return query;
|
||||
}
|
||||
|
||||
private addAppsPermissionsTOQuery(query: SelectQueryBuilder<GroupPermissions>, appsList?: ResourcesItem[]) {
|
||||
query
|
||||
.leftJoin('granularPermissions.appsGroupPermissions', 'appsGroupPermissions')
|
||||
.leftJoin('appsGroupPermissions.groupApps', 'groupApps')
|
||||
.addSelect([
|
||||
'groupApps.appId',
|
||||
'appsGroupPermissions.canEdit',
|
||||
'appsGroupPermissions.canView',
|
||||
'appsGroupPermissions.hideFromDashboard',
|
||||
]);
|
||||
|
||||
const appsIdList = Array.from(new Set(appsList?.filter((item) => item?.resourceId).map((item) => item.resourceId)));
|
||||
|
||||
if (appsIdList?.length) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
appsIdList.forEach((appId, index) => {
|
||||
if (index === 0) {
|
||||
qb.where('groupApps.appId = :appId', { appId })
|
||||
.orWhere('granularPermissions.isAll = true')
|
||||
.orWhere('groupApps.id IS NULL');
|
||||
} else {
|
||||
qb.orWhere('groupApps.appId = :appId', { appId });
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private addDataSourcesPermissionsTOQuery(
|
||||
query: SelectQueryBuilder<GroupPermissions>,
|
||||
dataSourcesList?: ResourcesItem[]
|
||||
|
|
@ -145,7 +185,7 @@ export class AbilityUtilService {
|
|||
// Use the provided manager to perform database operations
|
||||
await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const appsOwnedByUser = await manager.find(AppBase, {
|
||||
where: { userId: user.id, organizationId: user.organizationId },
|
||||
where: { userId: user.id, organizationId: user.organizationId, type: APP_TYPES.FRONT_END },
|
||||
});
|
||||
|
||||
const appsIdOwnedByUser = appsOwnedByUser.map((app) => app.id);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export abstract class AbilityFactory<TActions extends string, TSubject> {
|
|||
|
||||
await this.defineAbilityFor(
|
||||
can,
|
||||
{ userPermission, superAdmin, isAdmin, isBuilder, isEndUser, user },
|
||||
{ userPermission, superAdmin, isAdmin, isBuilder, isEndUser, user, resource },
|
||||
extractedMetadata,
|
||||
request
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ export abstract class AbilityGuard implements CanActivate {
|
|||
protected forwardAbility(): boolean {
|
||||
return false;
|
||||
}
|
||||
protected resource: any;
|
||||
protected getResourceObject(): any {
|
||||
return this.resource;
|
||||
}
|
||||
protected setResourceObject(resource: any): void {
|
||||
this.resource = resource;
|
||||
}
|
||||
protected getResource(): ResourceDetails | ResourceDetails[] {
|
||||
return;
|
||||
}
|
||||
|
|
@ -37,6 +44,9 @@ export abstract class AbilityGuard implements CanActivate {
|
|||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
const app: App = request.tj_app;
|
||||
if (app) {
|
||||
this.setResourceObject(app);
|
||||
}
|
||||
|
||||
if (!features?.length) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -111,13 +111,13 @@ export class AppModuleLoader {
|
|||
* █ █
|
||||
* ███████████████████████████████████████████████████████████████████████████████
|
||||
*/
|
||||
const dynamicModules: DynamicModule[] = [];
|
||||
const dynamicModules: Promise<DynamicModule>[] = [];
|
||||
|
||||
try {
|
||||
const { LogToFileModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/log-to-file/module`);
|
||||
const { AuditLogsModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/audit-logs/module`);
|
||||
dynamicModules.push(await LogToFileModule.register(configs));
|
||||
dynamicModules.push(await AuditLogsModule.register(configs));
|
||||
dynamicModules.push(LogToFileModule.register(configs));
|
||||
dynamicModules.push(AuditLogsModule.register(configs));
|
||||
} catch (error) {
|
||||
console.error('Error loading dynamic modules:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export interface UserAllPermissions {
|
|||
isBuilder: boolean;
|
||||
isEndUser: boolean;
|
||||
user: User;
|
||||
resource: ResourceDetails[];
|
||||
}
|
||||
|
||||
export interface FeatureConfig {
|
||||
|
|
|
|||
78
server/src/modules/apps/ability/app.ability.ts
Normal file
78
server/src/modules/apps/ability/app.ability.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineAppAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
appId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
const userAppPermissions = userPermission?.[MODULES.APP];
|
||||
const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
|
||||
const isAllAppsCreatable = !!userPermission?.appCreate;
|
||||
const isAllAppsDeletable = !!userPermission?.appDelete;
|
||||
const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
|
||||
|
||||
// App listing is available to all
|
||||
can(FEATURE_KEY.GET, App);
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
// Admin or super admin and do all operations
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.GET_ASSOCIATED_TABLES,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.UPDATE_ICON,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllAppsCreatable) {
|
||||
can(FEATURE_KEY.CREATE, App);
|
||||
}
|
||||
|
||||
if (
|
||||
isAllAppsEditable ||
|
||||
(userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId))
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.GET_ASSOCIATED_TABLES,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.UPDATE_ICON,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
if (isAllAppsDeletable) {
|
||||
// Gives delete permission only for editable apps
|
||||
can(FEATURE_KEY.DELETE, App);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isAllAppsViewable ||
|
||||
(userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId))
|
||||
) {
|
||||
// add view permissions for all apps or specific app
|
||||
can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,24 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
|
|||
import { App } from '@entities/app.entity';
|
||||
import { ResourceDetails } from '@modules/app/types';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { APP_TYPES } from '../constants';
|
||||
|
||||
@Injectable()
|
||||
export class FeatureAbilityGuard extends AbilityGuard {
|
||||
protected getResource(): ResourceDetails {
|
||||
return {
|
||||
resourceType: MODULES.APP,
|
||||
};
|
||||
const resource = this.getResourceObject();
|
||||
switch (resource?.type) {
|
||||
case APP_TYPES.FRONT_END:
|
||||
return {
|
||||
resourceType: MODULES.APP,
|
||||
};
|
||||
case APP_TYPES.WORKFLOW:
|
||||
return {
|
||||
resourceType: MODULES.WORKFLOWS,
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
protected getAbilityFactory() {
|
||||
return FeatureAbilityFactory;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { AbilityFactory } from '@modules/app/ability-factory';
|
|||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { createAbility } from './utility';
|
||||
|
||||
type Subjects = InferSubjects<typeof App> | 'all';
|
||||
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
|
||||
|
|
@ -21,72 +21,7 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
extractedMetadata: { moduleName: string; features: string[] },
|
||||
request?: any
|
||||
): void {
|
||||
const appId = request?.tj_resource_id;
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
|
||||
const userAppPermissions = userPermission?.[MODULES.APP];
|
||||
const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
|
||||
const isAllAppsCreatable = !!userPermission?.appCreate;
|
||||
const isAllAppsDeletable = !!userPermission?.appDelete;
|
||||
const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
|
||||
|
||||
// App listing is available to all
|
||||
can(FEATURE_KEY.GET, App);
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
// Admin or super admin and do all operations
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.GET_ASSOCIATED_TABLES,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.UPDATE_ICON,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllAppsCreatable) {
|
||||
can(FEATURE_KEY.CREATE, App);
|
||||
}
|
||||
|
||||
if (
|
||||
isAllAppsEditable ||
|
||||
(userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId))
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.GET_ASSOCIATED_TABLES,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.UPDATE_ICON,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
if (isAllAppsDeletable) {
|
||||
// Gives delete permission only for editable apps
|
||||
can(FEATURE_KEY.DELETE, App);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isAllAppsViewable ||
|
||||
(userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId))
|
||||
) {
|
||||
// add view permissions for all apps or specific app
|
||||
can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
|
||||
}
|
||||
const resourceId = request?.tj_resource_id;
|
||||
createAbility(can, UserAllPermissions, resourceId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
server/src/modules/apps/ability/utility.ts
Normal file
27
server/src/modules/apps/ability/utility.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
import { defineAppAbility } from './app.ability';
|
||||
import { defineWorkflowAbility } from './workflow.ability';
|
||||
|
||||
export function createAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
resourceId?: string
|
||||
): void {
|
||||
const resourceType = UserAllPermissions?.resource[0]?.resourceType
|
||||
? UserAllPermissions.resource[0].resourceType
|
||||
: MODULES.APP;
|
||||
|
||||
switch (resourceType) {
|
||||
case MODULES.APP:
|
||||
defineAppAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
case MODULES.WORKFLOWS:
|
||||
defineWorkflowAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported resource type: ${resourceType}`);
|
||||
}
|
||||
}
|
||||
78
server/src/modules/apps/ability/workflow.ability.ts
Normal file
78
server/src/modules/apps/ability/workflow.ability.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineWorkflowAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
workflowId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
const userWorkflowPermissions = userPermission?.[MODULES.WORKFLOWS];
|
||||
const isAllWorkflowsEditable = !!userWorkflowPermissions?.isAllEditable;
|
||||
const isAllWorkflowsCreatable = !!userPermission?.workflowCreate;
|
||||
const isAllWorkflowsDeletable = !!userPermission?.workflowDelete;
|
||||
const isAllWorkflowsExecutable = !!userWorkflowPermissions?.isAllExecutable;
|
||||
|
||||
// Workflow listing is available to all
|
||||
can(FEATURE_KEY.GET, App);
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
// Admin or super admin can do all operations
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllWorkflowsCreatable) {
|
||||
can(FEATURE_KEY.CREATE, App);
|
||||
}
|
||||
|
||||
if (
|
||||
isAllWorkflowsEditable ||
|
||||
(userWorkflowPermissions?.editableWorkflowsId?.length &&
|
||||
workflowId &&
|
||||
userWorkflowPermissions.editableWorkflowsId.includes(workflowId))
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
],
|
||||
App
|
||||
);
|
||||
if (isAllWorkflowsDeletable) {
|
||||
// Gives delete permission only for editable workflows
|
||||
can(FEATURE_KEY.DELETE, App);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isAllWorkflowsExecutable ||
|
||||
(userWorkflowPermissions?.executableWorkflowsId?.length &&
|
||||
workflowId &&
|
||||
userWorkflowPermissions.executableWorkflowsId.includes(workflowId))
|
||||
) {
|
||||
// add view permissions for all workflows or specific workflow
|
||||
can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
ValidateAppAccessResponseDto,
|
||||
VersionReleaseDto,
|
||||
} from './dto';
|
||||
import { FEATURE_KEY } from './constants';
|
||||
import { APP_TYPES, FEATURE_KEY } from './constants';
|
||||
import { camelizeKeys, decamelizeKeys } from 'humps';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { AppsUtilService } from './util.service';
|
||||
|
|
@ -62,7 +62,7 @@ export class AppsService implements IAppsService {
|
|||
async create(user: User, appCreateDto: AppCreateDto) {
|
||||
const { name, icon, type } = appCreateDto;
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const app = await this.appsUtilService.create(name, user, type, manager);
|
||||
const app = await this.appsUtilService.create(name, user, type as APP_TYPES, manager);
|
||||
|
||||
const appUpdateDto = new AppUpdateDto();
|
||||
appUpdateDto.name = name;
|
||||
|
|
@ -200,7 +200,8 @@ export class AppsService implements IAppsService {
|
|||
user,
|
||||
folder,
|
||||
parseInt(page || '1'),
|
||||
searchKey
|
||||
searchKey,
|
||||
type as APP_TYPES
|
||||
);
|
||||
apps = viewableApps;
|
||||
totalFolderCount = totalCount;
|
||||
|
|
@ -215,7 +216,7 @@ export class AppsService implements IAppsService {
|
|||
}
|
||||
}
|
||||
|
||||
const totalCount = await this.appsUtilService.count(user, searchKey, type);
|
||||
const totalCount = await this.appsUtilService.count(user, searchKey, type as APP_TYPES);
|
||||
|
||||
const totalPageCount = folderId ? totalFolderCount : totalCount;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AppsRepository } from '../repository';
|
||||
import { IWorkflowService } from '../interfaces/services/IWorkflowService';
|
||||
|
||||
import { APP_TYPES } from '../constants';
|
||||
@Injectable()
|
||||
export class WorkflowService implements IWorkflowService {
|
||||
constructor(protected readonly appsRepository: AppsRepository) {}
|
||||
|
||||
async getWorkflows(organizationId: string) {
|
||||
const workflowApps = await this.appsRepository.find({
|
||||
where: { type: 'workflow', organizationId },
|
||||
where: { type: APP_TYPES.WORKFLOW, organizationId },
|
||||
});
|
||||
|
||||
const result = workflowApps.map((workflowApp) => ({ id: workflowApp.id, name: workflowApp.name }));
|
||||
|
|
|
|||
|
|
@ -32,18 +32,16 @@ import { cloneDeep } from 'lodash';
|
|||
import { merge } from 'lodash';
|
||||
import { mergeWith } from 'lodash';
|
||||
import { isArray } from 'lodash';
|
||||
import { UserAppsPermissions } from '@modules/ability/types';
|
||||
import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
|
||||
import { AbilityService } from '@modules/ability/interfaces/IService';
|
||||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
import { IAppsUtilService } from './interfaces/IUtilService';
|
||||
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
|
||||
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
|
||||
import { APP_TYPES } from './constants';
|
||||
import { Component } from 'src/entities/component.entity';
|
||||
import { Layout } from 'src/entities/layout.entity';
|
||||
import { WorkspaceAppsResponseDto } from '@modules/external-apis/dto';
|
||||
import { RequestContext } from '@modules/request-context/service';
|
||||
import { RenameAppOrVersionDto } from '@modules/app-git/dto';
|
||||
import got from 'got';
|
||||
import { DataQuery } from '@entities/data_query.entity';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -58,7 +56,7 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
protected readonly dataSourceRepository: DataSourcesRepository,
|
||||
protected readonly dataSourceUtilService: DataSourcesUtilService
|
||||
) { }
|
||||
async create(name: string, user: User, type: string, manager: EntityManager): Promise<App> {
|
||||
async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise<App> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const app = await catchDbException(() => {
|
||||
return manager.save(
|
||||
|
|
@ -69,8 +67,8 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
updatedAt: new Date(),
|
||||
organizationId: user.organizationId,
|
||||
userId: user.id,
|
||||
isMaintenanceOn: type === 'workflow' ? true : false,
|
||||
...(type === 'workflow' && { workflowApiToken: uuidv4() }),
|
||||
isMaintenanceOn: type === APP_TYPES.WORKFLOW ? true : false,
|
||||
...(type === APP_TYPES.WORKFLOW && { workflowApiToken: uuidv4() }),
|
||||
})
|
||||
);
|
||||
}, [{ dbConstraint: DataBaseConstraints.APP_NAME_UNIQUE, message: 'This app name is already taken.' }]);
|
||||
|
|
@ -410,14 +408,26 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
|
||||
async all(user: User, page: number, searchKey: string, type: string): Promise<AppBase[]> {
|
||||
//Migrate it to app utility files
|
||||
let resourceType: MODULES;
|
||||
|
||||
switch (type) {
|
||||
case APP_TYPES.WORKFLOW:
|
||||
resourceType = MODULES.WORKFLOWS;
|
||||
break;
|
||||
case APP_TYPES.FRONT_END:
|
||||
resourceType = MODULES.APP;
|
||||
break;
|
||||
default:
|
||||
resourceType = MODULES.APP;
|
||||
}
|
||||
const userPermission = await this.abilityService.resourceActionsPermission(user, {
|
||||
resources: [{ resource: MODULES.APP }],
|
||||
resources: [{ resource: resourceType }],
|
||||
organizationId: user.organizationId,
|
||||
});
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const viewableAppsQb = this.viewableAppsQueryUsingPermissions(
|
||||
user,
|
||||
userPermission[MODULES.APP],
|
||||
userPermission[resourceType],
|
||||
manager,
|
||||
searchKey,
|
||||
undefined,
|
||||
|
|
@ -436,79 +446,119 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
|
||||
protected viewableAppsQueryUsingPermissions(
|
||||
user: User,
|
||||
userAppPermissions: UserAppsPermissions,
|
||||
userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
|
||||
manager: EntityManager,
|
||||
searchKey?: string,
|
||||
select?: Array<string>,
|
||||
type?: string
|
||||
): SelectQueryBuilder<AppBase> {
|
||||
const viewableApps = userAppPermissions.hideAll
|
||||
? [null, ...userAppPermissions.editableAppsId]
|
||||
: [
|
||||
null,
|
||||
...Array.from(
|
||||
new Set([
|
||||
...userAppPermissions.editableAppsId,
|
||||
...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
|
||||
])
|
||||
),
|
||||
];
|
||||
const viewableAppsQb = manager
|
||||
.createQueryBuilder(AppBase, 'viewable_apps')
|
||||
.innerJoin('viewable_apps.user', 'user')
|
||||
.createQueryBuilder(AppBase, 'apps')
|
||||
.innerJoin('apps.user', 'user')
|
||||
.addSelect(['user.firstName', 'user.lastName'])
|
||||
.where('viewable_apps.organizationId = :organizationId', { organizationId: user.organizationId });
|
||||
.where('apps.organizationId = :organizationId', { organizationId: user.organizationId });
|
||||
|
||||
if (type === 'module') {
|
||||
if (type === APP_TYPES.MODULE) {
|
||||
viewableAppsQb.leftJoinAndSelect('viewable_apps.appVersions', 'versions');
|
||||
}
|
||||
|
||||
if (type) viewableAppsQb.andWhere('viewable_apps.type = :type', { type: type });
|
||||
if (type) {
|
||||
viewableAppsQb.andWhere('apps.type = :type', { type });
|
||||
}
|
||||
|
||||
if (searchKey) {
|
||||
viewableAppsQb.andWhere('LOWER(viewable_apps.name) like :searchKey', {
|
||||
viewableAppsQb.andWhere('LOWER(apps.name) like :searchKey', {
|
||||
searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (select) {
|
||||
viewableAppsQb.select(select.map((col) => `viewable_apps.${col}`));
|
||||
viewableAppsQb.select(select.map((col) => `apps.${col}`));
|
||||
}
|
||||
viewableAppsQb.orderBy('viewable_apps.createdAt', 'DESC');
|
||||
|
||||
viewableAppsQb.orderBy('apps.createdAt', 'DESC');
|
||||
|
||||
if (this.isSuperAdmin(user)) {
|
||||
return viewableAppsQb;
|
||||
}
|
||||
|
||||
const viewableApps = this.calculateViewableFrontEndApps(userAppPermissions as unknown as UserAppsPermissions);
|
||||
|
||||
switch (type) {
|
||||
case APP_TYPES.FRONT_END:
|
||||
default:
|
||||
return this.addViewableFrontEndAppsFilter(
|
||||
viewableAppsQb,
|
||||
userAppPermissions as unknown as UserAppsPermissions,
|
||||
viewableApps
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private calculateViewableFrontEndApps(userAppPermissions: UserAppsPermissions): string[] {
|
||||
return userAppPermissions.hideAll
|
||||
? [null, ...userAppPermissions.editableAppsId]
|
||||
: [
|
||||
null,
|
||||
...Array.from(
|
||||
new Set([
|
||||
...userAppPermissions.editableAppsId,
|
||||
...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
|
||||
])
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private addViewableFrontEndAppsFilter(
|
||||
query: SelectQueryBuilder<AppBase>,
|
||||
userAppPermissions: UserAppsPermissions,
|
||||
viewableApps: string[]
|
||||
): SelectQueryBuilder<AppBase> {
|
||||
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
|
||||
if (isAllEditable) return viewableAppsQb;
|
||||
if (isAllEditable) return query;
|
||||
|
||||
if ((isAllViewable && hideAll) || (!isAllViewable && !hideAll) || (!isAllViewable && hideAll)) {
|
||||
viewableAppsQb.andWhere('viewable_apps.id IN (:...viewableApps)', {
|
||||
query.andWhere('apps.id IN (:...viewableApps)', {
|
||||
viewableApps,
|
||||
});
|
||||
return viewableAppsQb;
|
||||
return query;
|
||||
}
|
||||
|
||||
const hiddenApps = userAppPermissions.hiddenAppsId.filter((id) => !userAppPermissions.editableAppsId.includes(id));
|
||||
if (!userAppPermissions.hideAll && isAllViewable && hiddenApps.length > 0) {
|
||||
viewableAppsQb.andWhere('viewable_apps.id NOT IN (:...hiddenApps)', {
|
||||
query.andWhere('apps.id NOT IN (:...hiddenApps)', {
|
||||
hiddenApps,
|
||||
});
|
||||
}
|
||||
return viewableAppsQb;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected isSuperAdmin(user: User) {
|
||||
return !!(user?.userType === USER_TYPE.INSTANCE);
|
||||
}
|
||||
|
||||
async count(user: User, searchKey, type: string): Promise<number> {
|
||||
async count(user: User, searchKey, type: APP_TYPES): Promise<number> {
|
||||
let resourceType: MODULES;
|
||||
|
||||
switch (type) {
|
||||
case APP_TYPES.WORKFLOW:
|
||||
resourceType = MODULES.WORKFLOWS;
|
||||
break;
|
||||
case APP_TYPES.FRONT_END:
|
||||
resourceType = MODULES.APP;
|
||||
break;
|
||||
default:
|
||||
resourceType = MODULES.APP;
|
||||
}
|
||||
const userPermission = await this.abilityService.resourceActionsPermission(user, {
|
||||
resources: [{ resource: MODULES.APP }],
|
||||
resources: [{ resource: resourceType }],
|
||||
organizationId: user.organizationId,
|
||||
});
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const apps = await this.viewableAppsQueryUsingPermissions(
|
||||
user,
|
||||
userPermission[MODULES.APP],
|
||||
userPermission[resourceType],
|
||||
manager,
|
||||
searchKey,
|
||||
undefined,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineDataQueryAppAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
appId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
const resourcePermissions = userPermission?.[MODULES.APP];
|
||||
const isAllEditable = !!resourcePermissions?.isAllEditable;
|
||||
const isCanCreate = userPermission.appCreate;
|
||||
const isCanDelete = userPermission.appDelete;
|
||||
const isAllViewable = !!resourcePermissions?.isAllViewable;
|
||||
|
||||
// Always grant RUN_EDITOR and RUN_VIEWER permissions
|
||||
can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.UPDATE_DATA_SOURCE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllEditable || isCanCreate || isCanDelete) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resourcePermissions?.editableAppsId?.length && appId && resourcePermissions?.editableAppsId?.includes(appId)) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllViewable) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resourcePermissions?.viewableAppsId?.length && appId && resourcePermissions?.viewableAppsId?.includes(appId)) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineDataQueryWorkflowAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
workflowId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
const resourcePermissions = userPermission?.[MODULES.WORKFLOWS];
|
||||
const isAllEditable = !!resourcePermissions?.isAllEditable;
|
||||
const isCanCreate = userPermission.workflowCreate;
|
||||
const isCanDelete = userPermission.workflowDelete;
|
||||
const isAllExecutable = !!resourcePermissions?.isAllExecutable;
|
||||
|
||||
// Always grant RUN_EDITOR and RUN_VIEWER permissions
|
||||
can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.UPDATE_DATA_SOURCE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllEditable || isCanCreate || isCanDelete) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
resourcePermissions?.editableWorkflowsId?.length &&
|
||||
workflowId &&
|
||||
resourcePermissions?.editableWorkflowsId?.includes(workflowId)
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllExecutable) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
resourcePermissions?.executableWorkflowsId?.length &&
|
||||
workflowId &&
|
||||
resourcePermissions?.executableWorkflowsId?.includes(workflowId)
|
||||
) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
|
|||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { ResourceDetails } from '@modules/app/types';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class FeatureAbilityGuard extends AbilityGuard {
|
||||
|
|
@ -14,10 +15,23 @@ export class FeatureAbilityGuard extends AbilityGuard {
|
|||
protected getSubjectType() {
|
||||
return App;
|
||||
}
|
||||
|
||||
private getAppResourceType(): MODULES {
|
||||
const appResource: App = this.getResourceObject();
|
||||
switch (appResource.type) {
|
||||
case APP_TYPES.FRONT_END:
|
||||
return MODULES.APP;
|
||||
case APP_TYPES.WORKFLOW:
|
||||
return MODULES.WORKFLOWS;
|
||||
default:
|
||||
return MODULES.APP;
|
||||
}
|
||||
}
|
||||
protected getResource(): ResourceDetails | ResourceDetails[] {
|
||||
const appResource: MODULES = this.getAppResourceType();
|
||||
return [
|
||||
{
|
||||
resourceType: MODULES.APP,
|
||||
resourceType: appResource,
|
||||
},
|
||||
{
|
||||
resourceType: MODULES.GLOBAL_DATA_SOURCE,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
|
|||
import { AbilityFactory } from '@modules/app/ability-factory';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../../constants';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { defineDataQueryAppAbility } from './data-query-app.ability';
|
||||
import { defineDataQueryWorkflowAbility } from './data-query-workflow.ability';
|
||||
|
||||
type Subjects = InferSubjects<typeof App> | 'all';
|
||||
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
|
||||
|
|
@ -21,80 +23,18 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
extractedMetadata: { moduleName: string; features: string[] },
|
||||
request?: any
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
const resourceId = request?.tj_resource_id;
|
||||
const resourceType = UserAllPermissions.resource[0].resourceType;
|
||||
|
||||
const resourcePermissions = userPermission?.[MODULES.APP];
|
||||
const isAllEditable = !!resourcePermissions?.isAllEditable;
|
||||
const isCanCreate = userPermission.appCreate;
|
||||
const isCanDelete = userPermission.appDelete;
|
||||
const isAllViewable = !!resourcePermissions?.isAllViewable;
|
||||
|
||||
const appId = request?.tj_resource_id;
|
||||
|
||||
// Always grant RUN_EDITOR and RUN_VIEWER permissions
|
||||
can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
|
||||
|
||||
// Admin or super admin and do all operations
|
||||
if (isAdmin || superAdmin) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.UPDATE_DATA_SOURCE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllEditable || isCanCreate || isCanDelete) {
|
||||
// Can create and can delete has master permissions
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resourcePermissions?.editableAppsId?.length && appId && resourcePermissions?.editableAppsId?.includes(appId)) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_ONE,
|
||||
FEATURE_KEY.RUN_EDITOR,
|
||||
FEATURE_KEY.RUN_VIEWER,
|
||||
FEATURE_KEY.PREVIEW,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllViewable) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
}
|
||||
if (resourcePermissions?.viewableAppsId?.length && appId && resourcePermissions?.viewableAppsId?.includes(appId)) {
|
||||
can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
|
||||
return;
|
||||
switch (resourceType) {
|
||||
case MODULES.APP:
|
||||
defineDataQueryAppAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
case MODULES.WORKFLOWS:
|
||||
defineDataQueryWorkflowAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported resource type: ${resourceType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import { Folder } from '@entities/folder.entity';
|
|||
import { User } from '@entities/user.entity';
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { AppBase } from '@entities/app_base.entity';
|
||||
import { UserAppsPermissions } from '@modules/ability/types';
|
||||
import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
export interface IFolderAppsUtilService {
|
||||
allFoldersWithAppCount(
|
||||
user: User,
|
||||
userAppPermissions: UserAppsPermissions,
|
||||
userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
|
||||
manager: EntityManager,
|
||||
type?: string,
|
||||
searchKey?: string
|
||||
|
|
@ -16,6 +17,7 @@ export interface IFolderAppsUtilService {
|
|||
user: User,
|
||||
folder: Folder,
|
||||
page: number,
|
||||
searchKey: string
|
||||
searchKey: string,
|
||||
type?: APP_TYPES
|
||||
): Promise<{ viewableApps: AppBase[]; totalCount: number }>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { MODULES } from '@modules/app/constants/modules';
|
|||
import { AbilityService } from '@modules/ability/interfaces/IService';
|
||||
import { User } from '@entities/user.entity';
|
||||
import { USER_ROLE } from '@modules/group-permissions/constants';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
@Injectable()
|
||||
export class FolderAppsService implements IFolderAppsService {
|
||||
constructor(
|
||||
|
|
@ -49,16 +50,30 @@ export class FolderAppsService implements IFolderAppsService {
|
|||
return await manager.delete(FolderApp, { folderId, appId });
|
||||
});
|
||||
}
|
||||
|
||||
private getResourceTypefromAppType(type: APP_TYPES) {
|
||||
switch (type) {
|
||||
case APP_TYPES.FRONT_END:
|
||||
return MODULES.APP;
|
||||
case APP_TYPES.WORKFLOW:
|
||||
return MODULES.WORKFLOWS;
|
||||
default:
|
||||
throw new BadRequestException('Invalid resource type');
|
||||
}
|
||||
}
|
||||
|
||||
async getFolders(user: User, query) {
|
||||
return dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const type = query.type;
|
||||
const searchKey = query.searchKey;
|
||||
const resourceType = this.getResourceTypefromAppType(type as APP_TYPES);
|
||||
const userAppPermissions = (
|
||||
await this.abilityService.resourceActionsPermission(user, {
|
||||
resources: [{ resource: MODULES.APP }],
|
||||
resources: [{ resource: resourceType }],
|
||||
organizationId: user.organizationId,
|
||||
})
|
||||
)?.[MODULES.APP];
|
||||
)?.[resourceType];
|
||||
|
||||
const allFolderList = await this.foldersUtilService.allFolders(user, manager, type);
|
||||
if (allFolderList.length === 0) {
|
||||
return { folders: [] };
|
||||
|
|
|
|||
|
|
@ -7,26 +7,59 @@ import { AppBase } from '@entities/app_base.entity';
|
|||
import { dbTransactionWrap } from '@helpers/database.helper';
|
||||
import { FolderApp } from '@entities/folder_app.entity';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { UserAppsPermissions } from '@modules/ability/types';
|
||||
import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
|
||||
import { AbilityService } from '@modules/ability/interfaces/IService';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class FolderAppsUtilService implements IFolderAppsUtilService {
|
||||
constructor(protected readonly abilityService: AbilityService) {}
|
||||
|
||||
async allFoldersWithAppCount(
|
||||
user: User,
|
||||
userAppPermissions: UserAppsPermissions,
|
||||
userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
|
||||
manager: EntityManager,
|
||||
type = 'front-end',
|
||||
type = APP_TYPES.FRONT_END,
|
||||
searchKey?: string
|
||||
): Promise<Folder[]> {
|
||||
return this.getFolderQuery(user.organizationId, manager, userAppPermissions, type, searchKey).distinct().getMany();
|
||||
return this.getFolderQuery(user.organizationId, manager, userAppPermissions as UserAppsPermissions, type, searchKey)
|
||||
.distinct()
|
||||
.getMany();
|
||||
}
|
||||
|
||||
private getFolderQuery(
|
||||
protected getBaseFolderQuery(
|
||||
organizationId: string,
|
||||
manager: EntityManager,
|
||||
type: APP_TYPES,
|
||||
searchKey?: string
|
||||
): SelectQueryBuilder<Folder> {
|
||||
const query = manager.createQueryBuilder(Folder, 'folders');
|
||||
query.leftJoinAndSelect('folders.folderApps', 'folder_apps');
|
||||
query.leftJoin('folder_apps.app', 'app');
|
||||
|
||||
if (searchKey) {
|
||||
query.andWhere('LOWER(app.name) like :searchKey', {
|
||||
searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
|
||||
});
|
||||
}
|
||||
|
||||
query
|
||||
.andWhere('folders.organization_id = :organizationId', {
|
||||
organizationId,
|
||||
})
|
||||
.andWhere('folders.type = :type', {
|
||||
type,
|
||||
})
|
||||
.orderBy('folders.name', 'ASC');
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected getFolderQuery(
|
||||
organizationId: string,
|
||||
manager: EntityManager,
|
||||
userAppPermissions: UserAppsPermissions,
|
||||
type = 'front-end',
|
||||
type = APP_TYPES.FRONT_END,
|
||||
searchKey?: string
|
||||
): SelectQueryBuilder<Folder> {
|
||||
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
|
||||
|
|
@ -44,9 +77,8 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
|
|||
const hiddenApps = [
|
||||
...userAppPermissions.hiddenAppsId.filter((id) => !userAppPermissions.editableAppsId.includes(id)),
|
||||
];
|
||||
const query = manager.createQueryBuilder(Folder, 'folders');
|
||||
query.leftJoinAndSelect('folders.folderApps', 'folder_apps');
|
||||
query.leftJoin('folder_apps.app', 'app');
|
||||
|
||||
const query = this.getBaseFolderQuery(organizationId, manager, type, searchKey);
|
||||
|
||||
if (!isAllEditable) {
|
||||
// Not all apps are editable - filter with view privilege
|
||||
|
|
@ -66,27 +98,34 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
|
|||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected getBaseAppsQuery(
|
||||
manager: EntityManager,
|
||||
folderAppIds: string[],
|
||||
searchKey?: string
|
||||
): SelectQueryBuilder<AppBase> {
|
||||
const query = manager
|
||||
.createQueryBuilder(AppBase, 'apps')
|
||||
.innerJoin('apps.user', 'user')
|
||||
.addSelect(['user.firstName', 'user.lastName']);
|
||||
|
||||
if (searchKey) {
|
||||
query.andWhere('LOWER(app.name) like :searchKey', {
|
||||
searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
|
||||
query.andWhere('LOWER(apps.name) LIKE :searchKey', {
|
||||
searchKey: `%${searchKey.toLowerCase()}%`,
|
||||
});
|
||||
}
|
||||
query
|
||||
.andWhere('folders.organization_id = :organizationId', {
|
||||
organizationId,
|
||||
})
|
||||
.andWhere('folders.type = :type', {
|
||||
type,
|
||||
})
|
||||
.orderBy('folders.name', 'ASC');
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
async getAppsFor(
|
||||
user: User,
|
||||
folder: Folder,
|
||||
page: number,
|
||||
searchKey: string
|
||||
searchKey: string,
|
||||
type: APP_TYPES
|
||||
): Promise<{
|
||||
viewableApps: AppBase[];
|
||||
totalCount: number;
|
||||
|
|
@ -113,33 +152,9 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
|
|||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
|
||||
const viewableAppsTotal = isAllEditable
|
||||
? [null, ...folderAppIds]
|
||||
: hideAll
|
||||
? [null, ...userAppPermissions.editableAppsId]
|
||||
: isAllViewable
|
||||
? [null, ...folderAppIds].filter((id) => !userAppPermissions.hiddenAppsId.includes(id))
|
||||
: [
|
||||
null,
|
||||
...Array.from(
|
||||
new Set([
|
||||
...userAppPermissions.editableAppsId,
|
||||
...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
|
||||
])
|
||||
),
|
||||
];
|
||||
|
||||
const viewableAppIds = [null, ...viewableAppsTotal.filter((id) => folderAppIds.includes(id))];
|
||||
|
||||
const viewableAppsInFolder = manager
|
||||
.createQueryBuilder(AppBase, 'apps')
|
||||
.innerJoin('apps.user', 'user')
|
||||
.addSelect(['user.firstName', 'user.lastName']);
|
||||
|
||||
viewableAppsInFolder.where('apps.id IN (:...viewableAppIds)', {
|
||||
viewableAppIds: viewableAppIds,
|
||||
});
|
||||
const viewableAppsInFolder = this.getBaseAppsQuery(manager, folderAppIds, searchKey);
|
||||
this.addViewableFrontendFilter(viewableAppsInFolder, folderAppIds, userAppPermissions);
|
||||
|
||||
const [viewableApps, totalCount] = await Promise.all([
|
||||
viewableAppsInFolder
|
||||
|
|
@ -156,4 +171,36 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected addViewableFrontendFilter(
|
||||
query: SelectQueryBuilder<AppBase>,
|
||||
folderAppIds: string[],
|
||||
userAppPermissions: UserAppsPermissions
|
||||
): SelectQueryBuilder<AppBase> {
|
||||
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
|
||||
|
||||
const viewableAppsTotal = isAllEditable
|
||||
? [null, ...folderAppIds]
|
||||
: hideAll
|
||||
? [null, ...userAppPermissions.editableAppsId]
|
||||
: isAllViewable
|
||||
? [null, ...folderAppIds].filter((id) => !userAppPermissions.hiddenAppsId.includes(id))
|
||||
: [
|
||||
null,
|
||||
...Array.from(
|
||||
new Set([
|
||||
...userAppPermissions.editableAppsId,
|
||||
...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
|
||||
])
|
||||
),
|
||||
];
|
||||
|
||||
const viewableAppIds = [null, ...viewableAppsTotal.filter((id) => folderAppIds.includes(id))];
|
||||
|
||||
query.where('apps.id IN (:...viewableAppIds)', {
|
||||
viewableAppIds,
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ import { ResourceType } from './index';
|
|||
export const DEFAULT_GRANULAR_PERMISSIONS_NAME = {
|
||||
[ResourceType.APP]: 'Apps',
|
||||
[ResourceType.DATA_SOURCE]: 'Data sources',
|
||||
[ResourceType.WORKFLOWS]: 'Workflows',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const HUMANIZED_USER_LIST = ['End-user', 'Builder', 'Admin'];
|
|||
export enum ResourceType {
|
||||
APP = 'app',
|
||||
DATA_SOURCE = 'data_source',
|
||||
WORKFLOWS = 'workflow',
|
||||
}
|
||||
|
||||
export const DEFAULT_GROUP_PERMISSIONS = {
|
||||
|
|
@ -26,6 +27,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
|
|||
appCreate: true,
|
||||
appDelete: true,
|
||||
folderCRUD: true,
|
||||
workflowCreate: true,
|
||||
workflowDelete: true,
|
||||
orgConstantCRUD: true,
|
||||
dataSourceCreate: true,
|
||||
dataSourceDelete: true,
|
||||
|
|
@ -37,6 +40,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
|
|||
appCreate: true,
|
||||
appDelete: true,
|
||||
folderCRUD: true,
|
||||
workflowCreate: true,
|
||||
workflowDelete: true,
|
||||
orgConstantCRUD: true,
|
||||
dataSourceCreate: true,
|
||||
dataSourceDelete: true,
|
||||
|
|
@ -47,6 +52,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
|
|||
type: GROUP_PERMISSIONS_TYPE.DEFAULT,
|
||||
appCreate: false,
|
||||
appDelete: false,
|
||||
workflowCreate: false,
|
||||
workflowDelete: false,
|
||||
folderCRUD: false,
|
||||
orgConstantCRUD: false,
|
||||
dataSourceCreate: false,
|
||||
|
|
@ -68,6 +75,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
|
|||
canUse: false,
|
||||
},
|
||||
},
|
||||
[ResourceType.WORKFLOWS]: {
|
||||
canEdit: true,
|
||||
canView: false,
|
||||
},
|
||||
},
|
||||
[USER_ROLE.END_USER]: {
|
||||
[ResourceType.APP]: {
|
||||
|
|
@ -75,6 +86,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
|
|||
canView: true,
|
||||
hideFromDashboard: false,
|
||||
},
|
||||
[ResourceType.WORKFLOWS]: {
|
||||
canEdit: false,
|
||||
canView: true,
|
||||
},
|
||||
},
|
||||
[USER_ROLE.BUILDER]: {
|
||||
[ResourceType.APP]: {
|
||||
|
|
@ -88,6 +103,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
|
|||
canUse: false,
|
||||
},
|
||||
},
|
||||
[ResourceType.WORKFLOWS]: {
|
||||
canEdit: true,
|
||||
canView: false,
|
||||
},
|
||||
},
|
||||
} as Record<USER_ROLE, Record<ResourceType, CreateResourcePermissionObject<any>>>;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,14 @@ export class UpdateGroupPermissionDto {
|
|||
@IsOptional()
|
||||
orgConstantCRUD: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
workflowCreate: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
workflowDelete: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
dataSourceCreate: boolean;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ export class GranularPermissionsService implements IGranularPermissionsService {
|
|||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const apps = await manager.find(AppBase, {
|
||||
where: {
|
||||
type: 'front-end',
|
||||
organizationId,
|
||||
},
|
||||
});
|
||||
|
|
@ -53,6 +52,7 @@ export class GranularPermissionsService implements IGranularPermissionsService {
|
|||
return {
|
||||
name: app.name,
|
||||
id: app.id,
|
||||
type: app.type,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,21 +2,39 @@ import { GranularPermissions } from '@entities/granular_permissions.entity';
|
|||
import { ResourceType } from '../constants';
|
||||
import { CreateGranularPermissionDto, UpdateGranularPermissionDto } from '../dto/granular-permissions';
|
||||
import { GroupPermissions } from '@entities/group_permissions.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
export interface AddableResourceItem {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
export type CreateResourcePermissionObject<T extends ResourceType.APP | ResourceType.DATA_SOURCE> =
|
||||
T extends ResourceType.APP ? CreateAppsPermissionsObject : CreateDataSourcePermissionsObject;
|
||||
type CreateResourcePermissionMap = {
|
||||
[ResourceType.APP]: CreateAppsPermissionsObject;
|
||||
[ResourceType.DATA_SOURCE]: CreateDataSourcePermissionsObject;
|
||||
[ResourceType.WORKFLOWS]: CreateWorkflowPermissionsObject;
|
||||
};
|
||||
|
||||
export interface CreateAppsPermissionsObject {
|
||||
export type CreateResourcePermissionObject<T extends ResourceType> = CreateResourcePermissionMap[T];
|
||||
|
||||
export interface CreateBaseAppsPermissionsObject {
|
||||
canEdit?: boolean;
|
||||
canView?: boolean;
|
||||
appType?: APP_TYPES;
|
||||
resourcesToAdd?: GranularPermissionAddResourceItems<ResourceType.APP | ResourceType.WORKFLOWS>;
|
||||
}
|
||||
export interface CreateAppsPermissionsObject extends CreateBaseAppsPermissionsObject {
|
||||
hideFromDashboard?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateWorkflowPermissionsObject extends CreateBaseAppsPermissionsObject {}
|
||||
export interface CreateAppsPermissionsObject extends CreateBaseAppsPermissionsObject {
|
||||
resourcesToAdd?: GranularPermissionAddResourceItems<ResourceType.APP>;
|
||||
}
|
||||
|
||||
export interface CreateWorkflowPermissionsObject extends CreateBaseAppsPermissionsObject {
|
||||
resourcesToAdd?: GranularPermissionAddResourceItems<ResourceType.WORKFLOWS>;
|
||||
}
|
||||
|
||||
export interface CreateDataSourcePermissionsObject {
|
||||
action?: DataSourcesGroupPermissionsActions;
|
||||
resourcesToAdd?: GranularPermissionAddResourceItems<ResourceType.DATA_SOURCE>;
|
||||
|
|
@ -32,10 +50,28 @@ export interface CreateGranularPermissionObject {
|
|||
organizationId: string;
|
||||
}
|
||||
|
||||
export type GranularPermissionAddResourceItems<T extends ResourceType.APP | ResourceType.DATA_SOURCE> =
|
||||
T extends ResourceType.APP ? AppsPermissionAddResourceItem[] : DataSourcesPermissionResourceItem[];
|
||||
type ResourceToPermissionItemMap = {
|
||||
[ResourceType.APP]: AppsPermissionAddResourceItem[];
|
||||
[ResourceType.DATA_SOURCE]: DataSourcesPermissionResourceItem[];
|
||||
[ResourceType.WORKFLOWS]: WorkflowsPermissionAddResourceItem[];
|
||||
};
|
||||
|
||||
export interface AppsPermissionAddResourceItem {
|
||||
export type GranularPermissionAddResourceItems<T extends ResourceType> = ResourceToPermissionItemMap[T];
|
||||
|
||||
interface BaseAppsPermissionAddResourceItem {
|
||||
appId: string;
|
||||
}
|
||||
|
||||
export interface AppsPermissionAddResourceItem extends BaseAppsPermissionAddResourceItem {}
|
||||
|
||||
export interface WorkflowsPermissionAddResourceItem extends BaseAppsPermissionAddResourceItem {}
|
||||
|
||||
interface BaseAppsGroupPermissionsActions {
|
||||
canEdit: boolean;
|
||||
canView: boolean;
|
||||
}
|
||||
|
||||
interface BaseAppsPermissionAddResourceItem {
|
||||
appId: string;
|
||||
}
|
||||
|
||||
|
|
@ -43,12 +79,12 @@ export interface DataSourcesPermissionResourceItem {
|
|||
dataSourceId: string;
|
||||
}
|
||||
|
||||
export interface AppsGroupPermissionsActions {
|
||||
canEdit: boolean;
|
||||
canView: boolean;
|
||||
export interface AppsGroupPermissionsActions extends BaseAppsGroupPermissionsActions {
|
||||
hideFromDashboard: boolean;
|
||||
}
|
||||
|
||||
export interface WorkflowsGroupPermissionsActions extends BaseAppsGroupPermissionsActions {}
|
||||
|
||||
export interface ResourcePermissionMetaData {
|
||||
granularPermissions: GranularPermissions;
|
||||
organizationId: string;
|
||||
|
|
@ -66,7 +102,9 @@ export interface UpdateGranularPermissionObject {
|
|||
updateGranularPermissionDto: UpdateGranularPermissionDto<any>;
|
||||
}
|
||||
|
||||
export interface UpdateResourceGroupPermissionsObject<T extends ResourceType.APP | ResourceType.DATA_SOURCE> {
|
||||
export interface UpdateResourceGroupPermissionsObject<
|
||||
T extends ResourceType.APP | ResourceType.DATA_SOURCE | ResourceType.WORKFLOWS
|
||||
> {
|
||||
group: GroupPermissions;
|
||||
granularPermissions: GranularPermissions;
|
||||
actions: ResourceGroupActions<T>;
|
||||
|
|
@ -81,9 +119,13 @@ export interface GranularPermissionDeleteResourceItem {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export type ResourceGroupActions<T extends ResourceType.APP | ResourceType.DATA_SOURCE> = T extends ResourceType.APP
|
||||
? AppsGroupPermissionsActions
|
||||
: DataSourcesGroupPermissionsActions;
|
||||
type ResourceActionMap = {
|
||||
[ResourceType.APP]: AppsGroupPermissionsActions;
|
||||
[ResourceType.DATA_SOURCE]: DataSourcesGroupPermissionsActions;
|
||||
[ResourceType.WORKFLOWS]: WorkflowsGroupPermissionsActions;
|
||||
};
|
||||
|
||||
export type ResourceGroupActions<T extends ResourceType> = ResourceActionMap[T];
|
||||
|
||||
export interface ValidateResourceAction {
|
||||
isBuilderPermissions: boolean;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export interface CreateDefaultGroupObject {
|
|||
name: string;
|
||||
appCreate?: boolean;
|
||||
appDelete?: boolean;
|
||||
workflowCreate?: boolean;
|
||||
workflowDelete?: boolean;
|
||||
folderCRUD?: boolean;
|
||||
orgConstantCRUD?: boolean;
|
||||
dataSourceCreate?: boolean;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import * as _ from 'lodash';
|
|||
import { DEFAULT_GRANULAR_PERMISSIONS_NAME } from '../constants/granular_permissions';
|
||||
import { RolesRepository } from '@modules/roles/repository';
|
||||
import { IGranularPermissionsUtilService } from '../interfaces/IUtilService';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class GranularPermissionsUtilService implements IGranularPermissionsUtilService {
|
||||
|
|
@ -52,7 +53,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
|
||||
protected validateAppResourcePermissionUpdateOperation(
|
||||
group: GroupPermissions,
|
||||
actions: ResourceGroupActions<ResourceType.APP>
|
||||
actions: ResourceGroupActions<ResourceType.APP | ResourceType.WORKFLOWS>
|
||||
) {
|
||||
if (group.name === USER_ROLE.END_USER && actions.canEdit) {
|
||||
throw new BadRequestException(ERROR_HANDLER.EDITOR_LEVEL_PERMISSION_NOT_ALLOWED_END_USER);
|
||||
|
|
@ -120,7 +121,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
protected async createAppGroupPermission(
|
||||
organizationId: string,
|
||||
granularPermissions: GranularPermissions,
|
||||
createAppPermissionsObj?: CreateResourcePermissionObject<ResourceType.APP>,
|
||||
createAppPermissionsObj?: CreateResourcePermissionObject<ResourceType.APP | ResourceType.WORKFLOWS>,
|
||||
manager?: EntityManager
|
||||
): Promise<void> {
|
||||
const { resourcesToAdd, canEdit } = createAppPermissionsObj;
|
||||
|
|
@ -133,8 +134,8 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
},
|
||||
manager
|
||||
);
|
||||
|
||||
const appGRoupPermissions = await manager.save(
|
||||
createAppPermissionsObj.appType = this.getAppTypeFromResourceType(granularPermissions.type);
|
||||
const appGroupPermissions = await manager.save(
|
||||
manager.create(AppsGroupPermissions, {
|
||||
...createAppPermissionsObj,
|
||||
granularPermissionId: granularPermissions.id,
|
||||
|
|
@ -143,12 +144,23 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
if (resourcesToAdd?.length) {
|
||||
await manager.insert(
|
||||
GroupApps,
|
||||
resourcesToAdd.map((app) => ({ appId: app.appId, appsGroupPermissionsId: appGRoupPermissions.id }))
|
||||
resourcesToAdd.map((app) => ({ appId: app.appId, appsGroupPermissionsId: appGroupPermissions.id }))
|
||||
);
|
||||
}
|
||||
}, manager);
|
||||
}
|
||||
|
||||
private getAppTypeFromResourceType(type: ResourceType) {
|
||||
switch (type) {
|
||||
case ResourceType.APP:
|
||||
return APP_TYPES.FRONT_END;
|
||||
case ResourceType.WORKFLOWS:
|
||||
return APP_TYPES.WORKFLOW;
|
||||
default:
|
||||
throw new BadRequestException('Invalid resource type');
|
||||
}
|
||||
}
|
||||
|
||||
async validateResourceCreation(params: ResourceCreateValidation, manager: EntityManager) {
|
||||
const { groupId, organizationId, isBuilderPermissions } = params;
|
||||
if (!isBuilderPermissions) {
|
||||
|
|
@ -188,6 +200,8 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
appGranularPermission.isAll = true;
|
||||
appGranularPermission.type = ResourceType.APP;
|
||||
appGroupPermissions.canEdit = true;
|
||||
appGroupPermissions.appType = APP_TYPES.FRONT_END;
|
||||
|
||||
return [appGranularPermission];
|
||||
|
||||
case USER_ROLE.END_USER:
|
||||
|
|
@ -195,6 +209,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
appGranularPermission.isAll = true;
|
||||
appGranularPermission.type = ResourceType.APP;
|
||||
appGroupPermissions.canView = true;
|
||||
|
||||
return [appGranularPermission];
|
||||
|
||||
default:
|
||||
|
|
@ -224,6 +239,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
resourcesToAdd,
|
||||
allowRoleChange,
|
||||
};
|
||||
|
||||
await catchDbException(async () => {
|
||||
if (Object.keys(updateGranularPermission).length > 0)
|
||||
await manager.update(GranularPermissions, id, updateGranularPermission);
|
||||
|
|
@ -251,7 +267,9 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
}
|
||||
|
||||
protected async updateAppsGroupPermission(
|
||||
UpdateResourceGroupPermissionsObject: UpdateResourceGroupPermissionsObject<ResourceType.APP>,
|
||||
UpdateResourceGroupPermissionsObject: UpdateResourceGroupPermissionsObject<
|
||||
ResourceType.APP | ResourceType.WORKFLOWS
|
||||
>,
|
||||
organizationId: string,
|
||||
manager?: EntityManager
|
||||
) {
|
||||
|
|
@ -259,7 +277,10 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
|
|||
const { granularPermissions, actions, resourcesToDelete, resourcesToAdd, group, allowRoleChange } =
|
||||
UpdateResourceGroupPermissionsObject;
|
||||
|
||||
this.validateAppResourcePermissionUpdateOperation(group, actions);
|
||||
this.validateAppResourcePermissionUpdateOperation(
|
||||
group,
|
||||
actions as ResourceGroupActions<ResourceType.APP | ResourceType.WORKFLOWS>
|
||||
);
|
||||
const { canEdit } = actions;
|
||||
await this.validateResourceAction(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import { UserRepository } from '@modules/users/repository';
|
|||
import { USER_STATUS, WORKSPACE_USER_STATUS } from '@modules/users/constants/lifecycle';
|
||||
import { IGroupPermissionsUtilService } from './interfaces/IUtilService';
|
||||
import { GroupPermissionLicenseUtilService } from './util-services/license.util.service';
|
||||
import { getTooljetEdition } from '@helpers/utils.helper';
|
||||
import { TOOLJET_EDITIONS } from '@modules/app/constants';
|
||||
|
||||
@Injectable()
|
||||
export class GroupPermissionsUtilService implements IGroupPermissionsUtilService {
|
||||
constructor(
|
||||
|
|
@ -159,6 +162,7 @@ export class GroupPermissionsUtilService implements IGroupPermissionsUtilService
|
|||
CreateResourcePermissionObject<any>
|
||||
> = DEFAULT_RESOURCE_PERMISSIONS[group.name];
|
||||
for (const resource of Object.keys(groupGranularPermissions)) {
|
||||
if (getTooljetEdition() === TOOLJET_EDITIONS.CE && resource == ResourceType.WORKFLOWS) continue;
|
||||
const createResourcePermissionObj: CreateResourcePermissionObject<any> = groupGranularPermissions[resource];
|
||||
|
||||
const dtoObject = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { LicenseTermsService } from '../interfaces/IService';
|
|||
import { EntityManager } from 'typeorm';
|
||||
import { dbTransactionWrap } from '@helpers/database.helper';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class AppCountGuard implements CanActivate {
|
||||
|
|
@ -13,7 +14,7 @@ export class AppCountGuard implements CanActivate {
|
|||
async fetchTotalAppCount(manager: EntityManager): Promise<number> {
|
||||
const apps = await manager.find(App, {
|
||||
where: {
|
||||
type: 'front-end',
|
||||
type: APP_TYPES.FRONT_END,
|
||||
organization: {
|
||||
status: 'active',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ConfigService } from '@nestjs/config';
|
|||
import { LicenseTermsService } from '../interfaces/IService';
|
||||
import { LICENSE_FIELD, LICENSE_LIMIT } from '../constants';
|
||||
import { AppsRepository } from '@modules/apps/repository';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class WebhookGuard implements CanActivate {
|
||||
|
|
@ -21,7 +22,7 @@ export class WebhookGuard implements CanActivate {
|
|||
const workflowApp = await this.appsRepository.findOne({
|
||||
where: {
|
||||
id: request?.params?.id,
|
||||
type: 'workflow',
|
||||
type: APP_TYPES.WORKFLOW,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { CanActivate, ExecutionContext, HttpException, Injectable } from '@nestj
|
|||
import { LicenseTermsService } from '../interfaces/IService';
|
||||
import { LICENSE_FIELD, LICENSE_LIMIT } from '../constants';
|
||||
import { AppsRepository } from '@modules/apps/repository';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowCountGuard implements CanActivate {
|
||||
|
|
@ -24,7 +25,7 @@ export class WorkflowCountGuard implements CanActivate {
|
|||
(await this.appsRepository.count({
|
||||
where: {
|
||||
organizationId: request?.headers['tj-workspace-id'] ?? '',
|
||||
type: 'workflow',
|
||||
type: APP_TYPES.WORKFLOW,
|
||||
},
|
||||
})) >= workflowsLimit.workspace.total
|
||||
) {
|
||||
|
|
@ -37,7 +38,7 @@ export class WorkflowCountGuard implements CanActivate {
|
|||
workflowsLimit.instance.total !== LICENSE_LIMIT.UNLIMITED &&
|
||||
(await this.appsRepository.count({
|
||||
where: {
|
||||
type: 'workflow',
|
||||
type: APP_TYPES.WORKFLOW,
|
||||
},
|
||||
})) >= workflowsLimit.instance.total
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Organization } from '@entities/organization.entity';
|
|||
import { UserRepository } from '@modules/users/repository';
|
||||
import { USER_ROLE } from '@modules/group-permissions/constants';
|
||||
import { ILicenseCountsService } from '../interfaces/IService';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class LicenseCountsService implements ILicenseCountsService {
|
||||
|
|
@ -143,7 +144,7 @@ export class LicenseCountsService implements ILicenseCountsService {
|
|||
fetchTotalWorkflowsCount(workspaceId: string, manager: EntityManager): Promise<number> {
|
||||
return manager.count(App, {
|
||||
where: {
|
||||
type: 'workflow',
|
||||
type: APP_TYPES.WORKFLOW,
|
||||
...(workspaceId && { organizationId: workspaceId }),
|
||||
},
|
||||
});
|
||||
|
|
@ -188,7 +189,7 @@ export class LicenseCountsService implements ILicenseCountsService {
|
|||
async fetchTotalAppCount(manager: EntityManager): Promise<number> {
|
||||
const apps = await manager.find(App, {
|
||||
where: {
|
||||
type: 'front-end',
|
||||
type: APP_TYPES.FRONT_END,
|
||||
organization: {
|
||||
status: 'active',
|
||||
},
|
||||
|
|
|
|||
114
server/src/modules/versions/ability/app-version.ability.ts
Normal file
114
server/src/modules/versions/ability/app-version.ability.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineAppVersionAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
resourceId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission, resource } = UserAllPermissions;
|
||||
const userAppPermissions = userPermission?.[resource[0].resourceType];
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
FEATURE_KEY.CREATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
|
||||
FEATURE_KEY.DELETE_COMPONENTS,
|
||||
FEATURE_KEY.CREATE_PAGES,
|
||||
FEATURE_KEY.CLONE_PAGES,
|
||||
FEATURE_KEY.UPDATE_PAGES,
|
||||
FEATURE_KEY.DELETE_PAGE,
|
||||
FEATURE_KEY.REORDER_PAGES,
|
||||
FEATURE_KEY.GET_EVENTS,
|
||||
FEATURE_KEY.CREATE_EVENT,
|
||||
FEATURE_KEY.UPDATE_EVENT,
|
||||
FEATURE_KEY.DELETE_EVENT,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const isAllEditable = !!userAppPermissions?.isAllEditable;
|
||||
const isAllViewable = !!userAppPermissions?.isAllViewable;
|
||||
|
||||
if (isAllEditable) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
FEATURE_KEY.CREATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
|
||||
FEATURE_KEY.DELETE_COMPONENTS,
|
||||
FEATURE_KEY.CREATE_PAGES,
|
||||
FEATURE_KEY.CLONE_PAGES,
|
||||
FEATURE_KEY.UPDATE_PAGES,
|
||||
FEATURE_KEY.DELETE_PAGE,
|
||||
FEATURE_KEY.REORDER_PAGES,
|
||||
FEATURE_KEY.GET_EVENTS,
|
||||
FEATURE_KEY.CREATE_EVENT,
|
||||
FEATURE_KEY.UPDATE_EVENT,
|
||||
FEATURE_KEY.DELETE_EVENT,
|
||||
],
|
||||
App
|
||||
);
|
||||
} else if (
|
||||
userAppPermissions?.editableAppsId?.length &&
|
||||
resourceId &&
|
||||
userAppPermissions.editableAppsId.includes(resourceId)
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
FEATURE_KEY.CREATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
|
||||
FEATURE_KEY.DELETE_COMPONENTS,
|
||||
FEATURE_KEY.CREATE_PAGES,
|
||||
FEATURE_KEY.CLONE_PAGES,
|
||||
FEATURE_KEY.UPDATE_PAGES,
|
||||
FEATURE_KEY.DELETE_PAGE,
|
||||
FEATURE_KEY.REORDER_PAGES,
|
||||
FEATURE_KEY.GET_EVENTS,
|
||||
FEATURE_KEY.CREATE_EVENT,
|
||||
FEATURE_KEY.UPDATE_EVENT,
|
||||
FEATURE_KEY.DELETE_EVENT,
|
||||
],
|
||||
App
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllViewable) {
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
} else if (
|
||||
userAppPermissions?.viewableAppsId?.length &&
|
||||
resourceId &&
|
||||
userAppPermissions.viewableAppsId.includes(resourceId)
|
||||
) {
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
|
|||
import { ResourceDetails } from '@modules/app/types';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { APP_TYPES } from '@modules/apps/constants';
|
||||
|
||||
@Injectable()
|
||||
export class FeatureAbilityGuard extends AbilityGuard {
|
||||
|
|
@ -16,8 +17,18 @@ export class FeatureAbilityGuard extends AbilityGuard {
|
|||
}
|
||||
|
||||
protected getResource(): ResourceDetails {
|
||||
return {
|
||||
resourceType: MODULES.APP,
|
||||
};
|
||||
const resource: App = this.getResourceObject();
|
||||
switch (resource?.type) {
|
||||
case APP_TYPES.FRONT_END:
|
||||
return {
|
||||
resourceType: MODULES.APP,
|
||||
};
|
||||
case APP_TYPES.WORKFLOW:
|
||||
return {
|
||||
resourceType: MODULES.WORKFLOWS,
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
|
|||
import { AbilityFactory } from '@modules/app/ability-factory';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { createVersionAbility } from './utility';
|
||||
|
||||
type Subjects = InferSubjects<typeof App> | 'all';
|
||||
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
|
||||
|
|
@ -21,80 +21,7 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
extractedMetadata: { moduleName: string; features: string[] },
|
||||
request?: any
|
||||
): void {
|
||||
const appId = request?.tj_resource_id;
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
|
||||
const userAppPermissions = userPermission?.[MODULES.APP];
|
||||
const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
|
||||
const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
|
||||
|
||||
if (isAdmin || superAdmin || isAllAppsEditable) {
|
||||
// Admin or super admin and do all operations
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
FEATURE_KEY.CREATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
|
||||
FEATURE_KEY.DELETE_COMPONENTS,
|
||||
FEATURE_KEY.CREATE_PAGES,
|
||||
FEATURE_KEY.CLONE_PAGES,
|
||||
FEATURE_KEY.UPDATE_PAGES,
|
||||
FEATURE_KEY.DELETE_PAGE,
|
||||
FEATURE_KEY.REORDER_PAGES,
|
||||
FEATURE_KEY.GET_EVENTS,
|
||||
FEATURE_KEY.CREATE_EVENT,
|
||||
FEATURE_KEY.UPDATE_EVENT,
|
||||
FEATURE_KEY.DELETE_EVENT,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId)) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
FEATURE_KEY.CREATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENTS,
|
||||
FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
|
||||
FEATURE_KEY.DELETE_COMPONENTS,
|
||||
FEATURE_KEY.CREATE_PAGES,
|
||||
FEATURE_KEY.CLONE_PAGES,
|
||||
FEATURE_KEY.UPDATE_PAGES,
|
||||
FEATURE_KEY.DELETE_PAGE,
|
||||
FEATURE_KEY.REORDER_PAGES,
|
||||
FEATURE_KEY.GET_EVENTS,
|
||||
FEATURE_KEY.CREATE_EVENT,
|
||||
FEATURE_KEY.UPDATE_EVENT,
|
||||
FEATURE_KEY.DELETE_EVENT,
|
||||
],
|
||||
App
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllAppsViewable) {
|
||||
// add view permissions for all apps
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
} else if (
|
||||
userAppPermissions?.viewableAppsId?.length &&
|
||||
appId &&
|
||||
userAppPermissions.viewableAppsId.includes(appId)
|
||||
) {
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
}
|
||||
const resourceId = request?.tj_resource_id;
|
||||
createVersionAbility(can, UserAllPermissions, resourceId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
server/src/modules/versions/ability/utility.ts
Normal file
25
server/src/modules/versions/ability/utility.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { FeatureAbility } from './index';
|
||||
import { defineAppVersionAbility } from './app-version.ability';
|
||||
import { defineWorkflowVersionAbility } from './workflow-version.ability';
|
||||
|
||||
export function createVersionAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
resourceId?: string
|
||||
): void {
|
||||
const resourceType = UserAllPermissions.resource[0].resourceType ? UserAllPermissions.resource[0].resourceType : null;
|
||||
|
||||
switch (resourceType) {
|
||||
case MODULES.APP:
|
||||
defineAppVersionAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
case MODULES.WORKFLOWS:
|
||||
defineWorkflowVersionAbility(can, UserAllPermissions, resourceId);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported resource type: ${resourceType}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import { AbilityBuilder } from '@casl/ability';
|
||||
import { UserAllPermissions } from '@modules/app/types';
|
||||
import { FEATURE_KEY } from '../constants';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { FeatureAbility } from './index';
|
||||
|
||||
export function defineWorkflowVersionAbility(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
resourceId?: string
|
||||
): void {
|
||||
const { superAdmin, isAdmin, userPermission, resource } = UserAllPermissions;
|
||||
const userWorkflowPermissions = userPermission?.[resource[0].resourceType];
|
||||
|
||||
if (isAdmin || superAdmin) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
],
|
||||
App
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const isAllEditable = !!userWorkflowPermissions?.isAllEditable;
|
||||
const isAllExecutable = !!userWorkflowPermissions?.isAllExecutable;
|
||||
|
||||
if (isAllEditable) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
],
|
||||
App
|
||||
);
|
||||
} else if (
|
||||
userWorkflowPermissions?.editableWorkflowsId?.length &&
|
||||
resourceId &&
|
||||
userWorkflowPermissions.editableWorkflowsId.includes(resourceId)
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.GET,
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.CREATE,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.UPDATE_SETTINGS,
|
||||
FEATURE_KEY.PROMOTE,
|
||||
],
|
||||
App
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllExecutable) {
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
} else if (
|
||||
userWorkflowPermissions?.executableWorkflowsId?.length &&
|
||||
resourceId &&
|
||||
userWorkflowPermissions.executableWorkflowsId.includes(resourceId)
|
||||
) {
|
||||
can([FEATURE_KEY.GET_EVENTS], App);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue