diff --git a/.gitignore b/.gitignore index fe0247034..a7ac21b4f 100644 --- a/.gitignore +++ b/.gitignore @@ -201,4 +201,7 @@ arkid_celery_tasks .devcontainer/ storage/ results.sqlite -tasks/ + +tasks/* +~/* +tasks diff --git a/api/v1/pages/app_manage/app_list.py b/api/v1/pages/app_manage/app_list.py index 7ad44a108..94ac778ee 100644 --- a/api/v1/pages/app_manage/app_list.py +++ b/api/v1/pages/app_manage/app_list.py @@ -341,7 +341,7 @@ global_actions={ 'next': actions.NextAction( name="已支付", - path="/api/v1/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/", + path="/api/v1/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/extensions/{uuid}/", method=actions.FrontActionMethod.GET ), } diff --git a/api/v1/pages/extension_manage/extension_admin.py b/api/v1/pages/extension_manage/extension_admin.py index 294f6c7fb..7164895c4 100644 --- a/api/v1/pages/extension_manage/extension_admin.py +++ b/api/v1/pages/extension_manage/extension_admin.py @@ -315,7 +315,7 @@ global_actions={ 'next': actions.NextAction( name="已支付", - path="/api/v1/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/", + path="/api/v1/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/extensions/{uuid}/", method=actions.FrontActionMethod.GET ), } diff --git a/api/v1/schema/tenant.py b/api/v1/schema/tenant.py index f18ea9af0..c963f7e1d 100644 --- a/api/v1/schema/tenant.py +++ b/api/v1/schema/tenant.py @@ -39,6 +39,13 @@ class TenantOut(ResponseSchema): data: TenantItemOut class TenantCreateIn(ModelSchema): + + slug:str = Field( + title=_("短链接标识"), + format="^[a-z0-9]{1,24}$", + feedback=_("输入错误,必须为24位以内数字和小写字母的组合") + ) + class Config: model = Tenant model_fields = ["name","slug","icon"] diff --git a/api/v1/schema/user.py b/api/v1/schema/user.py index ae4824722..0a46a4a37 100644 --- a/api/v1/schema/user.py +++ b/api/v1/schema/user.py @@ -7,8 +7,15 @@ from arkid.core.schema import ResponseSchema class UserListQueryIn(Schema): - name:str = Field( - default=None + order:str = Field( + default=None, + title=_("排序字段"), + notranslation=True + ) + username:str = Field( + default=None, + title=_("用户名"), + notranslation=True ) class UserListItemOut(ModelSchema): @@ -24,6 +31,20 @@ class Config: class UserListOut(ResponseSchema): data: List[UserListItemOut] + +class UserPullItemOut(ModelSchema): + + class Config: + model = User + model_fields = ['id', 'username', 'avatar','is_platform_user','is_del','is_active','created','updated'] + + created:Any = Field( + title=_("注册时间") + ) + +class UserPullOut(ResponseSchema): + data: List[UserPullItemOut] + class UserCreateIn(ModelSchema): class Config: model = User diff --git a/api/v1/schema/user_group.py b/api/v1/schema/user_group.py index 0d5485035..b93385ac7 100644 --- a/api/v1/schema/user_group.py +++ b/api/v1/schema/user_group.py @@ -34,6 +34,16 @@ class UserGroupListOut(ResponseSchema): data: List[UserGroupListItemOut] +class UserGroupPullItemOut(ModelSchema): + parent_id:UUID = Field(default=None, alias='parent.id') + class Config: + model = UserGroup + model_fields = ['id', 'name','is_del','is_active','created','updated'] + +class UserGroupPullOut(ResponseSchema): + + data: List[UserGroupPullItemOut] + class UserGroupCreateOut(ResponseSchema): pass diff --git a/api/v1/views/arkstore.py b/api/v1/views/arkstore.py index f8150005a..072cc3da1 100644 --- a/api/v1/views/arkstore.py +++ b/api/v1/views/arkstore.py @@ -40,7 +40,7 @@ import enum from pydantic import Field from ninja.pagination import paginate -from arkid.core.pagenation import CustomPagination, ArstorePagination +from arkid.core.pagenation import CustomPagination, ArstorePagination, ArstoreExtensionPagination from arkid.extension.models import Extension, TenantExtension, ArkStoreCategory from arkid.core.translation import gettext_default as _ from pydantic import condecimal, conint @@ -138,6 +138,8 @@ class OnShelveExtensionPurchaseOut(ArkstoreExtensionItemSchemaOut): purchase_useful_life: Optional[List[str]] = Field( title=_('Purchase Useful Life', '有效期') ) + install: Optional[bool] = Field(title=_("Install", "安装"), default=False, hidden=True) + upgrade: Optional[bool] = Field(title=_("Upgrade", "升级"), default=False, hidden=True) class OrderStatusSchema(Schema): @@ -275,7 +277,7 @@ class ArkstoreAppQueryIn(Schema): @api.get("/tenant/{tenant_id}/arkstore/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) @operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) -@paginate(ArstorePagination) +@paginate(ArstoreExtensionPagination) def list_arkstore_extensions(request, tenant_id: str, query_data: ArkstoreExtensionQueryIn=Query(...)): query_data = query_data.dict() return get_arkstore_list(request, None, 'extension', extra_params=query_data) @@ -376,7 +378,7 @@ def get_arkstore_category_http(): @api.get("/tenant/{tenant_id}/arkstore/purchased/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) @operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) -@paginate(ArstorePagination) +@paginate(ArstoreExtensionPagination) def list_arkstore_purchased_extensions(request, tenant_id: str, category_id: str = None): extra_params = {} if category_id and category_id != "" and category_id != "0": @@ -384,6 +386,36 @@ def list_arkstore_purchased_extensions(request, tenant_id: str, category_id: str return get_arkstore_list(request, True, 'extension', extra_params=extra_params) +@api.get("/tenant/{tenant_id}/arkstore/not_installed/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) +@operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) +@paginate(CustomPagination) +def list_arkstore_not_installed_extensions(request, tenant_id: str, category_id: str = None): + extra_params = {} + if category_id and category_id != "" and category_id != "0": + extra_params['category_id'] = category_id + installed_exts = Extension.valid_objects.filter() + installed_ext_packages = set(str(ext.package) for ext in installed_exts) + purchased_exts = get_arkstore_list(request, True, 'extension', all=True, extra_params=extra_params)['items'] + return [ext for ext in purchased_exts if ext['package'] not in installed_ext_packages] + + +@api.get("/tenant/{tenant_id}/arkstore/not_upgraded/extensions/", tags=['方舟商店'], response=List[OnShelveExtensionPurchaseOut]) +@operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) +@paginate(CustomPagination) +def list_arkstore_not_upgraded_extensions(request, tenant_id: str, category_id: str = None): + extra_params = {} + if category_id and category_id != "" and category_id != "0": + extra_params['category_id'] = category_id + installed_exts = Extension.valid_objects.filter() + installed_ext_packages = {ext.package: ext for ext in installed_exts} + purchased_exts = get_arkstore_list(request, True, 'extension', all=True, extra_params=extra_params)['items'] + exts = [] + for ext in purchased_exts: + if ext['package'] in installed_ext_packages and installed_ext_packages[ext['package']].version < ext['version']: + exts.append(ext) + return exts + + @api.get("/tenant/{tenant_id}/arkstore/purchased/apps/", tags=['方舟商店'], response=List[ArkstoreAppItemSchemaOut]) @operation(List[ArkstoreItemSchemaOut], roles=[TENANT_ADMIN, PLATFORM_ADMIN]) @paginate(CustomPagination) @@ -438,13 +470,13 @@ def get_order_payment_arkstore_extension(request, tenant_id: str, order_no: str) return {'data': resp} -@api.get("/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/", tags=['方舟商店'], +@api.get("/tenant/{tenant_id}/arkstore/purchase/order/{order_no}/payment_status/extensions/{uuid}/", tags=['方舟商店'], response={ 200: PaymentStatus, 202: ResponseSchema, }) @operation(roles=[TENANT_ADMIN, PLATFORM_ADMIN]) -def get_order_payment_status_arkstore_extension(request, tenant_id: str, order_no: str): +def get_order_payment_status_arkstore_extension(request, tenant_id: str, order_no: str, uuid: str): token = request.user.auth_token tenant = Tenant.objects.get(id=tenant_id) access_token = get_arkstore_access_token(tenant, token) @@ -452,6 +484,9 @@ def get_order_payment_status_arkstore_extension(request, tenant_id: str, order_n if resp.get('code') == '0' and not resp.get('appid'): return 202, {'data': resp} else: + # install extension + if resp.get('trade_state') == 'SUCCESS': + install_arkstore_extension(tenant, token, uuid) return 200, resp @@ -578,6 +613,8 @@ def create_order_arkstore_extension_trial(request, tenant_id: str, uuid: str): resp = trial_arkstore_extension(access_token, uuid) if resp.get('code') == '10003': return ErrorDict(ErrorCode.TRIAL_EXTENSION_TWICE) + # install extension + install_arkstore_extension(tenant, token, uuid) return {'data': resp} diff --git a/api/v1/views/extension.py b/api/v1/views/extension.py index c327f01fc..1a40b0913 100644 --- a/api/v1/views/extension.py +++ b/api/v1/views/extension.py @@ -10,7 +10,7 @@ from arkid.core.constants import NORMAL_USER, PLATFORM_ADMIN, TENANT_ADMIN from arkid.core.extension import Extension from arkid.core.schema import ResponseSchema -from arkid.extension.utils import import_extension +from arkid.extension.utils import import_extension, restart_celery from arkid.extension.models import TenantExtensionConfig, Extension as ExtensionModel from arkid.core.error import ErrorCode, ErrorDict from ninja.pagination import paginate @@ -74,8 +74,10 @@ class ExtensionListOut(ModelSchema): class Config: model= ExtensionModel - model_fields=["id","name","type","package","labels","version","is_active","is_allow_use_platform_config"] - + model_fields=["id","name","type","package","labels","version","is_active","is_allow_use_platform_config", + "author", "logo", "homepage" + ] + labels:Optional[List[str]] is_active: bool = Field( title='是否启动', @@ -230,6 +232,7 @@ def toggle_extension_active_status(request, id: str): extension.is_active = True extension.save() + restart_celery() return ErrorDict(ErrorCode.OK) @api.post("/extensions/{id}/use_platform_config/toggle/", tags=["平台插件"]) diff --git a/api/v1/views/permission.py b/api/v1/views/permission.py index 05a4ec245..020674b7f 100644 --- a/api/v1/views/permission.py +++ b/api/v1/views/permission.py @@ -496,7 +496,9 @@ def permission_toggle_open(request, tenant_id: str, permission_id: str): if permission and permission.tenant is None: return ErrorDict(ErrorCode.SYSTEM_PERMISSION_NOT_OPERATION) if permission is None: - permission = Permission.valid_objects.filter(tenant_id=tenant_id, id=permission_id).first() + permission = Permission.valid_objects.filter(id=permission_id).first() + if str(permission.tenant_id) != tenant_id: + return ErrorDict(ErrorCode.PERMISSION_NOT_BELONG_TO_TENANT) if permission: is_open = permission.is_open if is_open: @@ -571,7 +573,9 @@ def permission_toggle_other_user_open(request, tenant_id: str, permission_id: st if permission and permission.tenant is None: return ErrorDict(ErrorCode.SYSTEM_PERMISSION_NOT_OPERATION) if permission is None: - permission = Permission.valid_objects.filter(tenant_id=tenant_id, id=permission_id).first() + permission = Permission.valid_objects.filter(id=permission_id).first() + if str(permission.tenant_id) != tenant_id: + return ErrorDict(ErrorCode.PERMISSION_NOT_BELONG_TO_TENANT) if permission: is_open_other_user = permission.is_open_other_user if is_open_other_user: diff --git a/api/v1/views/permission_group.py b/api/v1/views/permission_group.py index f7f9b88ac..ce4661ec2 100644 --- a/api/v1/views/permission_group.py +++ b/api/v1/views/permission_group.py @@ -134,6 +134,7 @@ def delete_permission_group(request, tenant_id: str, id: str): def get_permissions_from_group(request, tenant_id: str, permission_group_id: str, category: str = None, operation_id: str = None): """ 获取当前分组的权限列表 """ + from arkid.core.perm.permission_data import PermissionData if permission_group_id != 'arkid': permission = SystemPermission.valid_objects.filter(id=permission_group_id).first() if permission is None: @@ -151,15 +152,6 @@ def get_permissions_from_group(request, tenant_id: str, permission_group_id: str app = App.valid_objects.filter(id=permission_group_id).first() items = [] if app: - if app.entry_permission: - if category and category in app.entry_permission.category: - category = category.strip() - items.append(app.entry_permission) - elif operation_id and operation_id in app.entry_permission.operation_id: - operation_id = operation_id.strip() - items.append(app.entry_permission) - else: - items.append(app.entry_permission) app_permission_ids = [] base_permissions = Permission.valid_objects.filter( app_id=app.id, @@ -187,10 +179,25 @@ def get_permissions_from_group(request, tenant_id: str, permission_group_id: str operation_id = operation_id.strip() group_permission_details = group_permission_details.filter(operation_id__icontains=operation_id) items.extend(group_permission_details) + entry_permission = None + if app.entry_permission: + if category and category in app.entry_permission.category: + category = category.strip() + entry_permission = app.entry_permission + elif operation_id and operation_id in app.entry_permission.operation_id: + operation_id = operation_id.strip() + entry_permission = app.entry_permission + else: + entry_permission = app.entry_permission + # 需要过滤展示 + permissiondata = PermissionData() + items = permissiondata.get_permissions_by_app_filter(tenant_id, app.id, items, entry_permission, request.user) return items else: permissions = SystemPermission.valid_objects.filter(category='group', is_system=True) - return permissions + # 只能看到自己拥有的权限 + permissiondata = PermissionData() + return permissiondata.get_system_permission_by_filter(tenant_id, permissions, request.user) # tenant = request.tenant # if tenant.is_platform_tenant: # permission = get_object_or_404(SystemPermission, id=permission_group_id, is_del=False) diff --git a/api/v1/views/user.py b/api/v1/views/user.py index ebe1664c3..d005f3fe7 100644 --- a/api/v1/views/user.py +++ b/api/v1/views/user.py @@ -13,7 +13,7 @@ UserCreateIn, UserCreateOut, UserDeleteOut, UserListItemOut, UserListOut, UserListQueryIn, UserOut, UserUpdateIn, UserUpdateOut, - UserFieldsOut, + UserFieldsOut, UserPullOut, UserPullItemOut, ) from arkid.core.error import ErrorCode, ErrorDict from arkid.core.constants import NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN @@ -25,13 +25,13 @@ @api.get("/tenant/{tenant_id}/users/",response=List[UserListItemOut], tags=['用户']) @operation(UserListOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN]) @paginate(CustomPagination) -def user_list(request, tenant_id: str,order:str = None, query_data: UserListQueryIn=Query(...)): +def user_list(request, tenant_id: str, query_data: UserListQueryIn=Query(...)): from arkid.core.perm.permission_data import PermissionData users = User.expand_objects.filter(tenant_id=tenant_id, is_del=False) - - if order: - users = users.order_by(order) - + if query_data.username: + users = users.filter(username__icontains=query_data.username) + if query_data.order: + users = users.order_by(query_data.order) login_user = request.user tenant = request.tenant pd = PermissionData() @@ -42,6 +42,8 @@ def user_list(request, tenant_id: str,order:str = None, query_data: UserListQuer return list(users) + + @api.get("/tenant/{tenant_id}/user_no_super/",response=UserListOut, tags=['用户']) @operation(UserListOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN]) # @paginate(CustomPagination) @@ -60,14 +62,14 @@ def user_list_no_super(request, tenant_id: str): @api.post("/tenant/{tenant_id}/users/",response=UserCreateOut, tags=['用户']) @operation(UserCreateOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN]) def user_create(request, tenant_id: str,data:UserCreateIn): - + tenant = request.tenant # user = User.expand_objects.create(tenant=request.tenant,**data.dict()) - if User.objects.filter(tenant=request.tenant, username=data.username).count(): + if User.objects.filter(tenant=tenant, username=data.username).count(): return ErrorDict( ErrorCode.USERNAME_EXISTS_ERROR ) - user = User.objects.create(tenant=request.tenant, username=data.username) + user = User.objects.create(tenant=tenant, username=data.username) for key,value in data.dict().items(): if key=='username': continue @@ -75,8 +77,22 @@ def user_create(request, tenant_id: str,data:UserCreateIn): setattr(user,key,value) user.save() + tenant.users.add(user) + tenant.save() return {"data":{"user":user.id.hex}} +@api.get("/tenant/{tenant_id}/users/pull/",response=List[UserPullItemOut], tags=['用户']) +@operation(UserPullOut,roles=[PLATFORM_ADMIN]) +@paginate(CustomPagination) +def user_pull(request, tenant_id: str): + ''' + 拉取用户 + ''' + users = User.objects.filter( + tenant_id=tenant_id + ).order_by('created') + return users + # ------------- 删除用户接口 -------------- @api.delete("/tenant/{tenant_id}/users/{id}/",response=UserDeleteOut, tags=['用户']) @operation(UserDeleteOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN]) diff --git a/api/v1/views/user_group.py b/api/v1/views/user_group.py index d22a45b6d..6198e6335 100644 --- a/api/v1/views/user_group.py +++ b/api/v1/views/user_group.py @@ -72,6 +72,22 @@ def list_groups(request, tenant_id: str, parent_id: str = None): return {"data": list(usergroups.all())} +@api.get("/tenant/{tenant_id}/user_groups/pull/", response=List[UserGroupPullItemOut], tags=['用户分组']) +@operation(UserGroupPullOut, roles=[PLATFORM_ADMIN]) +@paginate(CustomPagination) +def user_group_pull(request, tenant_id: str, parent_id: str = ''): + ''' + 拉取用户分组 + ''' + usergroups = UserGroup.objects.filter( + tenant_id=tenant_id, + ) + if parent_id != '': + usergroups = usergroups.filter(parent__id=parent_id) + usergroups = usergroups.order_by('created') + return usergroups + + @api.get("/tenant/{tenant_id}/user_groups/{id}/", response=UserGroupDetailOut, tags=['用户分组']) @operation(roles=[TENANT_ADMIN, PLATFORM_ADMIN]) def get_group(request, tenant_id: str, id: str): diff --git a/arkid/common/arkstore.py b/arkid/common/arkstore.py index 0b2036dd1..ca0d2755f 100644 --- a/arkid/common/arkstore.py +++ b/arkid/common/arkstore.py @@ -13,7 +13,7 @@ from django.db import transaction from arkid.core.models import Tenant from arkid.extension.models import TenantExtension, Extension -from arkid.extension.utils import import_extension, unload_extension, load_extension_apps +from arkid.extension.utils import import_extension, unload_extension, load_extension_apps, restart_celery from pathlib import Path from arkid.common.logger import logger @@ -363,6 +363,7 @@ def download_arkstore_extension(tenant, token, extension_id, extension_detail): logger.exception(f'load download extension: {ext_package} failed: {str(e)}') return {'success': 'failed'} + restart_celery() return {'success': 'true'} diff --git a/arkid/core/error.py b/arkid/core/error.py index 941f2779c..a07ea489e 100644 --- a/arkid/core/error.py +++ b/arkid/core/error.py @@ -49,7 +49,8 @@ class ErrorCode(Enum): BAN_REMOVE_GROUP_SCOPE = ('10036', _('ban remove group permission', '该分组范围不允许移除')) PERMISSION_GROUP_NOT_EDIT = ('10037', _('the permission group not edit', '该分组权限不允许编辑')) PERMISSION_GROUP_NOT_DELETE = ('10038', _('the permission group not delete', '该分组权限不允许删除')) - SYSTEM_PERMISSION_NOT_OPERATION = ('10033', _('system permission not operation', '系统权限不支持此操作')) + SYSTEM_PERMISSION_NOT_OPERATION = ('10039', _('system permission not operation', '系统权限不支持此操作')) + PERMISSION_NOT_BELONG_TO_TENANT = ('10040', _('permission not belong to tenant', '该应用不属于该租户')) # SMS_PROVIDER_IS_MISSING = '11001' # AUTHCODE_PROVIDER_IS_MISSING = '11002' diff --git a/arkid/core/pagenation.py b/arkid/core/pagenation.py index 3c777ab15..2a69174c6 100644 --- a/arkid/core/pagenation.py +++ b/arkid/core/pagenation.py @@ -59,3 +59,33 @@ def paginate_queryset(self, queryset, pagination: CustomPagination.Input, reques "previous": f"{request.path}?page={page-1}&page_size={page_size}" if page > 2 else "", "next": f"{request.path}?page={page+1}&page_size={page_size}" if page * page_size < len(list(queryset)) else "" } + + +class ArstoreExtensionPagination(CustomPagination): + def paginate_queryset(self, queryset, pagination: CustomPagination.Input, request, **params): + + if isinstance(queryset,dict) and "error" in queryset.keys() and queryset.get("error") not in ["0",0]: + queryset["items"] = [] + return queryset + + page = pagination.page + page_size = pagination.page_size + items = queryset["items"] + count = queryset["count"] + + from arkid.extension.models import Extension + installed_exts = Extension.valid_objects.filter() + installed_ext_packages = {ext.package: ext for ext in installed_exts} + for ext in items: + if ext['package'] in installed_ext_packages: + if installed_ext_packages[ext['package']].version < ext['version']: + ext['upgrade'] = True + else: + ext['install'] = True + + return { + 'items': items, + 'count': count, + "previous": f"{request.path}?page={page-1}&page_size={page_size}" if page > 2 else "", + "next": f"{request.path}?page={page+1}&page_size={page_size}" if page * page_size < len(list(queryset)) else "" + } diff --git a/arkid/core/perm/permission_data.py b/arkid/core/perm/permission_data.py index 9272d70d2..3a8a275e0 100644 --- a/arkid/core/perm/permission_data.py +++ b/arkid/core/perm/permission_data.py @@ -1495,6 +1495,76 @@ def get_app_permissions_by_search(self, tenant_id, app_id, category = None, oper result.extend(list(result_items.order_by('sort_id'))) return result + + def get_system_permission_by_filter(self, tenant_id, items, login_user): + ''' + 根据已有的系统权限,作过滤 + ''' + sort_ids = [] + result = [] + compress = Compress() + for item in items: + sort_ids.append(item.sort_id) + userpermissionresult = UserPermissionResult.valid_objects.filter( + app=None, + user=login_user, + tenant_id=tenant_id + ).first() + permission_sort_ids = [] + if userpermissionresult: + permission_result = compress.decrypt(userpermissionresult.result) + permission_result_arr = list(permission_result) + for index, item in enumerate(permission_result_arr): + if int(item) == 1 and index in sort_ids: + permission_sort_ids.append(index) + for item in items: + if item.sort_id in permission_sort_ids: + result.append(item) + return result + + + def get_permissions_by_app_filter(self, tenant_id, app_id, items, entry_permission, login_user): + ''' + 根据已经有的应用权限,作过滤 + ''' + sort_ids = [] + result = [] + compress = Compress() + for item in items: + sort_ids.append(item.sort_id) + if sort_ids: + # 只展示为1的应用权限 + userpermissionresult = UserPermissionResult.valid_objects.filter( + app_id=app_id, + user=login_user, + tenant_id=tenant_id + ).first() + permission_sort_ids = [] + if userpermissionresult: + permission_result = compress.decrypt(userpermissionresult.result) + permission_result_arr = list(permission_result) + for index, item in enumerate(permission_result_arr): + if int(item) == 1 and index in sort_ids: + permission_sort_ids.append(index) + for item in items: + if item.sort_id in permission_sort_ids: + result.append(item) + # 只展示为1的系统权限 + if entry_permission: + userpermissionresult = UserPermissionResult.valid_objects.filter( + app=None, + user=login_user, + tenant_id=tenant_id + ).first() + permission_sort_ids = [] + if userpermissionresult: + permission_result = compress.decrypt(userpermissionresult.result) + permission_result_arr = list(permission_result) + for index, item in enumerate(permission_result_arr): + if int(item) == 1 and index == entry_permission.sort_id: + result.insert(0, entry_permission) + return result + def get_permissions_by_search(self, tenant_id, app_id, user_id, group_id, login_user, parent_id=None, is_only_show_group=False, app_name=None, category=None, operation_id=None): ''' diff --git a/arkid/core/tasks/celery.py b/arkid/core/tasks/celery.py index a598416ec..697b40532 100644 --- a/arkid/core/tasks/celery.py +++ b/arkid/core/tasks/celery.py @@ -54,3 +54,18 @@ def dispatch_task(self, task_name, *args, **kwargs): break else: logger.info(f"*** Warning! No task found for name {task_name} ***") + + +@app.task(bind=True) +def dispatch_task_with_options(self, task_name, celery_options={}, *args, **kwargs): + # logger.info(f'=== Dispatch task:{task_name}, args: {args}, kwargs: {kwargs}, celery_options: {celery_options}') + for name, task in app.tasks.items(): + func_name = name.split('.')[-1] + if func_name == task_name: + if not celery_options or not isinstance(celery_options, dict): + celery_options = {} + logger.info(f"Ready to apply_async funtion {name} with {celery_options}") + task.apply_async(args, kwargs, **celery_options) + break + else: + logger.info(f"*** Warning! No task found for name {task_name} ***") \ No newline at end of file diff --git a/arkid/core/translation.py b/arkid/core/translation.py index af8f28e77..415e5119f 100644 --- a/arkid/core/translation.py +++ b/arkid/core/translation.py @@ -61,5 +61,10 @@ def reset_lang_maps(): lang_maps[item.name].update(item.custom_data) except Exception as err: logger.error(err) + + for key in default_lang_maps.keys(): + if key not in lang_maps.keys(): + lang_maps[key] = default_lang_maps[key] + return lang_maps diff --git a/arkid/extension/loader.py b/arkid/extension/loader.py index 707200d0e..9dca0723e 100644 --- a/arkid/extension/loader.py +++ b/arkid/extension/loader.py @@ -36,6 +36,9 @@ def _start(self): 'ext_dir': str(ext.ext_dir), 'name': ext.name, 'version': ext.version, + 'author': ext.author, + 'homepage': ext.homepage, + 'logo': ext.logo, 'is_del': False, }, package = ext.package, diff --git a/arkid/extension/migrations/0011_extension_author_extension_homepage_extension_logo.py b/arkid/extension/migrations/0011_extension_author_extension_homepage_extension_logo.py new file mode 100644 index 000000000..daa830b32 --- /dev/null +++ b/arkid/extension/migrations/0011_extension_author_extension_homepage_extension_logo.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.6 on 2022-10-20 06:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extension', '0010_arkstorecategory'), + ] + + operations = [ + migrations.AddField( + model_name='extension', + name='author', + field=models.CharField(blank=True, default='', max_length=128, null=True), + ), + migrations.AddField( + model_name='extension', + name='homepage', + field=models.CharField(blank=True, default='', max_length=1024, null=True), + ), + migrations.AddField( + model_name='extension', + name='logo', + field=models.CharField(blank=True, default='', max_length=1024, null=True), + ), + ] diff --git a/arkid/extension/migrations/0012_alter_extension_author_alter_extension_homepage_and_more.py b/arkid/extension/migrations/0012_alter_extension_author_alter_extension_homepage_and_more.py new file mode 100644 index 000000000..4dee07cf4 --- /dev/null +++ b/arkid/extension/migrations/0012_alter_extension_author_alter_extension_homepage_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.6 on 2022-10-20 06:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extension', '0011_extension_author_extension_homepage_extension_logo'), + ] + + operations = [ + migrations.AlterField( + model_name='extension', + name='author', + field=models.CharField(blank=True, default='', max_length=128, null=True, verbose_name='Author'), + ), + migrations.AlterField( + model_name='extension', + name='homepage', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Homepage'), + ), + migrations.AlterField( + model_name='extension', + name='logo', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Logo'), + ), + ] diff --git a/arkid/extension/models.py b/arkid/extension/models.py index 86e4d4f7f..fbc6865f6 100644 --- a/arkid/extension/models.py +++ b/arkid/extension/models.py @@ -16,6 +16,9 @@ class Meta(object): ext_dir = models.CharField(max_length=1024, verbose_name=_('完整路径名')) name = models.CharField(max_length=128, verbose_name=_('名称')) version = models.CharField(max_length=128, verbose_name=_('版本')) + author = models.CharField(max_length=128, blank=True, null=True, default="", verbose_name=_('Author', '作者')) + logo = models.CharField(max_length=1024, blank=True, null=True, default="", verbose_name=_('Logo', '图标')) + homepage = models.CharField(max_length=1024, blank=True, null=True, default="", verbose_name=_('Homepage', '官方网站')) is_active = models.BooleanField(default=True, verbose_name=_('是否启动')) profile = models.JSONField(blank=True, default=dict, verbose_name=_('Setup Profile','启动设置')) is_allow_use_platform_config = models.BooleanField(default=False, verbose_name=_('是否允许租户使用平台配置')) diff --git a/arkid/extension/utils.py b/arkid/extension/utils.py index 60a977c7a..7a7fd9598 100644 --- a/arkid/extension/utils.py +++ b/arkid/extension/utils.py @@ -172,3 +172,16 @@ def reload_extension(ext_dir: str) -> None: # 重新加载相应的url reload(api.v1.urls) reload(arkid.urls) + + +def restart_celery(): + from django.core.cache import cache + key = "CELERY_RESTART" + value = True + timeout = 60*10 + if cache.get(key): + return + + cache.set(key, value, timeout=timeout) + from arkid.core.tasks.celery import dispatch_task_with_options + dispatch_task_with_options.delay('restart', celery_options={"countdown": timeout}) diff --git "a/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\345\216\237\347\224\237\351\203\250\347\275\262.md" "b/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\345\216\237\347\224\237\351\203\250\347\275\262.md" new file mode 100644 index 000000000..b549f998f --- /dev/null +++ "b/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\345\216\237\347\224\237\351\203\250\347\275\262.md" @@ -0,0 +1,128 @@ +下载 + +```shell +打开 https://github.com/longguikeji/arkid/releases/tag/2.6.2 (手动检查最新版本,clone最新的版本tag) + +下载 arkid.zip 解压,得到以下文件 +``` + +- be262.tar.gz +- desktop266.tar.gz +- fe262.tar.gz +- portal.conf +- settings_local.py +- supervisord.conf +- 原生arkid部署.md + + + +两台机器 + +## 一台后端: + +#### 1、软件安装 + +- python 3.8 + +- mysql 5.7 + +- redis 5 + +- ``` + gettext xmlsec1 supervisor tree + freetds-dev freetds-bin + python-dev python-pip + ``` + +#### 2、安装arkid后端 + +```shell +# 后端 be262.tar.gz 解压,放到 /var/arkid/ + +# 修改 settings_local.py,填写正确的 mysql 信息,mysql需要新建一个空的数据库 +DEBUG = False +# mysql database +MYSQLHOST = "localhost" +MYSQLPORT = "3306" +MYSQLDATABASE = "arkid" +MYSQLUSER = "root" +MYSQLPASSWORD = "root" + +# Redis cache, 默认端口 6379 +REDISHOST = "localhost" +REDISPASSWD = None + + + + +# 把 settings_local.py 和 supervisord.conf 放到 /var/arkid/ 下 + +``` + + + +#### 3、启动后端 + +```shell +# redis 和 mysql 需要保持启动状态 +# 进入/var/arkid/ 目录下 + +export PYTHONUSERBASE=/var/arkid/arkid_extensions +export PATH=$PATH:/var/arkid/arkid_extensions/bin +export ARKID_VERSION=2.6.2 + +pip install --disable-pip-version-check -r requirements.txt; + +/usr/local/bin/python3.8 manage.py migrate + +supervisord +``` + + + +## 一台前端: + +#### 1、软件安装 + +- nginx +#### 2、nginx配置文件 + +```shell +# 移掉默认配置 +mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf_back + +# 修改portal.conf文件,把 `http://be` 换成后端部署的地址 +# 将 portal.conf 放到 /etc/nginx/conf.d/portal.conf + + +``` + + + +#### 3、安装arkid前端和桌面 + +```shell +# 前端 fe262.tar.gz 解压,放到 /usr/share/nginx/html/ + +# 桌面 desktop.tar.gz 解压,放到 /usr/share/nginx/html/desktop/ + +``` + +#### 4、启动前端 + +```shell +nginx -t + +nginx -s reload +``` + +5、访问 + +```shell +http://前端机器ip +``` + + + + + diff --git "a/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\351\200\232\350\277\207k8s\351\203\250\347\275\262.md" "b/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\351\200\232\350\277\207k8s\351\203\250\347\275\262.md" index 4a135cf9d..3843df1e4 100644 --- "a/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\351\200\232\350\277\207k8s\351\203\250\347\275\262.md" +++ "b/docs/ \345\277\253\351\200\237\345\274\200\345\247\213/ \347\247\201\346\234\211\345\214\226\351\203\250\347\275\262/\351\200\232\350\277\207k8s\351\203\250\347\275\262.md" @@ -8,31 +8,26 @@ * PV provisioner support in the underlying infrastructure * ReadWriteMany volumes for deployment scaling -## 通过helm安装 +## 通过helm-controller安装 -> !!! 生产环境推荐使用 gitops工具(如argoCD)来部署和管理 -> chart源码仓库地址: https://github.com/longguikeji/arkid-charts.git - -### 添加helm仓库 +### 安装 CRD ```shell -helm repo add lgkj https://harbor.longguikeji.com/chartrepo/public - -helm repo update +CHARTCRD=`kubectl get crd|grep helmcharts.helm.cattle.io` +if [ -z "$CHARTCRD" ];then + kubectl create -f https://gitee.com/longguikeji/arkid-charts/raw/main/helmchartscrd.yaml +fi ``` -### helm 查找 arkid 的 charts +### 部署 arkid ```shell -helm search repo arkid -l +kubectl create ns arkid +kubectl create -f https://gitee.com/longguikeji/arkid-charts/raw/main/arkid.yaml ``` -### 安装 arkid chart - - +### 卸载 arkid ```shell -kubectl create ns arkid +kubectl -n arkid delete helmcharts arkid -helm --namespace arkid install arkid lgkj/arkid \ ---set persistence.init=true ``` ## nodeport 端口访问 arkid @@ -52,14 +47,14 @@ helm --namespace arkid install arkid lgkj/arkid \ ## 升级 arkid chart版本 ```shell -helm repo update +kubectl -n arkid edit helmcharts arkid -helm -n arkid upgrade arkid lgkj/arkid \ ---set persistence.init=true +## 修改版本号,保存退出, 会自动更新 +spec: + chart: arkid + version: 3.1.3 ``` - - ## 更多配置 > https://github.com/longguikeji/arkid-charts.git ### 公共配置 diff --git "a/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\346\235\203\351\231\220\347\256\241\347\220\206.md" "b/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\346\235\203\351\231\220\347\256\241\347\220\206.md" index 97868d8ba..cac7d07e2 100644 --- "a/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\346\235\203\351\231\220\347\256\241\347\220\206.md" +++ "b/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\346\235\203\351\231\220\347\256\241\347\220\206.md" @@ -4,16 +4,16 @@ 查看登录用户所能读取到的权限信息,进行权限的新建和编辑等 -=== "打开权限列表" +* "打开权限列表" [![jbmeAI.md.jpg](https://s1.ax1x.com/2022/07/20/jbmeAI.md.jpg)](https://imgtu.com/i/jbmeAI) -=== "新建权限" +* "新建权限" [![jbm7VA.md.jpg](https://s1.ax1x.com/2022/07/20/jbm7VA.md.jpg)](https://imgtu.com/i/jbm7VA) -=== "查看创建结果" +* "查看创建结果" [![jbmO8f.md.jpg](https://s1.ax1x.com/2022/07/20/jbmO8f.md.jpg)](https://imgtu.com/i/jbmO8f) -=== "删除权限" +* "删除权限" 只有自己创建的权限允许删除,不能删除系统权限 [![jbn9Vs.md.jpg](https://s1.ax1x.com/2022/07/20/jbn9Vs.md.jpg)](https://imgtu.com/i/jbn9Vs) -=== "权限搜索" +* "权限搜索" [![jbnkGV.md.jpg](https://s1.ax1x.com/2022/07/20/jbnkGV.md.jpg)](https://imgtu.com/i/jbnkGV) @@ -25,18 +25,18 @@ 可以再这里创建新的权限分组,并编辑其中权限。 -=== "打开权限分组列表" +* "打开权限分组列表" [![jbKEB4.md.jpg](https://s1.ax1x.com/2022/07/20/jbKEB4.md.jpg)](https://imgtu.com/i/jbKEB4) -=== "新建权限分组" +* "新建权限分组" [![jbK3uD.md.jpg](https://s1.ax1x.com/2022/07/20/jbK3uD.md.jpg)](https://imgtu.com/i/jbK3uD) -=== "查看创建结果" +* "查看创建结果" 只有自己创建的权限分组允许删除和编辑,不能删除和编辑系统分组 [![jbKaCt.md.jpg](https://s1.ax1x.com/2022/07/20/jbKaCt.md.jpg)](https://imgtu.com/i/jbKaCt) -=== "查看权限分组的权限" +* "查看权限分组的权限" [![jbKcUs.md.jpg](https://s1.ax1x.com/2022/07/20/jbKcUs.md.jpg)](https://imgtu.com/i/jbKcUs) -=== "权限分组添加权限" +* "权限分组添加权限" [![jbKhvT.md.jpg](https://s1.ax1x.com/2022/07/20/jbKhvT.md.jpg)](https://imgtu.com/i/jbKhvT) -=== "查看添加权限结果" +* "查看添加权限结果" 只有自己添加的权限才可以删除 [![jbKIrF.md.jpg](https://s1.ax1x.com/2022/07/20/jbKIrF.md.jpg)](https://imgtu.com/i/jbKIrF) @@ -52,43 +52,43 @@ ### 用户权限 -=== "打开用户权限列表" +* "打开用户权限列表" [![jbQ8kd.md.jpg](https://s1.ax1x.com/2022/07/20/jbQ8kd.md.jpg)](https://imgtu.com/i/jbQ8kd) -=== "添加用户权限" +* "添加用户权限" [![jbQyhn.md.jpg](https://s1.ax1x.com/2022/07/20/jbQyhn.md.jpg)](https://imgtu.com/i/jbQyhn) -=== "查看添加权限结果" +* "查看添加权限结果" [![jbQH91.md.jpg](https://s1.ax1x.com/2022/07/20/jbQH91.md.jpg)](https://imgtu.com/i/jbQH91) -=== "删除用户权限" +* "删除用户权限" [![jblC9I.md.jpg](https://s1.ax1x.com/2022/07/20/jblC9I.md.jpg)](https://imgtu.com/i/jblC9I) ### 用户分组权限 -=== "打开用户分组列表" +* "打开用户分组列表" [![jb1lid.md.jpg](https://s1.ax1x.com/2022/07/20/jb1lid.md.jpg)](https://imgtu.com/i/jb1lid) -=== "添加用户分组权限" +* "添加用户分组权限" [![jb10ij.md.jpg](https://s1.ax1x.com/2022/07/20/jb10ij.md.jpg)](https://imgtu.com/i/jb10ij) -=== "查看添加的用户分组权限" +* "查看添加的用户分组权限" [![jb1Wo4.md.jpg](https://s1.ax1x.com/2022/07/20/jb1Wo4.md.jpg)](https://imgtu.com/i/jb1Wo4) -=== "删除用户分组权限" +* "删除用户分组权限" [![jb1OTe.md.jpg](https://s1.ax1x.com/2022/07/20/jb1OTe.md.jpg)](https://imgtu.com/i/jb1OTe) ### 应用权限 -=== "打开应用权限列表" +* "打开应用权限列表" [![vThfr4.md.jpg](https://s1.ax1x.com/2022/09/05/vThfr4.md.jpg)](https://imgse.com/i/vThfr4) -=== "添加应用权限" +* "添加应用权限" [![vThoI1.md.jpg](https://s1.ax1x.com/2022/09/05/vThoI1.md.jpg)](https://imgse.com/i/vThoI1) -=== "查看添加的应用权限" +* "查看添加的应用权限" [![vTh5Z9.md.jpg](https://s1.ax1x.com/2022/09/05/vTh5Z9.md.jpg)](https://imgse.com/i/vTh5Z9) -=== "删除应用权限" +* "删除应用权限" [![vThIaR.md.jpg](https://s1.ax1x.com/2022/09/05/vThIaR.md.jpg)](https://imgse.com/i/vThIaR) ### 如果使用应用ID和Secret访问 -=== "获取应用ID和Secret" +* "获取应用ID和Secret" [![vThhqJ.md.jpg](https://s1.ax1x.com/2022/09/05/vThhqJ.md.jpg)](https://imgse.com/i/vThhqJ) -=== "使用应用ID和Secret访问,需要在headers加上APP-ID和APP-SECRET参数" +* "使用应用ID和Secret访问,需要在headers加上APP-ID和APP-SECRET参数" [![vTh7Px.md.jpg](https://s1.ax1x.com/2022/09/05/vTh7Px.md.jpg)](https://imgse.com/i/vTh7Px) ## 授权规则 @@ -99,11 +99,11 @@ 不同插件处理授权规则的方式不同,各自配置也不同,请参看[详细插件文档](../../../../%20%20系统插件/com_longgui_impower_rule/DefaultImpowerRule/) -=== "打开授权规则列表" +* "打开授权规则列表" [![xdAhFJ.jpg](https://s1.ax1x.com/2022/10/13/xdAhFJ.jpg)](https://imgse.com/i/xdAhFJ) -=== "新建授权规则" +* "新建授权规则" [![xdAcLT.jpg](https://s1.ax1x.com/2022/10/13/xdAcLT.jpg)](https://imgse.com/i/xdAcLT) -=== "编辑授权规则" +* "编辑授权规则" [![xdALwD.jpg](https://s1.ax1x.com/2022/10/13/xdALwD.jpg)](https://imgse.com/i/xdALwD) -=== "删除授权规则" +* "删除授权规则" [![xdAOTe.jpg](https://s1.ax1x.com/2022/10/13/xdAOTe.jpg)](https://imgse.com/i/xdAOTe) diff --git "a/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\347\224\250\346\210\267\347\256\241\347\220\206.md" "b/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\347\224\250\346\210\267\347\256\241\347\220\206.md" index 2af7c634c..f6e2437a7 100644 --- "a/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\347\224\250\346\210\267\347\256\241\347\220\206.md" +++ "b/docs/ \347\224\250\346\210\267\346\214\207\345\215\227/\347\224\250\346\210\267\346\211\213\345\206\214/ \347\247\237\346\210\267\347\256\241\347\220\206\345\221\230/\347\224\250\346\210\267\347\256\241\347\220\206.md" @@ -15,9 +15,9 @@ [![xN8ZOs.md.jpg](https://s1.ax1x.com/2022/10/11/xN8ZOs.md.jpg)](https://imgse.com/i/xN8ZOs) * 导入 点击菜单 "用户管理>用户列表>导入" -=== "选择下拉,点击导出模板" +* "选择下拉,点击导出模板" [![xNJniV.md.jpg](https://s1.ax1x.com/2022/10/11/xNJniV.md.jpg)](https://imgse.com/i/xNJniV) -=== "填写表格数据,点击导入" +* "填写表格数据,点击导入" [![xNJGZR.md.jpg](https://s1.ax1x.com/2022/10/11/xNJGZR.md.jpg)](https://imgse.com/i/xNJGZR) * 导出 点击菜单 "用户管理>用户列表>导出" @@ -41,9 +41,9 @@ [![xUZPZF.md.jpg](https://s1.ax1x.com/2022/10/12/xUZPZF.md.jpg)](https://imgse.com/i/xUZPZF) * 导入 点击菜单 "用户管理>用户分组>导入" -=== "选择下拉,点击导出模板" +* "选择下拉,点击导出模板" [![xUZZxx.md.jpg](https://s1.ax1x.com/2022/10/12/xUZZxx.md.jpg)](https://imgse.com/i/xUZZxx) -=== "填写表格数据,点击导入" +* "填写表格数据,点击导入" [![xUZQde.md.jpg](https://s1.ax1x.com/2022/10/12/xUZQde.md.jpg)](https://imgse.com/i/xUZQde) * 导出 点击菜单 "用户管理>用户分组>导出" diff --git a/extension_root/com_longgui_app_protocol_oidc/Case.md b/extension_root/com_longgui_app_protocol_oidc/Case.md index 67e90c8ff..0d77dba84 100644 --- a/extension_root/com_longgui_app_protocol_oidc/Case.md +++ b/extension_root/com_longgui_app_protocol_oidc/Case.md @@ -232,4 +232,109 @@ public class OidcRedirectServlet extends HttpServlet { return null; } } +``` + +## .NET + +``` C# +public partial class AutoLogin_Qywx3 : System.Web.UI.Page +{ + string clientId = "----------------------------------";//新建一个应用,提供如下信息 + string clientSecret = "---------------------"; + string myurl = "--------------------------; + string URL_Authorize = "--------------------oauth/authorize/"; + string URL_Token = "---------------------------/oauth/token/"; + string URL_Userinfo = "------------------------/oauth/userinfo/";//用户信息地址 + + protected void Page_Load(object sender, EventArgs e) + { + string code = Request.QueryString["code"]; + if (string.IsNullOrEmpty(code)) + { + //请求code + string return_url = Server.UrlEncode(myurl); + string url = ""; + url = URL_Authorize + "?client_id=" + clientId + "&redirect_uri=" + return_url + "&response_type=code&scope=userinfo"; + Response.Redirect(url); + return; + } + else + { + string json = sendMessage(URL_Token, code); + DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AccessToken)); + var mStream = new MemoryStream(Encoding.Default.GetBytes(json)); + AccessToken token = (AccessToken)serializer.ReadObject(mStream); + string access_token=token.access_token; +string url = "----------------/oauth/userinfo/"; //用户信息地址 + string R = SendGetHttpRequest(url, access_token); + Response.Write(R); + return; + } + } + + public string SendGetHttpRequest(string url, string requestData) + { + WebRequest request = (WebRequest)HttpWebRequest.Create(url); + request.Method = "Get"; + request.Headers["Authorization"] = "Bearer " + requestData; + string result = string.Empty; + using (WebResponse response = request.GetResponse()) + { + if (response != null) + { + using (Stream stream = response.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + result = reader.ReadToEnd(); + } + } + + } + } + return result; + } + + public string sendMessage(string strUrl, string code) + { + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true; + //1.设置消息头 + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strUrl); + request.Method = "Post"; + string a = clientId + ":" + clientSecret; + var b = Encoding.UTF8.GetBytes(a); + var base64 = Convert.ToBase64String(b); + request.Headers.Add("Authorization", "Basic " + base64); + request.UserAgent = "Apifox/1.0.0 (https://www.apifox.cn)"; + request.ContentType = "application/x-www-form-urlencoded"; + request.Accept = "*/*"; + request.Host = "-------";//sso域名及端口 + request.AllowAutoRedirect = true; + request.Headers.Add("accept-encoding", "gzip, deflate, br"); + string param = "grant_type=authorization_code&code=" + HttpUtility.UrlEncode(code); + byte[] byteData = Encoding.ASCII.GetBytes(param); + request.ContentLength = byteData.Length; + + using (Stream reqStream = request.GetRequestStream()) + { + reqStream.Write(byteData, 0, byteData.Length); + } + //Response应答流获取数据 + string strResponse = ""; + using (HttpWebResponse res = (HttpWebResponse)request.GetResponse()) + { + using (Stream resStream = res.GetResponseStream()) + { + using (StreamReader sr = new StreamReader(resStream, Encoding.UTF8)) //UTF8 + { + strResponse = sr.ReadToEnd(); + } + } + // res.Close(); + } + return strResponse; + } +} ``` \ No newline at end of file diff --git a/extension_root/com_longgui_auto_form_fill/__init__.py b/extension_root/com_longgui_auto_form_fill/__init__.py new file mode 100644 index 000000000..80f92e058 --- /dev/null +++ b/extension_root/com_longgui_auto_form_fill/__init__.py @@ -0,0 +1,216 @@ +from arkid.core.extension.app_protocol import AppProtocolExtension +from arkid.core.translation import gettext_default as _ +from pydantic import Field +from arkid.core.event import CREATE_APP, UPDATE_APP, DELETE_APP +import os +from arkid.common.logger import logger +from arkid.config import get_app_config +from typing import Optional +from urllib.parse import urlparse +from arkid.core.models import ExpiringToken +from arkid.core.extension import create_extension_schema, Extension +from django.urls import reverse +from django.shortcuts import redirect +from arkid.core.models import Tenant, ExpiringToken, App +from django.shortcuts import render +from django.http import StreamingHttpResponse +from django.http.response import HttpResponseRedirect, JsonResponse, HttpResponse +from arkid.core.api import api, operation +from arkid.core.constants import * +from arkid.config import get_app_config +from django.conf import settings +from pathlib import Path +from arkid.common.arkstore import get_app_config_from_arkstore + + +CURRENT_DIR = Path(__file__).resolve().parent + +AutoFormFillConfigSchema = create_extension_schema( + 'AutoFormFillConfigSchema', + __file__, + [ + ('login_url', str, Field(title=_('Auto Form Fill APP Login URL', '登录URL'))), + ( + 'username_css', + str, + Field(title=_('Username CSS Selector', '用户名输入框CSS Selector')), + ), + ( + 'password_css', + str, + Field(title=_('Password CSS Selector', '密码输入框CSS Selector')), + ), + ( + 'submit_css', + str, + Field(title=_('Submit Button CSS Selector', '提交/登录按钮CSS Selector')), + ), + ( + 'extra_js', + str, + Field(format="textarea", title=_('Extra Element JavaScrip Selector', '额外的元素JavaScript Selector')), + ), + ('auto_login', bool, Field(default=False, title=_('Auto Login', '自动登录'))), + ], +) + + +class AutoFormFillExtension(AppProtocolExtension): + def load(self): + tmpl_dir = CURRENT_DIR / 'templates' + settings.TEMPLATES[0]["DIRS"].append(str(tmpl_dir)) + self.register_app_protocol_schema(AutoFormFillConfigSchema, 'AutoFormFill') + self.register_api( + '/apps/{app_id}/arkid_form_login/', 'GET', self.login, auth=None + ) + self.register_api( + '/arkid_chrome_extension/download/', 'GET', self.download, auth=None + ) + self.register_api('/apps/', 'GET', self.app_list) + self.register_api('/apps/{app_id}/', 'GET', self.app_config) + super().load() + + def create_app(self, event, **kwargs): + self.update_app_url(event, True) + return True + + def update_app(self, event, **kwargs): + self.update_app_url(event, False) + return True + + def delete_app(self, event, **kwargs): + # 删除应用 + return True + + def update_app_url(self, event, is_created): + ''' + 更新配置中的url信息 + ''' + app = event.data["app"] + config = get_app_config() + frontend_url = config.get_frontend_host(schema=True) + + app.url = ( + f'{frontend_url}/api/v1/{self.pname}/apps/{app.id.hex}/arkid_form_login/' + ) + app.save() + + @operation(roles=[NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN]) + def login(self, request, app_id, tenant_id=None): + """ + 判断是否已经授权过,如果没有跳转到输入账号表单页面,发起认证授权 + 如果已经授权过,直接拿到已经保存的token,获取authkey,登录易签宝官网首页 + """ + token = request.GET.get("token") + if not token: + login_url = reverse('login_enter') + if tenant_id: + response = redirect( + f'{login_url}?next=/api/v1/{self.pname}/apps/{app_id}/arkid_form_login/&tenant_id={tenant_id}' + ) + else: + response = redirect( + f'{login_url}?next=/api/v1/{self.pname}/apps/{app_id}/arkid_form_login/' + ) + return response + + errors = [] + + app = App.valid_objects.get(id=app_id) + expiring_token = ExpiringToken.objects.get(token=token) + + user = expiring_token.user + tenant = user.tenant + + if app.tenant != tenant: + errors.append("错误的app_uuid,找不到该自动表单代填应用") + + err_msg = "|".join(errors) + + config = get_app_config() + frontend_url = config.get_frontend_host(schema=True) + + download_url = ( + f'{frontend_url}/api/v1/{self.pname}/arkid_chrome_extension/download/' + ) + context = { + "err_msg": err_msg, + "app_uuid": app.id.hex, + "tenant_uuid": tenant.id.hex, + "user_uuid": user.id.hex, + "download_url": download_url, + } + return render(request, "form_login.html", context=context) + + @operation(roles=[NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN]) + def download(self, request): + """ + 提供chrome 表单代填插件 + """ + file_name = "arkid_chrome_extension.zip" + base_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(base_dir, file_name) + + def file_iterator(file_path, chunk_size=512): + """ + 文件生成器,防止文件过大,导致内存溢出 + :param file_path: 文件绝对路径 + :param chunk_size: 块大小 + :return: 生成器 + """ + with open(file_path, mode="rb") as f: + while True: + c = f.read(chunk_size) + if c: + yield c + else: + break + + try: + # 设置响应头 + # StreamingHttpResponse将文件内容进行流式传输,数据量大可以用这个方法 + response = StreamingHttpResponse(file_iterator(file_path)) + # 以流的形式下载文件,这样可以实现任意格式的文件下载 + response["Content-Type"] = "application/octet-stream" + # Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名 + response["Content-Disposition"] = 'attachment;filename="{}"'.format( + file_name + ) + except: + return HttpResponse("Sorry but Not Found the File") + return response + + @operation(roles=[NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN]) + def app_config(self, request, app_id): + """ + 返回app的config + """ + app = App.valid_objects.get(id=app_id) + if not app: + return JsonResponse({}) + + if app.arkstore_app_id: + config = get_app_config_from_arkstore(request, app.arkstore_app_id) + return config + + if app.config: + return app.config.config + else: + return JsonResponse({}) + + @operation(roles=[NORMAL_USER, TENANT_ADMIN, PLATFORM_ADMIN]) + def app_list(self, request): + """ + 返回app的config + """ + user = request.user + tenant = user.tenant + all_apps = App.valid_objects.filter(tenant=tenant, type="AutoFormFill") + if not all_apps: + return JsonResponse({'result': []}) + + result = [{'uuid': app.id.hex, 'name': app.name} for app in all_apps] + return JsonResponse({'result': result}) + + +extension = AutoFormFillExtension() diff --git a/extension_root/com_longgui_auto_form_fill/apps.py b/extension_root/com_longgui_auto_form_fill/apps.py new file mode 100644 index 000000000..a996eaab0 --- /dev/null +++ b/extension_root/com_longgui_auto_form_fill/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +app_label = 'com_longgui_auto_form_fill' + +class AutoFormFillAppConfig(AppConfig): + name = app_label diff --git a/extension_root/com_longgui_auto_form_fill/arkid_chrome_extension.zip b/extension_root/com_longgui_auto_form_fill/arkid_chrome_extension.zip new file mode 100644 index 000000000..565f89152 Binary files /dev/null and b/extension_root/com_longgui_auto_form_fill/arkid_chrome_extension.zip differ diff --git a/extension_root/com_longgui_auto_form_fill/config.toml b/extension_root/com_longgui_auto_form_fill/config.toml new file mode 100644 index 000000000..b3e08ec88 --- /dev/null +++ b/extension_root/com_longgui_auto_form_fill/config.toml @@ -0,0 +1,7 @@ +package='com.longgui.auto.form.fill' +name='帐密代填插件' +version='1.1' +labels='auto-form-fill' +homepage='http://longguikeji.gitee.io/arkid/%20%E5%85%B6%E5%AE%83%E6%8F%92%E4%BB%B6/com_longgui_auto_form_fill/' +logo='' +author='fanhe@longguikeji.com' diff --git a/extension_root/com_longgui_auto_form_fill/index.md b/extension_root/com_longgui_auto_form_fill/index.md new file mode 100644 index 000000000..cd4eb5d63 --- /dev/null +++ b/extension_root/com_longgui_auto_form_fill/index.md @@ -0,0 +1,47 @@ +# 账密代填插件 + +## 功能介绍 + +通过此插件创建应用后,安装对应的chrome插件,可以实现将用于登录该应用的账号和密码存储在chrome插件里,点击应用图标,可以选择登录的账号,
+自动跳转到该应用的登录URL,并自动填入应用登录页账号和密码,实现自动登录,避免每次输入账号密码。
+另外,可以在chrome插件里做新增、修改、删除账号密码等管理操作。 + +### 配置账密代填应用 + +=== "创建代理URL应用" + [![vLCUaQ.png](https://s1.ax1x.com/2022/09/09/vLCUaQ.png)](https://imgse.com/i/vLCUaQ) + + + +=== "配置应用协议" + [![vLCbZD.png](https://s1.ax1x.com/2022/09/09/vLCbZD.png)](https://imgse.com/i/vLCbZD) + + !!! 提示 + 自动登录登录打开, 代表应用登录页自动填入账号密码后,自动点击登录按钮, 否则不自动点击登录按钮 + + +=== "点击应用图标" + [![vLPidg.png](https://s1.ax1x.com/2022/09/09/vLPidg.png)](https://imgse.com/i/vLPidg) + +=== "安装chrome插件" + 如果没有安装过chrome插件,需要按页面提示先安装chrome插件,安装完成后,刷新页面 + [![vLPg6P.png](https://s1.ax1x.com/2022/09/09/vLPg6P.png)](https://imgse.com/i/vLPg6P) + + +=== "首次登录输入账号密码" + [![vLiQjP.png](https://s1.ax1x.com/2022/09/09/vLiQjP.png)](https://imgse.com/i/vLiQjP) + + 点击添加后,会自动跳转到应用登录页面 + + +=== "跳转到应用登录页面" + [![vLiDBT.png](https://s1.ax1x.com/2022/09/09/vLiDBT.png)](https://imgse.com/i/vLiDBT) + + +### 使用chrome插件管理账号 + +=== "添加应用账号" + [![vLiIHO.png](https://s1.ax1x.com/2022/09/09/vLiIHO.png)](https://imgse.com/i/vLiIHO) + +=== "重新点击应用图标" + [![vLiH4H.png](https://s1.ax1x.com/2022/09/09/vLiH4H.png)](https://imgse.com/i/vLiH4H) diff --git a/extension_root/com_longgui_auto_form_fill/templates/form_login.html b/extension_root/com_longgui_auto_form_fill/templates/form_login.html new file mode 100644 index 000000000..0ec44b69b --- /dev/null +++ b/extension_root/com_longgui_auto_form_fill/templates/form_login.html @@ -0,0 +1,54 @@ + + + + + + ArkID账密代填 + + + + + + + + + +
+

检测到您当前浏览器没有安装ArkId自动表单代填chrome浏览器插件,请先下载安装

+

下载链接

+
+

插件安装步骤

+
+

1. 点击下载链接,下载插件zip包到本地后解压缩

+ vqOR56.png +

2. 打开chrome管理扩展程序页面,打开开发者模式,点击加载已解压的扩展程序

+ vqXprj.png +

3. 选择步骤1中解压缩的插件目录

+ vqXEGT.png +

4. 固定插件到地址栏,方便使用

+ vqXMZR.png +
+
+ +
+ +
+ {{err_msg}} +
+ +