diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index 9a7e8a6f..74933ed1 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -5,10 +5,12 @@ from typing import List from fastapi import HTTPException from sqlalchemy import and_, text, cast, or_, func from sqlalchemy.dialects.postgresql import JSONB -from sqlbot_xpack.permissions.models.ds_permission import DsPermission +from sqlbot_xpack.permissions.api.permission import transRecord2DTO +from sqlbot_xpack.permissions.models.ds_permission import DsPermission, PermissionDTO from sqlbot_xpack.permissions.models.ds_rules import DsRules from sqlmodel import select +from apps.datasource.crud.row_permission import transFilterTree from apps.datasource.utils.utils import aes_decrypt from apps.db.constant import DB from apps.db.db import get_engine, get_tables, get_fields, exec_sql @@ -238,6 +240,9 @@ def updateField(session: SessionDep, field: CoreField): def preview(session: SessionDep, current_user: CurrentUser, id: int, data: TableObj): + ds = session.query(CoreDatasource).filter(CoreDatasource.id == id).first() + check_status(session, ds, True) + if data.fields is None or len(data.fields) == 0: return {"fields": [], "data": [], "sql": ''} @@ -261,20 +266,41 @@ def preview(session: SessionDep, current_user: CurrentUser, id: int, data: Table if fields is None or len(fields) == 0: return {"fields": [], "data": [], "sql": ''} - ds = session.query(CoreDatasource).filter(CoreDatasource.id == id).first() - check_status(session, ds, True) + # row permission tree + row_permissions = session.query(DsPermission).filter( + and_(DsPermission.table_id == data.table.id, DsPermission.type == 'row')).all() + res: List[PermissionDTO] = [] + if row_permissions is not None: + for permission in row_permissions: + # check permission and user in same rules + obj = session.query(DsRules).filter( + and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)), + or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)), + DsRules.user_list.op('@>')(cast([current_user.id], JSONB)))) + ).first() + if obj is not None: + res.append(transRecord2DTO(session, permission)) + wheres = transFilterTree(session, res, ds) + where = (' where ' + wheres) if wheres is not None and wheres != '' else '' + conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config() sql: str = "" if ds.type == "mysql": - sql = f"""SELECT `{"`, `".join(fields)}` FROM `{data.table.table_name}` LIMIT 100""" + sql = f"""SELECT `{"`, `".join(fields)}` FROM `{data.table.table_name}` + {where} + LIMIT 100""" elif ds.type == "sqlServer": sql = f"""SELECT [{"], [".join(fields)}] FROM [{conf.dbSchema}].[{data.table.table_name}] + {where} ORDER BY [{data.fields[0].field_name}] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY""" elif ds.type == "pg" or ds.type == "excel": - sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" LIMIT 100""" + sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" + {where} + LIMIT 100""" elif ds.type == "oracle": sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" + {where} ORDER BY "{data.fields[0].field_name}" OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY""" return exec_sql(ds, sql) diff --git a/backend/apps/datasource/crud/row_permission.py b/backend/apps/datasource/crud/row_permission.py index b9b84558..84f79cca 100644 --- a/backend/apps/datasource/crud/row_permission.py +++ b/backend/apps/datasource/crud/row_permission.py @@ -3,8 +3,10 @@ from typing import List, Dict -from apps.datasource.models.datasource import CoreField, CoreDatasource from sqlbot_xpack.permissions.models.ds_permission import PermissionTree, PermissionDTO + +from apps.datasource.models.datasource import CoreField, CoreDatasource +from apps.db.constant import DB from common.core.deps import SessionDep @@ -48,7 +50,8 @@ def transTreeItem(session: SessionDep, item: Dict, ds: CoreDatasource) -> str | if field is None: return None - whereName = field.field_name + db = DB.get_db(ds.type) + whereName = db.prefix + field.field_name + db.suffix if item['filter_type'] == 'enum': if len(item['enum_value']) > 0: if ds['type'] == 'sqlServer' and ( diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py index d5a7a1db..cb4e9fdb 100644 --- a/backend/apps/system/api/user.py +++ b/backend/apps/system/api/user.py @@ -95,7 +95,7 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran return await user_ws_options(session, current_user.id, trans) @router.put("/ws/{oid}") -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int): ws_list: list[UserWs] = await user_ws_options(session, current_user.id) if not any(x.id == oid for x in ws_list): @@ -137,7 +137,7 @@ async def create(session: SessionDep, creator: UserCreator): session.commit() @router.put("") -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id") async def update(session: SessionDep, editor: UserEditor): user_model: UserModel = get_db_user(session = session, user_id = editor.id) origin_oid: int = user_model.oid @@ -173,7 +173,7 @@ async def batch_del(session: SessionDep, id_list: list[int]): await single_delete(session, id) @router.put("/language") -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage): lang = language.language if lang not in ["zh-CN", "en"]: @@ -184,7 +184,7 @@ async def langChange(session: SessionDep, current_user: CurrentUser, language: U session.commit() @router.patch("/pwd/{id}") -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int): if not current_user.isAdmin: raise HTTPException('only for admin') @@ -194,7 +194,7 @@ async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int): session.commit() @router.put("/pwd") -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor): db_user: UserModel = get_db_user(session=session, user_id=current_user.id) if not verify_md5pwd(editor.pwd, db_user.password): diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py index ffad38a8..fa0166e8 100644 --- a/backend/apps/system/crud/user.py +++ b/backend/apps/system/crud/user.py @@ -22,7 +22,7 @@ def get_user_by_account(*, session: Session, account: str) -> BaseUserDTO | None return None return BaseUserDTO.model_validate(db_user.model_dump()) -#@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id") +@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id") async def get_user_info(*, session: Session, user_id: int) -> UserInfoDTO | None: db_user: UserModel = get_db_user(session = session, user_id = user_id) userInfo = UserInfoDTO.model_validate(db_user.model_dump()) @@ -58,7 +58,7 @@ async def user_ws_options(session: Session, uid: int, trans: Optional[I18n] = No for id, name in result.all() ] -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") async def single_delete(session: SessionDep, id: int): user_model: UserModel = get_db_user(session = session, user_id = id) del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == id) @@ -66,6 +66,6 @@ async def single_delete(session: SessionDep, id: int): session.delete(user_model) session.commit() -#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") +@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") async def clean_user_cache(id: int): SQLBotLogUtil.info(f"User cache for [{id}] has been cleaned") diff --git a/frontend/src/components/layout/Workspace.vue b/frontend/src/components/layout/Workspace.vue index 6c4d34e5..da669173 100644 --- a/frontend/src/components/layout/Workspace.vue +++ b/frontend/src/components/layout/Workspace.vue @@ -6,13 +6,11 @@ import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg' import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg' import { userApi } from '@/api/auth' import { ElMessage } from 'element-plus-secondary' -import { useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' import { useUserStore } from '@/stores/user' const userStore = useUserStore() const { t } = useI18n() -const router = useRouter() defineProps({ collapse: { type: [Boolean], required: true }, }) @@ -46,7 +44,6 @@ const handleDefaultWorkspaceChange = (item: any) => { currentWorkspace.value = { id: item.id, name: item.name } userApi.ws_change(item.id).then(() => { ElMessage.success(t('common.switch_success')) - router.push('/chat/index') setTimeout(() => { location.reload() }, 300) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 8a75f65d..fb34662f 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -114,38 +114,6 @@ const router = createRouter({ component: DashboardPreview, meta: { title: 'DashboardPreview', icon: 'dashboard' }, }, - /* { - path: "/setting", - component: Layout, - redirect: "/setting/index", - children: [ - { - path: "index", - name: "setting", - component: setting, - meta: { title: "Settings", icon: "setting" }, - }, - ], - }, */ - // { - // path: '/system', - // component: Layout, - // redirect: '/system/model', - // children: [ - // /* { - // path: "user", - // name: "user", - // component: User, - // meta: { title: "User Management", icon: "icon_user" }, - // }, */ - // { - // path: 'model', - // name: 'model', - // component: Model, - // meta: { title: 'AI Model Configuration', icon: 'icon_ai' }, - // }, - // ], - // }, { path: '/system', component: LayoutDsl, diff --git a/frontend/src/router/watch.ts b/frontend/src/router/watch.ts index 14bf2ad9..b480d2ca 100644 --- a/frontend/src/router/watch.ts +++ b/frontend/src/router/watch.ts @@ -26,10 +26,7 @@ export const watchRouter = (router: any) => { if (!userStore.getUid) { await userStore.info() } - /* if (!wsCache.get('user.uid')) { - await userStore.info() - } */ - if (to.path === '/') { + if (to.path === '/' || accessCrossPermission(to)) { next('/chat') return } @@ -41,6 +38,14 @@ export const watchRouter = (router: any) => { } }) } + +const accessCrossPermission = (to: any) => { + if (!to?.path) return false + return ( + (to.path.startsWith('/system') && !userStore.isAdmin) || + (to.path.startsWith('/set') && !userStore.isSpaceAdmin) + ) +} const loadXpackStatic = () => { if (document.getElementById('sqlbot_xpack_static')) { return Promise.resolve()