diff --git a/Dockerfile b/Dockerfile index d8fd27492..2174df9c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM jumpserver/python:3.9-slim-buster as stage-build +FROM python:3.11-slim-bullseye as stage-build ARG TARGETARCH ARG VERSION @@ -8,9 +8,8 @@ WORKDIR /opt/jumpserver ADD . . RUN cd utils && bash -ixeu build.sh -FROM jumpserver/python:3.9-slim-buster +FROM python:3.11-slim-bullseye ARG TARGETARCH -MAINTAINER JumpServer Team ARG BUILD_DEPENDENCIES=" \ g++ \ @@ -22,6 +21,7 @@ ARG DEPENDENCIES=" \ libpq-dev \ libffi-dev \ libjpeg-dev \ + libkrb5-dev \ libldap2-dev \ libsasl2-dev \ libssl-dev \ @@ -37,13 +37,11 @@ ARG TOOLS=" \ default-libmysqlclient-dev \ default-mysql-client \ locales \ + nmap \ openssh-client \ - procps \ sshpass \ telnet \ - unzip \ vim \ - git \ wget" ARG APT_MIRROR=http://mirrors.ustc.edu.cn @@ -65,46 +63,17 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && sed -i "s@# alias @alias @g" ~/.bashrc \ && rm -rf /var/lib/apt/lists/* -ARG DOWNLOAD_URL=https://download.jumpserver.org - -RUN set -ex \ - && \ - if [ "${TARGETARCH}" == "amd64" ] || [ "${TARGETARCH}" == "arm64" ]; then \ - mkdir -p /opt/oracle; \ - cd /opt/oracle; \ - wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \ - unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \ - echo "/opt/oracle/instantclient_19_10" > /etc/ld.so.conf.d/oracle-instantclient.conf; \ - ldconfig; \ - rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \ - fi - -WORKDIR /tmp/build -COPY ./requirements ./requirements - -ARG PIP_MIRROR=https://pypi.douban.com/simple -ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple - -RUN --mount=type=cache,target=/root/.cache/pip \ - set -ex \ - && pip config set global.index-url ${PIP_MIRROR} \ - && pip install --upgrade pip \ - && pip install --upgrade setuptools wheel \ - && \ - if [ "${TARGETARCH}" == "loong64" ]; then \ - pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl; \ - pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl; \ - pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl; \ - pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl; \ - fi \ - && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install -r requirements/requirements.txt - COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver -RUN echo > /opt/jumpserver/config.yml \ - && rm -rf /tmp/build - WORKDIR /opt/jumpserver + +ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple +RUN --mount=type=cache,target=/root/.cache \ + set -ex \ + && echo > /opt/jumpserver/config.yml \ + && pip install poetry -i ${PIP_MIRROR} \ + && poetry config virtualenvs.create false \ + && poetry install --only=main + VOLUME /opt/jumpserver/data VOLUME /opt/jumpserver/logs diff --git a/Dockerfile-ee b/Dockerfile-ee index 63c65d21c..b485f1ecc 100644 --- a/Dockerfile-ee +++ b/Dockerfile-ee @@ -1,10 +1,9 @@ ARG VERSION FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack FROM jumpserver/core:${VERSION} + COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack -WORKDIR /opt/jumpserver - -RUN --mount=type=cache,target=/root/.cache/pip \ +RUN --mount=type=cache,target=/root/.cache \ set -ex \ - && pip install -r requirements/requirements_xpack.txt + && poetry install --only=xpack \ No newline at end of file diff --git a/README.md b/README.md index 634a8e56a..e83f8ee8f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ 9 年时间,倾情投入,用心做好一款开源堡垒机。

+------------------------------ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。 JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括: @@ -83,9 +84,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型 ### 参与贡献 -欢迎提交 PR 参与贡献。感谢以下贡献者,他们让 JumpServer 变的越来越好。 - - +欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md) ## 组件项目 diff --git a/apps/accounts/api/account/__init__.py b/apps/accounts/api/account/__init__.py index f6d8376a6..7f90c23c7 100644 --- a/apps/accounts/api/account/__init__.py +++ b/apps/accounts/api/account/__init__.py @@ -1,3 +1,4 @@ from .account import * from .task import * from .template import * +from .virtual import * diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index ddf88e0c7..a0d38039b 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -22,10 +22,11 @@ __all__ = [ class AccountViewSet(OrgBulkModelViewSet): model = Account - search_fields = ('username', 'name', 'asset__name', 'asset__address') + search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment') filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, + 'retrieve': serializers.AccountDetailSerializer, } rbac_perms = { 'partial_update': ['accounts.change_account'], @@ -52,20 +53,21 @@ class AccountViewSet(OrgBulkModelViewSet): return Response(data=serializer.data) @action( - methods=['get'], detail=False, url_path='username-suggestions', + methods=['post'], detail=False, url_path='username-suggestions', permission_classes=[IsValidUser] ) def username_suggestions(self, request, *args, **kwargs): - asset_ids = request.query_params.get('assets') - node_keys = request.query_params.get('keys') - username = request.query_params.get('username') + asset_ids = request.data.get('assets') + node_ids = request.data.get('nodes') + username = request.data.get('username') assets = Asset.objects.all() if asset_ids: - assets = assets.filter(id__in=asset_ids.split(',')) - if node_keys: - patten = Node.get_node_all_children_key_pattern(node_keys.split(',')) - assets = assets.filter(nodes__key__regex=patten) + assets = assets.filter(id__in=asset_ids) + if node_ids: + nodes = Node.objects.filter(id__in=node_ids) + node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) + assets = assets.filter(id__in=set(list(asset_ids) + list(node_asset_ids))) accounts = Account.objects.filter(asset__in=assets) if username: @@ -132,11 +134,13 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List def get_queryset(self): account = self.get_object() histories = account.history.all() - last_history = account.history.first() - if not last_history: + latest_history = account.history.first() + if not latest_history: return histories - - if account.secret == last_history.secret \ - and account.secret_type == last_history.secret_type: - histories = histories.exclude(history_id=last_history.history_id) + if account.secret != latest_history.secret: + return histories + if account.secret_type != latest_history.secret_type: + return histories + histories = histories.exclude(history_id=latest_history.history_id) return histories + diff --git a/apps/accounts/api/account/virtual.py b/apps/accounts/api/account/virtual.py new file mode 100644 index 000000000..79dec3f78 --- /dev/null +++ b/apps/accounts/api/account/virtual.py @@ -0,0 +1,20 @@ +from django.shortcuts import get_object_or_404 + +from accounts.models import VirtualAccount +from accounts.serializers import VirtualAccountSerializer +from common.utils import is_uuid +from orgs.mixins.api import OrgBulkModelViewSet + + +class VirtualAccountViewSet(OrgBulkModelViewSet): + serializer_class = VirtualAccountSerializer + search_fields = ('alias',) + filterset_fields = ('alias',) + + def get_queryset(self): + return VirtualAccount.get_or_init_queryset() + + def get_object(self, ): + pk = self.kwargs.get('pk') + kwargs = {'pk': pk} if is_uuid(pk) else {'alias': pk} + return get_object_or_404(VirtualAccount, **kwargs) diff --git a/apps/accounts/api/automations/backup.py b/apps/accounts/api/automations/backup.py index 134f9548c..b2d6d7352 100644 --- a/apps/accounts/api/automations/backup.py +++ b/apps/accounts/api/automations/backup.py @@ -26,8 +26,8 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet): class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet): serializer_class = serializers.AccountBackupPlanExecutionSerializer - search_fields = ('trigger',) - filterset_fields = ('trigger', 'plan_id') + search_fields = ('trigger', 'plan__name') + filterset_fields = ('trigger', 'plan_id', 'plan__name') http_method_names = ['get', 'post', 'options'] def get_queryset(self): diff --git a/apps/accounts/api/automations/base.py b/apps/accounts/api/automations/base.py index 7d5db0ac8..70ebfd709 100644 --- a/apps/accounts/api/automations/base.py +++ b/apps/accounts/api/automations/base.py @@ -1,5 +1,5 @@ from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status, mixins, viewsets from rest_framework.response import Response @@ -95,8 +95,8 @@ class AutomationExecutionViewSet( mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet ): - search_fields = ('trigger',) - filterset_fields = ('trigger', 'automation_id') + search_fields = ('trigger', 'automation__name') + filterset_fields = ('trigger', 'automation_id', 'automation__name') serializer_class = serializers.AutomationExecutionSerializer tp: str diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py index 89ad972a9..56ca232f7 100644 --- a/apps/accounts/apps.py +++ b/apps/accounts/apps.py @@ -6,6 +6,5 @@ class AccountsConfig(AppConfig): name = 'accounts' def ready(self): - from . import signal_handlers - from . import tasks - __all__ = signal_handlers + from . import signal_handlers # noqa + from . import tasks # noqa diff --git a/apps/accounts/automations/backup_account/handlers.py b/apps/accounts/automations/backup_account/handlers.py index 65c96c67d..e708e8d2d 100644 --- a/apps/accounts/automations/backup_account/handlers.py +++ b/apps/accounts/automations/backup_account/handlers.py @@ -1,22 +1,17 @@ import os import time -from openpyxl import Workbook from collections import defaultdict, OrderedDict from django.conf import settings -from django.db.models import F +from openpyxl import Workbook from rest_framework import serializers -from accounts.models import Account -from assets.const import AllTypes -from accounts.serializers import AccountSecretSerializer from accounts.notifications import AccountBackupExecutionTaskMsg -from users.models import User -from common.utils import get_logger -from common.utils.timezone import local_now_display +from accounts.serializers import AccountSecretSerializer +from assets.const import AllTypes from common.utils.file import encrypt_and_compress_zip_file - -logger = get_logger(__file__) +from common.utils.timezone import local_now_display +from users.models import User PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') @@ -76,8 +71,22 @@ class AssetAccountHandler(BaseAccountHandler): ) return filename + @staticmethod + def handler_secret(data, section): + for account_data in data: + secret = account_data.get('secret') + if not secret: + continue + length = len(secret) + index = length // 2 + if section == "front": + secret = secret[:index] + '*' * (length - index) + elif section == "back": + secret = '*' * (length - index) + secret[index:] + account_data['secret'] = secret + @classmethod - def create_data_map(cls, accounts): + def create_data_map(cls, accounts, section): data_map = defaultdict(list) if not accounts.exists(): @@ -97,9 +106,10 @@ class AssetAccountHandler(BaseAccountHandler): for tp, _accounts in account_type_map.items(): sheet_name = type_dict.get(tp, tp) data = AccountSecretSerializer(_accounts, many=True).data + cls.handler_secret(data, section) data_map.update(cls.add_rows(data, header_fields, sheet_name)) - logger.info('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count())) + print('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count())) return data_map @@ -109,8 +119,8 @@ class AccountBackupHandler: self.plan_name = self.execution.plan.name self.is_frozen = False # 任务状态冻结标志 - def create_excel(self): - logger.info( + def create_excel(self, section='complete'): + print( '\n' '\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m' '' @@ -119,7 +129,7 @@ class AccountBackupHandler: time_start = time.time() files = [] accounts = self.execution.backup_accounts - data_map = AssetAccountHandler.create_data_map(accounts) + data_map = AssetAccountHandler.create_data_map(accounts, section) if not data_map: return files @@ -133,14 +143,14 @@ class AccountBackupHandler: wb.save(filename) files.append(filename) timedelta = round((time.time() - time_start), 2) - logger.info('步骤完成: 用时 {}s'.format(timedelta)) + print('步骤完成: 用时 {}s'.format(timedelta)) return files def send_backup_mail(self, files, recipients): if not files: return recipients = User.objects.filter(id__in=list(recipients)) - logger.info( + print( '\n' '\033[32m>>> 发送备份邮件\033[0m' '' @@ -155,7 +165,7 @@ class AccountBackupHandler: encrypt_and_compress_zip_file(attachment, password, files) attachment_list = [attachment, ] AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list) - logger.info('邮件已发送至{}({})'.format(user, user.email)) + print('邮件已发送至{}({})'.format(user, user.email)) for file in files: os.remove(file) @@ -163,33 +173,42 @@ class AccountBackupHandler: self.execution.reason = reason[:1024] self.execution.is_success = is_success self.execution.save() - logger.info('已完成对任务状态的更新') + print('已完成对任务状态的更新') - def step_finished(self, is_success): + @staticmethod + def step_finished(is_success): if is_success: - logger.info('任务执行成功') + print('任务执行成功') else: - logger.error('任务执行失败') + print('任务执行失败') def _run(self): is_success = False error = '-' try: - recipients = self.execution.plan_snapshot.get('recipients') - if not recipients: - logger.info( + recipients_part_one = self.execution.snapshot.get('recipients_part_one', []) + recipients_part_two = self.execution.snapshot.get('recipients_part_two', []) + if not recipients_part_one and not recipients_part_two: + print( '\n' '\033[32m>>> 该备份任务未分配收件人\033[0m' '' ) + if recipients_part_one and recipients_part_two: + files = self.create_excel(section='front') + self.send_backup_mail(files, recipients_part_one) + + files = self.create_excel(section='back') + self.send_backup_mail(files, recipients_part_two) else: + recipients = recipients_part_one or recipients_part_two files = self.create_excel() self.send_backup_mail(files, recipients) except Exception as e: self.is_frozen = True - logger.error('任务执行被异常中断') - logger.info('下面打印发生异常的 Traceback 信息 : ') - logger.error(e, exc_info=True) + print('任务执行被异常中断') + print('下面打印发生异常的 Traceback 信息 : ') + print(e) error = str(e) else: is_success = True @@ -199,15 +218,15 @@ class AccountBackupHandler: self.step_finished(is_success) def run(self): - logger.info('任务开始: {}'.format(local_now_display())) + print('任务开始: {}'.format(local_now_display())) time_start = time.time() try: self._run() except Exception as e: - logger.error('任务运行出现异常') - logger.error('下面显示异常 Traceback 信息: ') - logger.error(e, exc_info=True) + print('任务运行出现异常') + print('下面显示异常 Traceback 信息: ') + print(e) finally: - logger.info('\n任务结束: {}'.format(local_now_display())) + print('\n任务结束: {}'.format(local_now_display())) timedelta = round((time.time() - time_start), 2) - logger.info('用时: {}'.format(timedelta)) + print('用时: {}'.format(timedelta)) diff --git a/apps/accounts/automations/backup_account/manager.py b/apps/accounts/automations/backup_account/manager.py index 311361c69..f523803d5 100644 --- a/apps/accounts/automations/backup_account/manager.py +++ b/apps/accounts/automations/backup_account/manager.py @@ -4,13 +4,9 @@ import time from django.utils import timezone -from common.utils import get_logger from common.utils.timezone import local_now_display - from .handlers import AccountBackupHandler -logger = get_logger(__name__) - class AccountBackupManager: def __init__(self, execution): @@ -23,7 +19,7 @@ class AccountBackupManager: def do_run(self): execution = self.execution - logger.info('\n\033[33m# 账号备份计划正在执行\033[0m') + print('\n\033[33m# 账号备份计划正在执行\033[0m') handler = AccountBackupHandler(execution) handler.run() @@ -35,10 +31,10 @@ class AccountBackupManager: self.time_end = time.time() self.date_end = timezone.now() - logger.info('\n\n' + '-' * 80) - logger.info('计划执行结束 {}\n'.format(local_now_display())) + print('\n\n' + '-' * 80) + print('计划执行结束 {}\n'.format(local_now_display())) self.timedelta = self.time_end - self.time_start - logger.info('用时: {}s'.format(self.timedelta)) + print('用时: {}s'.format(self.timedelta)) self.execution.timedelta = self.timedelta self.execution.save() diff --git a/apps/accounts/automations/change_secret/custom/ssh/main.yml b/apps/accounts/automations/change_secret/custom/ssh/main.yml index 853a666d4..b35d2175a 100644 --- a/apps/accounts/automations/change_secret/custom/ssh/main.yml +++ b/apps/accounts/automations/change_secret/custom/ssh/main.yml @@ -2,9 +2,10 @@ gather_facts: no vars: ansible_connection: local + ansible_become: false tasks: - - name: Test privileged account + - name: Test privileged account (paramiko) ssh_ping: login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" @@ -12,9 +13,14 @@ login_password: "{{ jms_account.secret }}" login_secret_type: "{{ jms_account.secret_type }}" login_private_key_path: "{{ jms_account.private_key_path }}" + become: "{{ custom_become | default(False) }}" + become_method: "{{ custom_become_method | default('su') }}" + become_user: "{{ custom_become_user | default('') }}" + become_password: "{{ custom_become_password | default('') }}" + become_private_key_path: "{{ custom_become_private_key_path | default(None) }}" register: ping_info - - name: Change asset password + - name: Change asset password (paramiko) custom_command: login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" @@ -22,6 +28,11 @@ login_port: "{{ jms_asset.port }}" login_secret_type: "{{ jms_account.secret_type }}" login_private_key_path: "{{ jms_account.private_key_path }}" + become: "{{ custom_become | default(False) }}" + become_method: "{{ custom_become_method | default('su') }}" + become_user: "{{ custom_become_user | default('') }}" + become_password: "{{ custom_become_password | default('') }}" + become_private_key_path: "{{ custom_become_private_key_path | default(None) }}" name: "{{ account.username }}" password: "{{ account.secret }}" commands: "{{ params.commands }}" @@ -30,9 +41,10 @@ when: ping_info is succeeded register: change_info - - name: Verify password + - name: Verify password (paramiko) ssh_ping: login_user: "{{ account.username }}" login_password: "{{ account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" + become: false diff --git a/apps/accounts/automations/change_secret/host/aix/main.yml b/apps/accounts/automations/change_secret/host/aix/main.yml index b51ddf69e..56fde8a2c 100644 --- a/apps/accounts/automations/change_secret/host/aix/main.yml +++ b/apps/accounts/automations/change_secret/host/aix/main.yml @@ -1,10 +1,41 @@ - hosts: demo gather_facts: no tasks: - - name: Test privileged account + - name: "Test privileged {{ jms_account.username }} account" ansible.builtin.ping: - - name: Change password + - name: "Check if {{ account.username }} user exists" + getent: + database: passwd + key: "{{ account.username }}" + register: user_info + ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败 + + - name: "Add {{ account.username }} user" + ansible.builtin.user: + name: "{{ account.username }}" + shell: "{{ params.shell }}" + home: "{{ params.home | default('/home/' + account.username, true) }}" + groups: "{{ params.groups }}" + expires: -1 + state: present + when: user_info.failed + + - name: "Add {{ account.username }} group" + ansible.builtin.group: + name: "{{ account.username }}" + state: present + when: user_info.failed + + - name: "Add {{ account.username }} user to group" + ansible.builtin.user: + name: "{{ account.username }}" + groups: "{{ params.groups }}" + when: + - user_info.failed + - params.groups + + - name: "Change {{ account.username }} password" ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('des') }}" @@ -12,44 +43,54 @@ ignore_errors: true when: account.secret_type == "password" - - name: create user If it already exists, no operation will be performed - ansible.builtin.user: - name: "{{ account.username }}" - when: account.secret_type == "ssh_key" - - name: remove jumpserver ssh key ansible.builtin.lineinfile: dest: "{{ ssh_params.dest }}" regexp: "{{ ssh_params.regexp }}" state: absent when: - - account.secret_type == "ssh_key" - - ssh_params.strategy == "set_jms" + - account.secret_type == "ssh_key" + - ssh_params.strategy == "set_jms" - - name: Change SSH key + - name: "Change {{ account.username }} SSH key" ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" + - name: "Set {{ account.username }} sudo setting" + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^{{ account.username }} ALL=" + line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" + validate: visudo -cf %s + when: + - user_info.failed + - params.sudo + - name: Refresh connection ansible.builtin.meta: reset_connection - - name: Verify password - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_password: "{{ account.secret }}" - ansible_become: no + - name: "Verify {{ account.username }} password (paramiko)" + ssh_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "password" + delegate_to: localhost - - name: Verify SSH key - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_ssh_private_key_file: "{{ account.private_key_path }}" - ansible_become: no + - name: "Verify {{ account.username }} SSH KEY (paramiko)" + ssh_ping: + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_user: "{{ account.username }}" + login_private_key_path: "{{ account.private_key_path }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "ssh_key" + delegate_to: localhost diff --git a/apps/accounts/automations/change_secret/host/posix/main.yml b/apps/accounts/automations/change_secret/host/posix/main.yml index 325ad644d..1dca70a5a 100644 --- a/apps/accounts/automations/change_secret/host/posix/main.yml +++ b/apps/accounts/automations/change_secret/host/posix/main.yml @@ -1,10 +1,17 @@ - hosts: demo gather_facts: no tasks: - - name: Test privileged account + - name: "Test privileged {{ jms_account.username }} account" ansible.builtin.ping: - - name: Check user + - name: "Check if {{ account.username }} user exists" + getent: + database: passwd + key: "{{ account.username }}" + register: user_info + ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败 + + - name: "Add {{ account.username }} user" ansible.builtin.user: name: "{{ account.username }}" shell: "{{ params.shell }}" @@ -12,19 +19,23 @@ groups: "{{ params.groups }}" expires: -1 state: present + when: user_info.failed - name: "Add {{ account.username }} group" ansible.builtin.group: name: "{{ account.username }}" state: present + when: user_info.failed - - name: Add user groups + - name: "Add {{ account.username }} user to group" ansible.builtin.user: name: "{{ account.username }}" groups: "{{ params.groups }}" - when: params.groups + when: + - user_info.failed + - params.groups - - name: Change password + - name: "Change {{ account.username }} password" ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" @@ -32,11 +43,6 @@ ignore_errors: true when: account.secret_type == "password" - - name: create user If it already exists, no operation will be performed - ansible.builtin.user: - name: "{{ account.username }}" - when: account.secret_type == "ssh_key" - - name: remove jumpserver ssh key ansible.builtin.lineinfile: dest: "{{ ssh_params.dest }}" @@ -46,14 +52,14 @@ - account.secret_type == "ssh_key" - ssh_params.strategy == "set_jms" - - name: Change SSH key + - name: "Change {{ account.username }} SSH key" ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - - name: Set sudo setting + - name: "Set {{ account.username }} sudo setting" ansible.builtin.lineinfile: dest: /etc/sudoers state: present @@ -61,25 +67,30 @@ line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" validate: visudo -cf %s when: + - user_info.failed - params.sudo - name: Refresh connection ansible.builtin.meta: reset_connection - - name: Verify password - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_password: "{{ account.secret }}" - ansible_become: no + - name: "Verify {{ account.username }} password (paramiko)" + ssh_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "password" + delegate_to: localhost - - name: Verify SSH key - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_ssh_private_key_file: "{{ account.private_key_path }}" - ansible_become: no + - name: "Verify {{ account.username }} SSH KEY (paramiko)" + ssh_ping: + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_user: "{{ account.username }}" + login_private_key_path: "{{ account.private_key_path }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "ssh_key" + delegate_to: localhost diff --git a/apps/accounts/automations/push_account/host/aix/main.yml b/apps/accounts/automations/push_account/host/aix/main.yml index 2dc10fdc2..0e6fba5c5 100644 --- a/apps/accounts/automations/push_account/host/aix/main.yml +++ b/apps/accounts/automations/push_account/host/aix/main.yml @@ -1,10 +1,17 @@ - hosts: demo gather_facts: no tasks: - - name: Test privileged account + - name: "Test privileged {{ jms_account.username }} account" ansible.builtin.ping: - - name: Push user + - name: "Check if {{ account.username }} user exists" + getent: + database: passwd + key: "{{ account.username }}" + register: user_info + ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败 + + - name: "Add {{ account.username }} user" ansible.builtin.user: name: "{{ account.username }}" shell: "{{ params.shell }}" @@ -12,22 +19,26 @@ groups: "{{ params.groups }}" expires: -1 state: present + when: user_info.failed - name: "Add {{ account.username }} group" ansible.builtin.group: name: "{{ account.username }}" state: present + when: user_info.failed - - name: Add user groups + - name: "Add {{ account.username }} user to group" ansible.builtin.user: name: "{{ account.username }}" groups: "{{ params.groups }}" - when: params.groups + when: + - user_info.failed + - params.groups - - name: Push user password + - name: "Change {{ account.username }} password" ansible.builtin.user: name: "{{ account.username }}" - password: "{{ account.secret | password_hash('sha512') }}" + password: "{{ account.secret | password_hash('des') }}" update_password: always ignore_errors: true when: account.secret_type == "password" @@ -41,14 +52,14 @@ - account.secret_type == "ssh_key" - ssh_params.strategy == "set_jms" - - name: Push SSH key + - name: "Change {{ account.username }} SSH key" ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - - name: Set sudo setting + - name: "Set {{ account.username }} sudo setting" ansible.builtin.lineinfile: dest: /etc/sudoers state: present @@ -56,25 +67,31 @@ line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" validate: visudo -cf %s when: + - user_info.failed - params.sudo - name: Refresh connection ansible.builtin.meta: reset_connection - - name: Verify password - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_password: "{{ account.secret }}" - ansible_become: no + - name: "Verify {{ account.username }} password (paramiko)" + ssh_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "password" + delegate_to: localhost - - name: Verify SSH key - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_ssh_private_key_file: "{{ account.private_key_path }}" - ansible_become: no + - name: "Verify {{ account.username }} SSH KEY (paramiko)" + ssh_ping: + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_user: "{{ account.username }}" + login_private_key_path: "{{ account.private_key_path }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "ssh_key" + delegate_to: localhost + diff --git a/apps/accounts/automations/push_account/host/posix/main.yml b/apps/accounts/automations/push_account/host/posix/main.yml index 2dc10fdc2..ea5128b17 100644 --- a/apps/accounts/automations/push_account/host/posix/main.yml +++ b/apps/accounts/automations/push_account/host/posix/main.yml @@ -1,10 +1,17 @@ - hosts: demo gather_facts: no tasks: - - name: Test privileged account + - name: "Test privileged {{ jms_account.username }} account" ansible.builtin.ping: - - name: Push user + - name: "Check if {{ account.username }} user exists" + getent: + database: passwd + key: "{{ account.username }}" + register: user_info + ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败 + + - name: "Add {{ account.username }} user" ansible.builtin.user: name: "{{ account.username }}" shell: "{{ params.shell }}" @@ -12,19 +19,23 @@ groups: "{{ params.groups }}" expires: -1 state: present + when: user_info.failed - name: "Add {{ account.username }} group" ansible.builtin.group: name: "{{ account.username }}" state: present + when: user_info.failed - - name: Add user groups + - name: "Add {{ account.username }} user to group" ansible.builtin.user: name: "{{ account.username }}" groups: "{{ params.groups }}" - when: params.groups + when: + - user_info.failed + - params.groups - - name: Push user password + - name: "Change {{ account.username }} password" ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" @@ -41,14 +52,14 @@ - account.secret_type == "ssh_key" - ssh_params.strategy == "set_jms" - - name: Push SSH key + - name: "Change {{ account.username }} SSH key" ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - - name: Set sudo setting + - name: "Set {{ account.username }} sudo setting" ansible.builtin.lineinfile: dest: /etc/sudoers state: present @@ -56,25 +67,31 @@ line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" validate: visudo -cf %s when: + - user_info.failed - params.sudo - name: Refresh connection ansible.builtin.meta: reset_connection - - name: Verify password - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_password: "{{ account.secret }}" - ansible_become: no + - name: "Verify {{ account.username }} password (paramiko)" + ssh_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "password" + delegate_to: localhost - - name: Verify SSH key - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_ssh_private_key_file: "{{ account.private_key_path }}" - ansible_become: no + - name: "Verify {{ account.username }} SSH KEY (paramiko)" + ssh_ping: + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_user: "{{ account.username }}" + login_private_key_path: "{{ account.private_key_path }}" + gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" + become: false when: account.secret_type == "ssh_key" + delegate_to: localhost + diff --git a/apps/accounts/automations/verify_account/custom/ssh/main.yml b/apps/accounts/automations/verify_account/custom/ssh/main.yml index 29b1dc22b..4e35b9587 100644 --- a/apps/accounts/automations/verify_account/custom/ssh/main.yml +++ b/apps/accounts/automations/verify_account/custom/ssh/main.yml @@ -2,6 +2,7 @@ gather_facts: no vars: ansible_connection: local + ansible_become: false tasks: - name: Verify account (paramiko) @@ -12,3 +13,8 @@ login_password: "{{ account.secret }}" login_secret_type: "{{ account.secret_type }}" login_private_key_path: "{{ account.private_key_path }}" + become: "{{ custom_become | default(False) }}" + become_method: "{{ custom_become_method | default('su') }}" + become_user: "{{ custom_become_user | default('') }}" + become_password: "{{ custom_become_password | default('') }}" + become_private_key_path: "{{ custom_become_private_key_path | default(None) }}" diff --git a/apps/accounts/backends/__init__.py b/apps/accounts/backends/__init__.py new file mode 100644 index 000000000..0143d75c8 --- /dev/null +++ b/apps/accounts/backends/__init__.py @@ -0,0 +1,41 @@ +from importlib import import_module + +from django.utils.functional import LazyObject + +from common.utils import get_logger +from ..const import VaultTypeChoices + +__all__ = ['vault_client', 'get_vault_client'] + + +logger = get_logger(__file__) + + +def get_vault_client(raise_exception=False, **kwargs): + enabled = kwargs.get('VAULT_ENABLED') + tp = 'hcp' if enabled else 'local' + try: + module_path = f'apps.accounts.backends.{tp}.main' + client = import_module(module_path).Vault(**kwargs) + except Exception as e: + logger.error(f'Init vault client failed: {e}') + if raise_exception: + raise + tp = VaultTypeChoices.local + module_path = f'apps.accounts.backends.{tp}.main' + client = import_module(module_path).Vault(**kwargs) + return client + + +class VaultClient(LazyObject): + + def _setup(self): + from jumpserver import settings as js_settings + from django.conf import settings + vault_config_names = [k for k in js_settings.__dict__.keys() if k.startswith('VAULT_')] + vault_configs = {name: getattr(settings, name, None) for name in vault_config_names} + self._wrapped = get_vault_client(**vault_configs) + + +""" 为了安全, 页面修改配置, 重启服务后才会重新初始化 vault_client """ +vault_client = VaultClient() diff --git a/apps/accounts/backends/base.py b/apps/accounts/backends/base.py new file mode 100644 index 000000000..f7648caed --- /dev/null +++ b/apps/accounts/backends/base.py @@ -0,0 +1,74 @@ +from abc import ABC, abstractmethod + +from django.forms.models import model_to_dict + +__all__ = ['BaseVault'] + + +class BaseVault(ABC): + + def __init__(self, *args, **kwargs): + self.enabled = kwargs.get('VAULT_ENABLED') + + def get(self, instance): + """ 返回 secret 值 """ + return self._get(instance) + + def create(self, instance): + if not instance.secret_has_save_to_vault: + self._create(instance) + self._clean_db_secret(instance) + self.save_metadata(instance) + + if instance.is_sync_metadata: + self.save_metadata(instance) + + def update(self, instance): + if not instance.secret_has_save_to_vault: + self._update(instance) + self._clean_db_secret(instance) + self.save_metadata(instance) + + if instance.is_sync_metadata: + self.save_metadata(instance) + + def delete(self, instance): + self._delete(instance) + + def save_metadata(self, instance): + metadata = model_to_dict(instance, fields=[ + 'name', 'username', 'secret_type', + 'connectivity', 'su_from', 'privileged' + ]) + metadata = {k: str(v)[:500] for k, v in metadata.items() if v} + return self._save_metadata(instance, metadata) + + # -------- abstractmethod -------- # + + @abstractmethod + def _get(self, instance): + raise NotImplementedError + + @abstractmethod + def _create(self, instance): + raise NotImplementedError + + @abstractmethod + def _update(self, instance): + raise NotImplementedError + + @abstractmethod + def _delete(self, instance): + raise NotImplementedError + + @abstractmethod + def _clean_db_secret(self, instance): + raise NotImplementedError + + @abstractmethod + def _save_metadata(self, instance, metadata): + raise NotImplementedError + + @abstractmethod + def is_active(self, *args, **kwargs) -> (bool, str): + raise NotImplementedError diff --git a/apps/accounts/backends/hcp/__init__.py b/apps/accounts/backends/hcp/__init__.py new file mode 100644 index 000000000..15b6a64ba --- /dev/null +++ b/apps/accounts/backends/hcp/__init__.py @@ -0,0 +1 @@ +from .main import * diff --git a/apps/accounts/backends/hcp/entries.py b/apps/accounts/backends/hcp/entries.py new file mode 100644 index 000000000..ff73ef98d --- /dev/null +++ b/apps/accounts/backends/hcp/entries.py @@ -0,0 +1,84 @@ +import sys +from abc import ABC + +from common.db.utils import Encryptor +from common.utils import lazyproperty + +current_module = sys.modules[__name__] + +__all__ = ['build_entry'] + + +class BaseEntry(ABC): + + def __init__(self, instance): + self.instance = instance + + @lazyproperty + def full_path(self): + path_base = self.path_base + path_spec = self.path_spec + path = f'{path_base}/{path_spec}' + return path + + @property + def path_base(self): + path = f'orgs/{self.instance.org_id}' + return path + + @property + def path_spec(self): + raise NotImplementedError + + def to_internal_data(self): + secret = getattr(self.instance, '_secret', None) + if secret is not None: + secret = Encryptor(secret).encrypt() + data = {'secret': secret} + return data + + @staticmethod + def to_external_data(data): + secret = data.pop('secret', None) + if secret is not None: + secret = Encryptor(secret).decrypt() + return secret + + +class AccountEntry(BaseEntry): + + @property + def path_spec(self): + path = f'assets/{self.instance.asset_id}/accounts/{self.instance.id}' + return path + + +class AccountTemplateEntry(BaseEntry): + + @property + def path_spec(self): + path = f'account-templates/{self.instance.id}' + return path + + +class HistoricalAccountEntry(BaseEntry): + + @property + def path_base(self): + account = self.instance.instance + path = f'accounts/{account.id}/' + return path + + @property + def path_spec(self): + path = f'histories/{self.instance.history_id}' + return path + + +def build_entry(instance) -> BaseEntry: + class_name = instance.__class__.__name__ + entry_class_name = f'{class_name}Entry' + entry_class = getattr(current_module, entry_class_name, None) + if not entry_class: + raise Exception(f'Entry class {entry_class_name} is not found') + return entry_class(instance) diff --git a/apps/accounts/backends/hcp/main.py b/apps/accounts/backends/hcp/main.py new file mode 100644 index 000000000..53491e975 --- /dev/null +++ b/apps/accounts/backends/hcp/main.py @@ -0,0 +1,53 @@ +from common.db.utils import get_logger +from .entries import build_entry +from .service import VaultKVClient +from ..base import BaseVault + +__all__ = ['Vault'] + +logger = get_logger(__name__) + + +class Vault(BaseVault): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.client = VaultKVClient( + url=kwargs.get('VAULT_HCP_HOST'), + token=kwargs.get('VAULT_HCP_TOKEN'), + mount_point=kwargs.get('VAULT_HCP_MOUNT_POINT') + ) + + def is_active(self): + return self.client.is_active() + + def _get(self, instance): + entry = build_entry(instance) + # TODO: get data 是不是层数太多了 + data = self.client.get(path=entry.full_path).get('data', {}) + data = entry.to_external_data(data) + return data + + def _create(self, instance): + entry = build_entry(instance) + data = entry.to_internal_data() + self.client.create(path=entry.full_path, data=data) + + def _update(self, instance): + entry = build_entry(instance) + data = entry.to_internal_data() + self.client.patch(path=entry.full_path, data=data) + + def _delete(self, instance): + entry = build_entry(instance) + self.client.delete(path=entry.full_path) + + def _clean_db_secret(self, instance): + instance.is_sync_metadata = False + instance.mark_secret_save_to_vault() + + def _save_metadata(self, instance, metadata): + try: + entry = build_entry(instance) + self.client.update_metadata(path=entry.full_path, metadata=metadata) + except Exception as e: + logger.error(f'save metadata error: {e}') diff --git a/apps/accounts/backends/hcp/service.py b/apps/accounts/backends/hcp/service.py new file mode 100644 index 000000000..b2fd4ae42 --- /dev/null +++ b/apps/accounts/backends/hcp/service.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# +import hvac +from hvac import exceptions +from requests.exceptions import ConnectionError + +from common.utils import get_logger + +logger = get_logger(__name__) + +__all__ = ['VaultKVClient'] + + +class VaultKVClient(object): + max_versions = 20 + + def __init__(self, url, token, mount_point): + assert isinstance(self.max_versions, int) and self.max_versions >= 3, ( + 'max_versions must to be an integer that is greater than or equal to 3' + ) + self.client = hvac.Client(url=url, token=token) + self.mount_point = mount_point + self.enable_secrets_engine_if_need() + + def is_active(self): + try: + if not self.client.sys.is_initialized(): + return False, 'Vault is not initialized' + if self.client.sys.is_sealed(): + return False, 'Vault is sealed' + if not self.client.is_authenticated(): + return False, 'Vault is not authenticated' + except ConnectionError as e: + logger.error(str(e)) + return False, f'Vault is not reachable: {e}' + else: + return True, '' + + def enable_secrets_engine_if_need(self): + secrets_engines = self.client.sys.list_mounted_secrets_engines() + mount_points = secrets_engines.keys() + if f'{self.mount_point}/' in mount_points: + return + self.client.sys.enable_secrets_engine( + backend_type='kv', + path=self.mount_point, + options={'version': 2} # TODO: version 是否从配置中读取? + ) + self.client.secrets.kv.v2.configure( + max_versions=self.max_versions, + mount_point=self.mount_point + ) + + def get(self, path, version=None): + try: + response = self.client.secrets.kv.v2.read_secret_version( + path=path, + version=version, + mount_point=self.mount_point + ) + except exceptions.InvalidPath as e: + return {} + data = response.get('data', {}) + return data + + def create(self, path, data: dict): + self._update_or_create(path=path, data=data) + + def update(self, path, data: dict): + """ 未更新的数据会被删除 """ + self._update_or_create(path=path, data=data) + + def patch(self, path, data: dict): + """ 未更新的数据不会被删除 """ + self.client.secrets.kv.v2.patch( + path=path, + secret=data, + mount_point=self.mount_point + ) + + def delete(self, path): + self.client.secrets.kv.v2.delete_metadata_and_all_versions( + path=path, + mount_point=self.mount_point, + ) + + def _update_or_create(self, path, data: dict): + self.client.secrets.kv.v2.create_or_update_secret( + path=path, + secret=data, + mount_point=self.mount_point + ) + + def update_metadata(self, path, metadata: dict): + try: + self.client.secrets.kv.v2.update_metadata( + path=path, + mount_point=self.mount_point, + custom_metadata=metadata + ) + except exceptions.InvalidPath as e: + logger.error('Update metadata error: {}'.format(e)) diff --git a/apps/accounts/backends/local/__init__.py b/apps/accounts/backends/local/__init__.py new file mode 100644 index 000000000..15b6a64ba --- /dev/null +++ b/apps/accounts/backends/local/__init__.py @@ -0,0 +1 @@ +from .main import * diff --git a/apps/accounts/backends/local/main.py b/apps/accounts/backends/local/main.py new file mode 100644 index 000000000..a3e608bbc --- /dev/null +++ b/apps/accounts/backends/local/main.py @@ -0,0 +1,36 @@ +from common.utils import get_logger +from ..base import BaseVault + +logger = get_logger(__name__) + +__all__ = ['Vault'] + + +class Vault(BaseVault): + + def is_active(self): + return True, '' + + def _get(self, instance): + secret = getattr(instance, '_secret', None) + return secret + + def _create(self, instance): + """ Ignore """ + pass + + def _update(self, instance): + """ Ignore """ + pass + + def _delete(self, instance): + """ Ignore """ + pass + + def _save_metadata(self, instance, metadata): + """ Ignore """ + pass + + def _clean_db_secret(self, instance): + """ Ignore *重要* 不能删除本地 secret """ + pass diff --git a/apps/accounts/const/__init__.py b/apps/accounts/const/__init__.py index 6db502556..0e1997f00 100644 --- a/apps/accounts/const/__init__.py +++ b/apps/accounts/const/__init__.py @@ -1,2 +1,3 @@ from .account import * from .automation import * +from .vault import * diff --git a/apps/accounts/const/account.py b/apps/accounts/const/account.py index 9a3541001..b1d3e348e 100644 --- a/apps/accounts/const/account.py +++ b/apps/accounts/const/account.py @@ -1,5 +1,5 @@ from django.db.models import TextChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class SecretType(TextChoices): @@ -16,6 +16,10 @@ class AliasAccount(TextChoices): USER = '@USER', _('Dynamic user') ANON = '@ANON', _('Anonymous account') + @classmethod + def virtual_choices(cls): + return [(k, v) for k, v in cls.choices if k not in (cls.ALL,)] + class Source(TextChoices): LOCAL = 'local', _('Local') diff --git a/apps/accounts/const/automation.py b/apps/accounts/const/automation.py index 791527078..cb39703e0 100644 --- a/apps/accounts/const/automation.py +++ b/apps/accounts/const/automation.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import Connectivity from common.db.fields import TreeChoices diff --git a/apps/accounts/const/vault.py b/apps/accounts/const/vault.py new file mode 100644 index 000000000..1e41b6d86 --- /dev/null +++ b/apps/accounts/const/vault.py @@ -0,0 +1,9 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +__all__ = ['VaultTypeChoices'] + + +class VaultTypeChoices(models.TextChoices): + local = 'local', _('Database') + hcp = 'hcp', _('HCP Vault') diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py index be2cf1dfd..e71a5b9fb 100644 --- a/apps/accounts/filters.py +++ b/apps/accounts/filters.py @@ -13,7 +13,8 @@ class AccountFilterSet(BaseFilterSet): hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact') username = drf_filters.CharFilter(field_name="username", lookup_expr='exact') address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact') - asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact') + asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr='exact') + asset = drf_filters.CharFilter(field_name='asset', lookup_expr='exact') assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact') nodes = drf_filters.CharFilter(method='filter_nodes') node_id = drf_filters.CharFilter(method='filter_nodes') @@ -45,7 +46,7 @@ class AccountFilterSet(BaseFilterSet): class Meta: model = Account - fields = ['id', 'asset_id', 'source_id', 'secret_type'] + fields = ['id', 'asset', 'source_id', 'secret_type'] class GatheredAccountFilterSet(BaseFilterSet): diff --git a/apps/accounts/migrations/0012_auto_20230621_1456.py b/apps/accounts/migrations/0012_auto_20230621_1456.py new file mode 100644 index 000000000..389e2b63d --- /dev/null +++ b/apps/accounts/migrations/0012_auto_20230621_1456.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.19 on 2023-06-21 06:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0011_auto_20230506_1443'), + ] + + operations = [ + migrations.RenameField( + model_name='account', + old_name='secret', + new_name='_secret', + ), + migrations.RenameField( + model_name='accounttemplate', + old_name='secret', + new_name='_secret', + ), + migrations.RenameField( + model_name='historicalaccount', + old_name='secret', + new_name='_secret', + ), + ] diff --git a/apps/accounts/migrations/0013_account_backup_recipients.py b/apps/accounts/migrations/0013_account_backup_recipients.py new file mode 100644 index 000000000..6fe03e349 --- /dev/null +++ b/apps/accounts/migrations/0013_account_backup_recipients.py @@ -0,0 +1,77 @@ +# Generated by Django 4.1.10 on 2023-08-03 08:28 +from django.conf import settings +from django.db import migrations, models + +import common.db.encoder + + +def migrate_recipients(apps, schema_editor): + account_backup_model = apps.get_model('accounts', 'AccountBackupAutomation') + execution_model = apps.get_model('accounts', 'AccountBackupExecution') + for account_backup in account_backup_model.objects.all(): + recipients = list(account_backup.recipients.all()) + if not recipients: + continue + account_backup.recipients_part_one.set(recipients) + + objs = [] + for execution in execution_model.objects.all(): + snapshot = execution.snapshot + recipients = snapshot.pop('recipients', {}) + snapshot.update({'recipients_part_one': recipients, 'recipients_part_two': {}}) + objs.append(execution) + execution_model.objects.bulk_update(objs, ['snapshot']) + + +def migrate_snapshot(apps, schema_editor): + model = apps.get_model('accounts', 'AccountBackupExecution') + objs = [] + for execution in model.objects.all(): + execution.snapshot = execution.plan_snapshot + objs.append(execution) + model.objects.bulk_update(objs, ['snapshot']) + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('accounts', '0012_auto_20230621_1456'), + ] + + operations = [ + migrations.AddField( + model_name='accountbackupautomation', + name='recipients_part_one', + field=models.ManyToManyField( + blank=True, related_name='recipient_part_one_plans', + to=settings.AUTH_USER_MODEL, verbose_name='Recipient part one' + ), + ), + migrations.AddField( + model_name='accountbackupautomation', + name='recipients_part_two', + field=models.ManyToManyField( + blank=True, related_name='recipient_part_two_plans', + to=settings.AUTH_USER_MODEL, verbose_name='Recipient part two' + ), + ), + migrations.AddField( + model_name='accountbackupexecution', + name='snapshot', + field=models.JSONField( + default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, + null=True, blank=True, verbose_name='Account backup snapshot' + ), + ), + migrations.RunPython(migrate_snapshot), + migrations.RunPython(migrate_recipients), + migrations.RemoveField( + model_name='accountbackupexecution', + name='plan_snapshot', + ), + migrations.RemoveField( + model_name='accountbackupautomation', + name='recipients', + ), + + ] diff --git a/apps/accounts/migrations/0014_virtualaccount.py b/apps/accounts/migrations/0014_virtualaccount.py new file mode 100644 index 000000000..b2c344fce --- /dev/null +++ b/apps/accounts/migrations/0014_virtualaccount.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.10 on 2023-08-01 09:12 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0013_account_backup_recipients'), + ] + + operations = [ + migrations.CreateModel( + name='VirtualAccount', + fields=[ + ('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account')], max_length=128, verbose_name='Alias')), + ('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')), + ], + options={ + 'unique_together': {('alias', 'org_id')}, + }, + ), + ] diff --git a/apps/accounts/models/__init__.py b/apps/accounts/models/__init__.py index df686a50b..9a3b780c8 100644 --- a/apps/accounts/models/__init__.py +++ b/apps/accounts/models/__init__.py @@ -1,3 +1,5 @@ from .account import * from .automations import * from .base import * +from .template import * +from .virtual import * diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py index d84978b17..1bdf6e83d 100644 --- a/apps/accounts/models/account.py +++ b/apps/accounts/models/account.py @@ -1,15 +1,14 @@ from django.db import models -from django.db.models import Count, Q -from django.utils import timezone from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from assets.models.base import AbsConnectivity from common.utils import lazyproperty from .base import BaseAccount -from ..const import AliasAccount, Source +from .mixins import VaultModelMixin +from ..const import Source -__all__ = ['Account', 'AccountTemplate'] +__all__ = ['Account', 'AccountHistoricalRecords'] class AccountHistoricalRecords(HistoricalRecords): @@ -32,7 +31,7 @@ class AccountHistoricalRecords(HistoricalRecords): diff = attrs - history_attrs if not diff: return - super().post_save(instance, created, using=using, **kwargs) + return super().post_save(instance, created, using=using, **kwargs) def create_history_model(self, model, inherited): if self.included_fields and not self.excluded_fields: @@ -53,7 +52,7 @@ class Account(AbsConnectivity, BaseAccount): on_delete=models.SET_NULL, verbose_name=_("Su from") ) version = models.IntegerField(default=0, verbose_name=_('Version')) - history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) + history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version']) source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source')) source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID')) @@ -88,29 +87,6 @@ class Account(AbsConnectivity, BaseAccount): def has_secret(self): return bool(self.secret) - @classmethod - def get_special_account(cls, name): - if name == AliasAccount.INPUT.value: - return cls.get_manual_account() - elif name == AliasAccount.ANON.value: - return cls.get_anonymous_account() - else: - return cls(name=name, username=name, secret=None) - - @classmethod - def get_manual_account(cls): - """ @INPUT 手动登录的账号(any) """ - return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None) - - @classmethod - def get_anonymous_account(cls): - return cls(name=AliasAccount.ANON.label, username=AliasAccount.ANON.value, secret=None) - - @classmethod - def get_user_account(cls): - """ @USER 动态用户的账号(self) """ - return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value, secret=None) - @lazyproperty def versions(self): return self.history.count() @@ -120,81 +96,19 @@ class Account(AbsConnectivity, BaseAccount): return self.asset.accounts.exclude(id=self.id).exclude(su_from=self) -class AccountTemplate(BaseAccount): - su_from = models.ForeignKey( - 'self', related_name='su_to', null=True, - on_delete=models.SET_NULL, verbose_name=_("Su from") - ) +def replace_history_model_with_mixin(): + """ + 替换历史模型中的父类为指定的Mixin类。 - class Meta: - verbose_name = _('Account template') - unique_together = ( - ('name', 'org_id'), - ) - permissions = [ - ('view_accounttemplatesecret', _('Can view asset account template secret')), - ('change_accounttemplatesecret', _('Can change asset account template secret')), - ] + Parameters: + model (class): 历史模型类,例如 Account.history.model + mixin_class (class): 要替换为的Mixin类 - @classmethod - def get_su_from_account_templates(cls, pk=None): - if pk is None: - return cls.objects.all() - return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk)) + Returns: + None + """ + model = Account.history.model + model.__bases__ = (VaultModelMixin,) + model.__bases__ - def __str__(self): - return f'{self.name}({self.username})' - def get_su_from_account(self, asset): - su_from = self.su_from - if su_from and asset.platform.su_enabled: - account = asset.accounts.filter( - username=su_from.username, - secret_type=su_from.secret_type - ).first() - return account - - def __str__(self): - return self.username - - @staticmethod - def bulk_update_accounts(accounts, data): - history_model = Account.history.model - account_ids = accounts.values_list('id', flat=True) - history_accounts = history_model.objects.filter(id__in=account_ids) - account_id_count_map = { - str(i['id']): i['count'] - for i in history_accounts.values('id').order_by('id') - .annotate(count=Count(1)).values('id', 'count') - } - - for account in accounts: - account_id = str(account.id) - account.version = account_id_count_map.get(account_id) + 1 - for k, v in data.items(): - setattr(account, k, v) - Account.objects.bulk_update(accounts, ['version', 'secret']) - - @staticmethod - def bulk_create_history_accounts(accounts, user_id): - history_model = Account.history.model - history_account_objs = [] - for account in accounts: - history_account_objs.append( - history_model( - id=account.id, - version=account.version, - secret=account.secret, - secret_type=account.secret_type, - history_user_id=user_id, - history_date=timezone.now() - ) - ) - history_model.objects.bulk_create(history_account_objs) - - def bulk_sync_account_secret(self, accounts, user_id): - """ 批量同步账号密码 """ - if not accounts: - return - self.bulk_update_accounts(accounts, {'secret': self.secret}) - self.bulk_create_history_accounts(accounts, user_id) +replace_history_model_with_mixin() diff --git a/apps/accounts/models/automations/backup_account.py b/apps/accounts/models/automations/backup_account.py index fba3b3149..c99c9e220 100644 --- a/apps/accounts/models/automations/backup_account.py +++ b/apps/accounts/models/automations/backup_account.py @@ -6,7 +6,7 @@ import uuid from celery import current_task from django.db import models from django.db.models import F -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.const.choices import Trigger from common.db.encoder import ModelJSONFieldEncoder @@ -22,9 +22,13 @@ logger = get_logger(__file__) class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): types = models.JSONField(default=list) - recipients = models.ManyToManyField( - 'users.User', related_name='recipient_escape_route_plans', blank=True, - verbose_name=_("Recipient") + recipients_part_one = models.ManyToManyField( + 'users.User', related_name='recipient_part_one_plans', blank=True, + verbose_name=_("Recipient part one") + ) + recipients_part_two = models.ManyToManyField( + 'users.User', related_name='recipient_part_two_plans', blank=True, + verbose_name=_("Recipient part two") ) def __str__(self): @@ -52,9 +56,13 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): 'org_id': self.org_id, 'created_by': self.created_by, 'types': self.types, - 'recipients': { - str(recipient.id): (str(recipient), bool(recipient.secret_key)) - for recipient in self.recipients.all() + 'recipients_part_one': { + str(user.id): (str(user), bool(user.secret_key)) + for user in self.recipients_part_one.all() + }, + 'recipients_part_two': { + str(user.id): (str(user), bool(user.secret_key)) + for user in self.recipients_part_two.all() } } @@ -68,7 +76,7 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): except AttributeError: hid = str(uuid.uuid4()) execution = AccountBackupExecution.objects.create( - id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger + id=hid, plan=self, snapshot=self.to_attr_json(), trigger=trigger ) return execution.start() @@ -85,7 +93,7 @@ class AccountBackupExecution(OrgModelMixin): timedelta = models.FloatField( default=0.0, verbose_name=_('Time'), null=True ) - plan_snapshot = models.JSONField( + snapshot = models.JSONField( encoder=ModelJSONFieldEncoder, default=dict, blank=True, null=True, verbose_name=_('Account backup snapshot') ) @@ -108,16 +116,9 @@ class AccountBackupExecution(OrgModelMixin): @property def types(self): - types = self.plan_snapshot.get('types') + types = self.snapshot.get('types') return types - @property - def recipients(self): - recipients = self.plan_snapshot.get('recipients') - if not recipients: - return [] - return recipients.values() - @lazyproperty def backup_accounts(self): from accounts.models import Account diff --git a/apps/accounts/models/automations/change_secret.py b/apps/accounts/models/automations/change_secret.py index 2226040a2..0efeff049 100644 --- a/apps/accounts/models/automations/change_secret.py +++ b/apps/accounts/models/automations/change_secret.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import ( AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy @@ -86,7 +86,7 @@ class ChangeSecretRecord(JMSBaseModel): asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True) account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True) old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret')) - new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) + new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret')) date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started')) date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished')) status = models.CharField(max_length=16, default='pending') diff --git a/apps/accounts/models/automations/gather_account.py b/apps/accounts/models/automations/gather_account.py index dd9b5c862..9ea5dcc3b 100644 --- a/apps/accounts/models/automations/gather_account.py +++ b/apps/accounts/models/automations/gather_account.py @@ -1,6 +1,6 @@ from django.db import models from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import AutomationTypes, Source from accounts.models import Account diff --git a/apps/accounts/models/automations/push_account.py b/apps/accounts/models/automations/push_account.py index d3a14411f..fe628f4cb 100644 --- a/apps/accounts/models/automations/push_account.py +++ b/apps/accounts/models/automations/push_account.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import AutomationTypes from accounts.models import Account diff --git a/apps/accounts/models/automations/verify_account.py b/apps/accounts/models/automations/verify_account.py index 03ec4f8c9..ae9beb9a6 100644 --- a/apps/accounts/models/automations/verify_account.py +++ b/apps/accounts/models/automations/verify_account.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import AutomationTypes from .base import AccountBaseAutomation diff --git a/apps/accounts/models/base.py b/apps/accounts/models/base.py index e4bd780ac..b06012c41 100644 --- a/apps/accounts/models/base.py +++ b/apps/accounts/models/base.py @@ -6,36 +6,35 @@ from hashlib import md5 import sshpubkeys from django.conf import settings from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import SecretType -from common.db import fields from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key ) +from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin from orgs.mixins.models import JMSOrgBaseModel, OrgManager logger = get_logger(__file__) -class BaseAccountQuerySet(models.QuerySet): +class BaseAccountQuerySet(VaultQuerySetMixin, models.QuerySet): def active(self): return self.filter(is_active=True) -class BaseAccountManager(OrgManager): +class BaseAccountManager(VaultManagerMixin, OrgManager): def active(self): return self.get_queryset().active() -class BaseAccount(JMSOrgBaseModel): +class BaseAccount(VaultModelMixin, JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) secret_type = models.CharField( max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type') ) - secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) diff --git a/apps/accounts/models/mixins/__init__.py b/apps/accounts/models/mixins/__init__.py new file mode 100644 index 000000000..203d7b3cb --- /dev/null +++ b/apps/accounts/models/mixins/__init__.py @@ -0,0 +1 @@ +from .vault import * diff --git a/apps/accounts/models/mixins/vault.py b/apps/accounts/models/mixins/vault.py new file mode 100644 index 000000000..b16927b62 --- /dev/null +++ b/apps/accounts/models/mixins/vault.py @@ -0,0 +1,94 @@ +from django.db import models +from django.db.models.signals import post_save +from django.utils.translation import gettext_lazy as _ + +from common.db import fields + +__all__ = ['VaultQuerySetMixin', 'VaultManagerMixin', 'VaultModelMixin'] + + +class VaultQuerySetMixin(models.QuerySet): + + def update(self, **kwargs): + """ + 1. 替换 secret 为 _secret + 2. 触发 post_save 信号 + """ + if 'secret' in kwargs: + kwargs.update({ + '_secret': kwargs.pop('secret') + }) + rows = super().update(**kwargs) + + # 为了获取更新后的对象所以单独查询一次 + ids = self.values_list('id', flat=True) + objs = self.model.objects.filter(id__in=ids) + for obj in objs: + post_save.send(obj.__class__, instance=obj, created=False) + return rows + + +class VaultManagerMixin(models.Manager): + """ 触发 bulk_create 和 bulk_update 操作下的 post_save 信号 """ + + def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): + objs = super().bulk_create(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts) + for obj in objs: + post_save.send(obj.__class__, instance=obj, created=True) + return objs + + def bulk_update(self, objs, batch_size=None, ignore_conflicts=False): + objs = super().bulk_update(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts) + for obj in objs: + post_save.send(obj.__class__, instance=obj, created=False) + return objs + + +class VaultModelMixin(models.Model): + _secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) + is_sync_metadata = True + + class Meta: + abstract = True + + # 缓存 secret 值, lazy-property 不能用 + __secret = None + + @property + def secret(self): + if self.__secret: + return self.__secret + from accounts.backends import vault_client + secret = vault_client.get(self) + if not secret and not self.secret_has_save_to_vault: + # vault_client 获取不到, 并且 secret 没有保存到 vault, 就从 self._secret 获取 + secret = self._secret + self.__secret = secret + return self.__secret + + @secret.setter + def secret(self, value): + """ + 保存的时候通过 post_save 信号监听进行处理, + 先保存到 db, 再保存到 vault 同时删除本地 db _secret 值 + """ + self._secret = value + self.__secret = value + + _secret_save_to_vault_mark = '# Secret-has-been-saved-to-vault #' + + def mark_secret_save_to_vault(self): + self._secret = self._secret_save_to_vault_mark + self.save() + + @property + def secret_has_save_to_vault(self): + return self._secret == self._secret_save_to_vault_mark + + def save(self, *args, **kwargs): + """ 通过 post_save signal 处理 _secret 数据 """ + update_fields = kwargs.get('update_fields') + if update_fields and 'secret' in update_fields: + update_fields.remove('secret') + update_fields.append('_secret') + return super().save(*args, **kwargs) diff --git a/apps/accounts/models/template.py b/apps/accounts/models/template.py new file mode 100644 index 000000000..adee261e6 --- /dev/null +++ b/apps/accounts/models/template.py @@ -0,0 +1,86 @@ +from django.db import models +from django.db.models import Count, Q +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + +from .account import Account +from .base import BaseAccount + +__all__ = ['AccountTemplate', ] + + +class AccountTemplate(BaseAccount): + su_from = models.ForeignKey( + 'self', related_name='su_to', null=True, + on_delete=models.SET_NULL, verbose_name=_("Su from") + ) + + class Meta: + verbose_name = _('Account template') + unique_together = ( + ('name', 'org_id'), + ) + permissions = [ + ('view_accounttemplatesecret', _('Can view asset account template secret')), + ('change_accounttemplatesecret', _('Can change asset account template secret')), + ] + + @classmethod + def get_su_from_account_templates(cls, pk=None): + if pk is None: + return cls.objects.all() + return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk)) + + def __str__(self): + return f'{self.name}({self.username})' + + def get_su_from_account(self, asset): + su_from = self.su_from + if su_from and asset.platform.su_enabled: + account = asset.accounts.filter( + username=su_from.username, + secret_type=su_from.secret_type + ).first() + return account + + @staticmethod + def bulk_update_accounts(accounts, data): + history_model = Account.history.model + account_ids = accounts.values_list('id', flat=True) + history_accounts = history_model.objects.filter(id__in=account_ids) + account_id_count_map = { + str(i['id']): i['count'] + for i in history_accounts.values('id').order_by('id') + .annotate(count=Count(1)).values('id', 'count') + } + + for account in accounts: + account_id = str(account.id) + account.version = account_id_count_map.get(account_id) + 1 + for k, v in data.items(): + setattr(account, k, v) + Account.objects.bulk_update(accounts, ['version', 'secret']) + + @staticmethod + def bulk_create_history_accounts(accounts, user_id): + history_model = Account.history.model + history_account_objs = [] + for account in accounts: + history_account_objs.append( + history_model( + id=account.id, + version=account.version, + secret=account.secret, + secret_type=account.secret_type, + history_user_id=user_id, + history_date=timezone.now() + ) + ) + history_model.objects.bulk_create(history_account_objs) + + def bulk_sync_account_secret(self, accounts, user_id): + """ 批量同步账号密码 """ + if not accounts: + return + self.bulk_update_accounts(accounts, {'secret': self.secret}) + self.bulk_create_history_accounts(accounts, user_id) diff --git a/apps/accounts/models/virtual.py b/apps/accounts/models/virtual.py new file mode 100644 index 000000000..88cf1b605 --- /dev/null +++ b/apps/accounts/models/virtual.py @@ -0,0 +1,103 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from accounts.const import AliasAccount +from orgs.mixins.models import JMSOrgBaseModel + +__all__ = ['VirtualAccount'] + +from orgs.utils import tmp_to_org + + +class VirtualAccount(JMSOrgBaseModel): + alias = models.CharField(max_length=128, choices=AliasAccount.virtual_choices(), verbose_name=_('Alias'), ) + secret_from_login = models.BooleanField(default=None, null=True, verbose_name=_("Secret from login"), ) + + class Meta: + unique_together = [('alias', 'org_id')] + + @property + def name(self): + return self.get_alias_display() + + @property + def username(self): + usernames_map = { + AliasAccount.INPUT: _("Manual input"), + AliasAccount.USER: _("Same with user"), + AliasAccount.ANON: '' + } + usernames_map = {str(k): v for k, v in usernames_map.items()} + return usernames_map.get(self.alias, '') + + @property + def comment(self): + comments_map = { + AliasAccount.INPUT: _('Non-asset account, Input username/password on connect'), + AliasAccount.USER: _('The account username name same with user on connect'), + AliasAccount.ANON: _('Connect asset without using a username and password, ' + 'and it only supports web-based and custom-type assets'), + } + comments_map = {str(k): v for k, v in comments_map.items()} + return comments_map.get(self.alias, '') + + @classmethod + def get_or_init_queryset(cls): + aliases = [i[0] for i in AliasAccount.virtual_choices()] + alias_created = cls.objects.all().values_list('alias', flat=True) + need_created = set(aliases) - set(alias_created) + + if need_created: + accounts = [cls(alias=alias) for alias in need_created] + cls.objects.bulk_create(accounts, ignore_conflicts=True) + return cls.objects.all() + + @classmethod + def get_special_account(cls, alias, user, asset, input_username='', input_secret='', from_permed=True): + if alias == AliasAccount.INPUT.value: + account = cls.get_manual_account(input_username, input_secret, from_permed) + elif alias == AliasAccount.ANON.value: + account = cls.get_anonymous_account() + elif alias == AliasAccount.USER.value: + account = cls.get_same_account(user, asset, input_secret=input_secret, from_permed=from_permed) + else: + account = cls(name=alias, username=alias, secret=None) + account.alias = alias + if asset: + account.asset = asset + account.org_id = asset.org_id + return account + + @classmethod + def get_manual_account(cls, input_username='', input_secret='', from_permed=True): + """ @INPUT 手动登录的账号(any) """ + from .account import Account + if from_permed: + username = AliasAccount.INPUT.value + secret = '' + else: + username = input_username + secret = input_secret + return Account(name=AliasAccount.INPUT.label, username=username, secret=secret) + + @classmethod + def get_anonymous_account(cls): + from .account import Account + return Account(name=AliasAccount.ANON.label, username=AliasAccount.ANON.value, secret=None) + + @classmethod + def get_same_account(cls, user, asset, input_secret='', from_permed=True): + """ @USER 动态用户的账号(self) """ + from .account import Account + username = user.username + + with tmp_to_org(asset.org): + same_account = cls.objects.filter(alias='@USER').first() + + secret = '' + if same_account and same_account.secret_from_login: + secret = user.get_cached_password_if_has() + + if not secret and not from_permed: + secret = input_secret + return Account(name=AliasAccount.USER.label, username=username, secret=secret) diff --git a/apps/accounts/notifications.py b/apps/accounts/notifications.py index 3c4897df1..190eb8fb6 100644 --- a/apps/accounts/notifications.py +++ b/apps/accounts/notifications.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.tasks import send_mail_attachment_async from users.models import User diff --git a/apps/accounts/serializers/account/__init__.py b/apps/accounts/serializers/account/__init__.py index 2829931b2..207029047 100644 --- a/apps/accounts/serializers/account/__init__.py +++ b/apps/accounts/serializers/account/__init__.py @@ -1,5 +1,6 @@ from .account import * from .backup import * from .base import * -from .template import * from .gathered_account import * +from .template import * +from .virtual import * diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index ebeb11788..fc5f0b160 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -3,7 +3,7 @@ from copy import deepcopy from django.db import IntegrityError from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.generics import get_object_or_404 from rest_framework.validators import UniqueTogetherValidator @@ -95,6 +95,8 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): field.name for field in template._meta.fields if field.name not in ignore_fields ] + field_names = [name if name != '_secret' else 'secret' for name in field_names] + attrs = {} for name in field_names: value = getattr(template, name, None) @@ -198,7 +200,6 @@ class AccountAssetSerializer(serializers.ModelSerializer): class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer): asset = AccountAssetSerializer(label=_('Asset')) - has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) source = LabeledChoiceField( choices=Source.choices, label=_("Source"), required=False, allow_null=True, default=Source.LOCAL @@ -233,6 +234,15 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize return queryset +class AccountDetailSerializer(AccountSerializer): + has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) + + class Meta(AccountSerializer.Meta): + model = Account + fields = AccountSerializer.Meta.fields + ['has_secret'] + read_only_fields = AccountSerializer.Meta.read_only_fields + ['has_secret'] + + class AssetAccountBulkSerializerResultSerializer(serializers.Serializer): asset = serializers.CharField(read_only=True, label=_('Asset')) state = serializers.CharField(read_only=True, label=_('State')) diff --git a/apps/accounts/serializers/account/backup.py b/apps/accounts/serializers/account/backup.py index 712bdd095..f11861fa3 100644 --- a/apps/accounts/serializers/account/backup.py +++ b/apps/accounts/serializers/account/backup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import AccountBackupAutomation, AccountBackupExecution @@ -24,7 +24,7 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer ] fields = read_only_fields + [ 'id', 'name', 'is_periodic', 'interval', 'crontab', - 'comment', 'recipients', 'types' + 'comment', 'types', 'recipients_part_one', 'recipients_part_two' ] extra_kwargs = { 'name': {'required': True}, @@ -44,7 +44,7 @@ class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer): class Meta: model = AccountBackupExecution read_only_fields = [ - 'id', 'date_start', 'timedelta', 'plan_snapshot', - 'trigger', 'reason', 'is_success', 'org_id', 'recipients' + 'id', 'date_start', 'timedelta', 'snapshot', + 'trigger', 'reason', 'is_success', 'org_id' ] fields = read_only_fields + ['plan'] diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py index 2f9660bd5..5289ea25b 100644 --- a/apps/accounts/serializers/account/base.py +++ b/apps/accounts/serializers/account/base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.const import SecretType @@ -61,20 +61,18 @@ class AuthValidateMixin(serializers.Serializer): class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): - has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) class Meta: model = BaseAccount fields_mini = ['id', 'name', 'username'] fields_small = fields_mini + [ - 'secret_type', 'secret', 'has_secret', 'passphrase', + 'secret_type', 'secret', 'passphrase', 'privileged', 'is_active', 'spec_info', ] fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] fields = fields_small + fields_other read_only_fields = [ - 'has_secret', 'spec_info', - 'date_verified', 'created_by', 'date_created', + 'spec_info', 'date_verified', 'created_by', 'date_created', ] extra_kwargs = { 'spec_info': {'label': _('Spec info')}, diff --git a/apps/accounts/serializers/account/gathered_account.py b/apps/accounts/serializers/account/gathered_account.py index b382fda4c..a36bddc5e 100644 --- a/apps/accounts/serializers/account/gathered_account.py +++ b/apps/accounts/serializers/account/gathered_account.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.models import GatheredAccount from orgs.mixins.serializers import BulkOrgResourceModelSerializer diff --git a/apps/accounts/serializers/account/template.py b/apps/accounts/serializers/account/template.py index ffe8c7564..9642c36b0 100644 --- a/apps/accounts/serializers/account/template.py +++ b/apps/accounts/serializers/account/template.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import AccountTemplate, Account diff --git a/apps/accounts/serializers/account/virtual.py b/apps/accounts/serializers/account/virtual.py new file mode 100644 index 000000000..d4bd42589 --- /dev/null +++ b/apps/accounts/serializers/account/virtual.py @@ -0,0 +1,26 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from accounts.models import VirtualAccount + +__all__ = ['VirtualAccountSerializer'] + + +class VirtualAccountSerializer(serializers.ModelSerializer): + class Meta: + model = VirtualAccount + field_mini = ['id', 'alias', 'username', 'name'] + common_fields = ['date_created', 'date_updated', 'comment'] + fields = field_mini + [ + 'secret_from_login', + ] + common_fields + read_only_fields = common_fields + common_fields + extra_kwargs = { + 'comment': {'label': _('Comment')}, + 'name': {'label': _('Name')}, + 'username': {'label': _('Username')}, + 'secret_from_login': {'help_text': _('Current only support login from AD/LDAP. Secret priority: ' + 'Same account in asset secret > Login secret > Manual input') + }, + 'alias': {'required': False}, + } diff --git a/apps/accounts/serializers/automations/base.py b/apps/accounts/serializers/automations/base.py index 8e7f11f23..c2cd21be3 100644 --- a/apps/accounts/serializers/automations/base.py +++ b/apps/accounts/serializers/automations/base.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import AutomationExecution diff --git a/apps/accounts/serializers/automations/change_secret.py b/apps/accounts/serializers/automations/change_secret.py index 3c6e11205..da8a1585c 100644 --- a/apps/accounts/serializers/automations/change_secret.py +++ b/apps/accounts/serializers/automations/change_secret.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.const import ( diff --git a/apps/accounts/signal_handlers.py b/apps/accounts/signal_handlers.py index cf09842cc..868e85614 100644 --- a/apps/accounts/signal_handlers.py +++ b/apps/accounts/signal_handlers.py @@ -1,8 +1,9 @@ -from django.db.models.signals import pre_save +from django.db.models.signals import pre_save, post_save, post_delete from django.dispatch import receiver +from accounts.backends import vault_client from common.utils import get_logger -from .models import Account +from .models import Account, AccountTemplate logger = get_logger(__name__) @@ -13,3 +14,23 @@ def on_account_pre_save(sender, instance, **kwargs): instance.version = 1 else: instance.version = instance.history.count() + + +class VaultSignalHandler(object): + """ 处理 Vault 相关的信号 """ + + @staticmethod + def save_to_vault(sender, instance, created, **kwargs): + if created: + vault_client.create(instance) + else: + vault_client.update(instance) + + @staticmethod + def delete_to_vault(sender, instance, **kwargs): + vault_client.delete(instance) + + +for model in (Account, AccountTemplate, Account.history.model): + post_save.connect(VaultSignalHandler.save_to_vault, sender=model) + post_delete.connect(VaultSignalHandler.delete_to_vault, sender=model) diff --git a/apps/accounts/tasks/backup_account.py b/apps/accounts/tasks/backup_account.py index 16eafaf5e..c9b881b4d 100644 --- a/apps/accounts/tasks/backup_account.py +++ b/apps/accounts/tasks/backup_account.py @@ -23,7 +23,7 @@ def task_activity_callback(self, pid, trigger, *args, **kwargs): @shared_task(verbose_name=_('Execute account backup plan'), activity_callback=task_activity_callback) -def execute_account_backup_task(pid, trigger): +def execute_account_backup_task(pid, trigger, **kwargs): from accounts.models import AccountBackupAutomation with tmp_to_root_org(): plan = get_object_or_none(AccountBackupAutomation, pk=pid) diff --git a/apps/accounts/tasks/push_account.py b/apps/accounts/tasks/push_account.py index 623481be9..0e0608999 100644 --- a/apps/accounts/tasks/push_account.py +++ b/apps/accounts/tasks/push_account.py @@ -1,5 +1,5 @@ from celery import shared_task -from django.utils.translation import gettext_noop, ugettext_lazy as _ +from django.utils.translation import gettext_noop, gettext_lazy as _ from accounts.const import AutomationTypes from accounts.tasks.common import quickstart_automation_by_snapshot diff --git a/apps/accounts/tasks/vault.py b/apps/accounts/tasks/vault.py new file mode 100644 index 000000000..6429b7133 --- /dev/null +++ b/apps/accounts/tasks/vault.py @@ -0,0 +1,68 @@ +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime + +from celery import shared_task +from django.utils.translation import gettext_lazy as _ + +from accounts.backends import vault_client +from accounts.models import Account, AccountTemplate +from common.utils import get_logger +from orgs.utils import tmp_to_root_org + +logger = get_logger(__name__) + + +def sync_instance(instance): + instance_desc = f'[{instance._meta.verbose_name}-{instance.id}-{instance}]' + if instance.secret_has_save_to_vault: + msg = f'\033[32m- 跳过同步: {instance_desc}, 原因: [已同步]' + return "skipped", msg + + try: + vault_client.create(instance) + except Exception as e: + msg = f'\033[31m- 同步失败: {instance_desc}, 原因: [{e}]' + return "failed", msg + else: + msg = f'\033[32m- 同步成功: {instance_desc}' + return "succeeded", msg + + +@shared_task(verbose_name=_('Sync secret to vault')) +def sync_secret_to_vault(): + if not vault_client.enabled: + # 这里不能判断 settings.VAULT_ENABLED, 必须判断当前 vault_client 的类型 + print('\033[35m>>> 当前 Vault 功能未开启, 不需要同步') + return + + failed, skipped, succeeded = 0, 0, 0 + to_sync_models = [Account, AccountTemplate, Account.history.model] + print(f'\033[33m>>> 开始同步密钥数据到 Vault ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})') + with tmp_to_root_org(): + instances = [] + for model in to_sync_models: + instances += list(model.objects.all()) + + with ThreadPoolExecutor(max_workers=10) as executor: + tasks = [executor.submit(sync_instance, instance) for instance in instances] + + for future in as_completed(tasks): + status, msg = future.result() + print(msg) + if status == "succeeded": + succeeded += 1 + elif status == "failed": + failed += 1 + elif status == "skipped": + skipped += 1 + + total = succeeded + failed + skipped + print( + f'\033[33m>>> 同步完成: {model.__module__}, ' + f'共计: {total}, ' + f'成功: {succeeded}, ' + f'失败: {failed}, ' + f'跳过: {skipped}' + ) + print(f'\033[33m>>> 全部同步完成 ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})') + print('\033[0m') diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index 94c8311e0..12d04274e 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -9,6 +9,7 @@ app_name = 'accounts' router = BulkRouter() router.register(r'accounts', api.AccountViewSet, 'account') +router.register(r'virtual-accounts', api.VirtualAccountViewSet, 'virtual-account') router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-account') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') diff --git a/apps/accounts/utils.py b/apps/accounts/utils.py index ef0d61fe1..c0e3fc1cd 100644 --- a/apps/accounts/utils.py +++ b/apps/accounts/utils.py @@ -1,9 +1,7 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from accounts.const import ( - SecretType, DEFAULT_PASSWORD_RULES -) +from accounts.const import SecretType, DEFAULT_PASSWORD_RULES from common.utils import ssh_key_gen, random_string from common.utils import validate_ssh_private_key, parse_ssh_private_key_str diff --git a/apps/acls/apps.py b/apps/acls/apps.py index 0a93fe495..291ce855e 100644 --- a/apps/acls/apps.py +++ b/apps/acls/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class AclsConfig(AppConfig): diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 9029142f2..4fbfd0829 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -3,7 +3,7 @@ import re from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import lazyproperty, get_logger from orgs.mixins.models import JMSOrgBaseModel diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index 7191fe4b6..6f2e21b34 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_request_ip, get_ip_city from common.utils.timezone import local_now_display diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 5d2293a18..7669152b4 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .base import UserAssetAccountBaseACL diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py index 94c4d9fa1..4c2d80bfc 100644 --- a/apps/acls/serializers/base.py +++ b/apps/acls/serializers/base.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from acls.models.base import BaseACL diff --git a/apps/acls/serializers/command_acl.py b/apps/acls/serializers/command_acl.py index 672164012..dde953277 100644 --- a/apps/acls/serializers/command_acl.py +++ b/apps/acls/serializers/command_acl.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from acls.models import CommandGroup, CommandFilterACL diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py index c86424986..737e5269d 100644 --- a/apps/acls/serializers/login_acl.py +++ b/apps/acls/serializers/login_acl.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from common.serializers import MethodSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer diff --git a/apps/acls/serializers/rules/rules.py b/apps/acls/serializers/rules/rules.py index 85b865cd6..4c88d0ac7 100644 --- a/apps/acls/serializers/rules/rules.py +++ b/apps/acls/serializers/rules/rules.py @@ -1,7 +1,7 @@ # coding: utf-8 # +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment diff --git a/apps/applications/apps.py b/apps/applications/apps.py index a025a0517..c672bf36e 100644 --- a/apps/applications/apps.py +++ b/apps/applications/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.utils.translation import ugettext_lazy as _ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class ApplicationsConfig(AppConfig): @@ -9,5 +9,4 @@ class ApplicationsConfig(AppConfig): verbose_name = _('Applications') def ready(self): - from . import signal_handlers super().ready() diff --git a/apps/applications/migrations/0006_application.py b/apps/applications/migrations/0006_application.py index fbe629873..423e9468a 100644 --- a/apps/applications/migrations/0006_application.py +++ b/apps/applications/migrations/0006_application.py @@ -2,7 +2,6 @@ from django.db import migrations, models import django.db.models.deletion -import django_mysql.models import uuid @@ -127,7 +126,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=128, verbose_name='Name')), ('category', models.CharField(choices=[('db', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Cloud')], max_length=16, verbose_name='Category')), ('type', models.CharField(choices=[('mysql', 'MySQL'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('mariadb', 'MariaDB'), ('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom'), ('k8s', 'Kubernetes')], max_length=16, verbose_name='Type')), - ('attrs', django_mysql.models.JSONField(default=dict)), + ('attrs', models.JSONField(default=dict)), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), ('domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='assets.Domain', verbose_name='Domain')), ], diff --git a/apps/applications/models.py b/apps/applications/models.py index 60efc8aab..c13ddc092 100644 --- a/apps/applications/models.py +++ b/apps/applications/models.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 40d676d78..86097247c 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -1,5 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.detail import SingleObjectMixin from rest_framework.serializers import ValidationError from rest_framework.views import APIView, Response @@ -29,6 +29,7 @@ class DomainViewSet(OrgBulkModelViewSet): def get_queryset(self): return super().get_queryset().prefetch_related('assets') + class GatewayViewSet(HostViewSet): perm_model = Gateway filterset_fields = ("domain__name", "name", "domain") diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 2cd928605..7d3c1c7a8 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -2,7 +2,7 @@ from typing import List from rest_framework.request import Request -from assets.models import Node, PlatformProtocol, Protocol +from assets.models import Node, Protocol from assets.utils import get_node_from_request, is_query_node_all_assets from common.utils import lazyproperty, timeit @@ -42,7 +42,7 @@ class SerializeToTreeNodeMixin: 'name': _name(node), 'title': _name(node), 'pId': node.parent_key, - 'isParent': True, + 'isParent': node.assets_amount > 0, 'open': _open(node), 'meta': { 'data': { @@ -70,25 +70,18 @@ class SerializeToTreeNodeMixin: @timeit def serialize_assets(self, assets, node_key=None, pid=None): - sftp_enabled_platform = PlatformProtocol.objects \ - .filter(name='ssh', setting__sftp_enabled=True) \ - .values_list('platform', flat=True) \ - .distinct() if node_key is None: get_pid = lambda asset: getattr(asset, 'parent_key', '') else: get_pid = lambda asset: node_key - ssh_asset_ids = [ - str(i) for i in - Protocol.objects.filter(name='ssh').values_list('asset_id', flat=True) - ] + sftp_asset_ids = Protocol.objects.filter(name='sftp') \ + .values_list('asset_id', flat=True) + sftp_asset_ids = list(sftp_asset_ids) data = [ { 'id': str(asset.id), 'name': asset.name, - 'title': - f'{asset.address}\n{asset.comment}' - if asset.comment else asset.address, + 'title': f'{asset.address}\n{asset.comment}', 'pId': pid or get_pid(asset), 'isParent': False, 'open': False, @@ -99,8 +92,7 @@ class SerializeToTreeNodeMixin: 'data': { 'platform_type': asset.platform.type, 'org_name': asset.org_name, - 'sftp': (asset.platform_id in sftp_enabled_platform) \ - and (str(asset.id) in ssh_asset_ids), + 'sftp': asset.id in sftp_asset_ids, 'name': asset.name, 'address': asset.address }, diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 47ac0b4e6..30c533179 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -3,7 +3,7 @@ from collections import namedtuple, defaultdict from functools import partial from django.db.models.signals import m2m_changed -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 diff --git a/apps/assets/apps.py b/apps/assets/apps.py index a91b10ffc..9ebd7a93d 100644 --- a/apps/assets/apps.py +++ b/apps/assets/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.utils.translation import ugettext_lazy as _ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class AssetsConfig(AppConfig): @@ -12,7 +12,6 @@ class AssetsConfig(AppConfig): super().__init__(*args, **kwargs) def ready(self): + from . import signal_handlers # noqa + from . import tasks # noqa super().ready() - from . import signal_handlers - from . import tasks - diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index aa31c886f..4d35c9121 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -9,7 +9,7 @@ import yaml from django.conf import settings from django.utils import timezone from django.utils.translation import gettext as _ -from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError +from sshtunnel import SSHTunnelForwarder from assets.automations.methods import platform_automation_methods from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen @@ -262,22 +262,21 @@ class BasePlaybookManager: info = self.file_to_json(runner.inventory) servers, not_valid = [], [] for k, host in info['all']['hosts'].items(): - jms_asset, jms_gateway = host['jms_asset'], host.get('gateway') + jms_asset, jms_gateway = host.get('jms_asset'), host.get('gateway') if not jms_gateway: continue - - server = SSHTunnelForwarder( - (jms_gateway['address'], jms_gateway['port']), - ssh_username=jms_gateway['username'], - ssh_password=jms_gateway['secret'], - ssh_pkey=jms_gateway['private_key_path'], - remote_bind_address=(jms_asset['address'], jms_asset['port']) - ) try: + server = SSHTunnelForwarder( + (jms_gateway['address'], jms_gateway['port']), + ssh_username=jms_gateway['username'], + ssh_password=jms_gateway['secret'], + ssh_pkey=jms_gateway['private_key_path'], + remote_bind_address=(jms_asset['address'], jms_asset['port']) + ) server.start() - except BaseSSHTunnelForwarderError: + except Exception as e: err_msg = 'Gateway is not active: %s' % jms_asset.get('name', '') - print('\033[31m %s \033[0m\n' % err_msg) + print(f'\033[31m {err_msg} 原因: {e} \033[0m\n') not_valid.append(k) else: host['ansible_host'] = jms_asset['address'] = '127.0.0.1' diff --git a/apps/assets/automations/ping/custom/ssh/main.yml b/apps/assets/automations/ping/custom/ssh/main.yml index 9725f63d7..7820df8e7 100644 --- a/apps/assets/automations/ping/custom/ssh/main.yml +++ b/apps/assets/automations/ping/custom/ssh/main.yml @@ -2,6 +2,7 @@ gather_facts: no vars: ansible_connection: local + ansible_become: false tasks: - name: Test asset connection (paramiko) @@ -12,3 +13,8 @@ login_port: "{{ jms_asset.port }}" login_secret_type: "{{ jms_account.secret_type }}" login_private_key_path: "{{ jms_account.private_key_path }}" + become: "{{ custom_become | default(False) }}" + become_method: "{{ custom_become_method | default('su') }}" + become_user: "{{ custom_become_user | default('') }}" + become_password: "{{ custom_become_password | default('') }}" + become_private_key_path: "{{ custom_become_private_key_path | default(None) }}" diff --git a/apps/assets/automations/ping_gateway/manager.py b/apps/assets/automations/ping_gateway/manager.py index b3f243fdb..c272570d3 100644 --- a/apps/assets/automations/ping_gateway/manager.py +++ b/apps/assets/automations/ping_gateway/manager.py @@ -2,7 +2,7 @@ import socket import paramiko from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import AutomationTypes, Connectivity from assets.models import Gateway diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py index 831b11b66..4a2054db1 100644 --- a/apps/assets/const/automation.py +++ b/apps/assets/const/automation.py @@ -1,5 +1,5 @@ from django.db.models import TextChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class Connectivity(TextChoices): diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index afb92a447..be8f1d1bf 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -33,10 +33,10 @@ class HostTypes(BaseType): def _get_protocol_constrains(cls) -> dict: return { '*': { - 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] + 'choices': ['ssh', 'sftp', 'telnet', 'vnc', 'rdp'] }, cls.WINDOWS: { - 'choices': ['rdp', 'ssh', 'vnc', 'winrm'] + 'choices': ['rdp', 'ssh', 'sftp', 'vnc', 'winrm'] } } diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index aface581c..553201237 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -11,6 +11,7 @@ __all__ = ['Protocol'] class Protocol(ChoicesMixin, models.TextChoices): ssh = 'ssh', 'SSH' + sftp = 'sftp', 'SFTP' rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' vnc = 'vnc', 'VNC' @@ -36,17 +37,16 @@ class Protocol(ChoicesMixin, models.TextChoices): cls.ssh: { 'port': 22, 'secret_types': ['password', 'ssh_key'], + }, + cls.sftp: { + 'port': 22, + 'secret_types': ['password', 'ssh_key'], 'setting': { - 'sftp_enabled': { - 'type': 'bool', - 'default': True, - 'label': _('SFTP enabled') - }, 'sftp_home': { 'type': 'str', 'default': '/tmp', 'label': _('SFTP home') - }, + } } }, cls.rdp: { @@ -81,6 +81,26 @@ class Protocol(ChoicesMixin, models.TextChoices): cls.telnet: { 'port': 23, 'secret_types': ['password'], + 'setting': { + 'username_prompt': { + 'type': 'str', + 'default': 'username:|login:', + 'label': _('Username prompt'), + 'help_text': _('We will send username when we see this prompt') + }, + 'password_prompt': { + 'type': 'str', + 'default': 'password:', + 'label': _('Password prompt'), + 'help_text': _('We will send password when we see this prompt') + }, + 'success_prompt': { + 'type': 'str', + 'default': 'success|成功|#|>|\$', + 'label': _('Success prompt'), + 'help_text': _('We will consider login success when we see this prompt') + } + } }, cls.winrm: { 'port': 5985, @@ -119,7 +139,15 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port': 1521, 'required': True, 'secret_types': ['password'], - 'xpack': True + 'xpack': True, + 'setting': { + 'sysdba': { + 'type': 'bool', + 'default': False, + 'label': _('SYSDBA'), + 'help_text': _('Connect as SYSDBA') + }, + } }, cls.sqlserver: { 'port': 1433, @@ -166,6 +194,15 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port_from_addr': True, 'secret_types': ['password'], 'setting': { + 'safe_mode': { + 'type': 'bool', + 'default': False, + 'label': _('Safe mode'), + 'help_text': _( + 'When safe mode is enabled, some operations will be disabled, such as: ' + 'New tab, right click, visit other website, etc.' + ) + }, 'autofill': { 'label': _('Autofill'), 'type': 'choice', diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index afa0f6d05..8654002b9 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -224,7 +224,7 @@ class AllTypes(ChoicesMixin): return dict(id='ROOT', name=_('All types'), title=_('All types'), open=True, isParent=True) @classmethod - def get_tree_nodes(cls, resource_platforms, include_asset=False): + def get_tree_nodes(cls, resource_platforms, include_asset=False, get_root=True): from ..models import Platform platform_count = defaultdict(int) for platform_id in resource_platforms: @@ -239,10 +239,10 @@ class AllTypes(ChoicesMixin): category_type_mapper[p.category] += platform_count[p.id] tp_platforms[p.category + '_' + p.type].append(p) - nodes = [cls.get_root_nodes()] + nodes = [cls.get_root_nodes()] if get_root else [] for category, type_cls in cls.category_types(): # Category 格式化 - meta = {'type': 'category', 'category': category.value} + meta = {'type': 'category', 'category': category.value, '_type': category.value} category_node = cls.choice_to_node(category, 'ROOT', meta=meta) category_count = category_type_mapper.get(category, 0) category_node['name'] += f'({category_count})' diff --git a/apps/assets/exceptions.py b/apps/assets/exceptions.py index ad22b6339..07e4de0a2 100644 --- a/apps/assets/exceptions.py +++ b/apps/assets/exceptions.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status from common.exceptions import JMSException diff --git a/apps/assets/filters.py b/apps/assets/filters.py index a9cb5a614..17e8414bf 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -1,14 +1,11 @@ # -*- coding: utf-8 -*- # from django.db.models import Q -from django_filters import rest_framework as drf_filters from rest_framework import filters from rest_framework.compat import coreapi, coreschema from assets.utils import get_node_from_request, is_query_node_all_assets -from common.drf.filters import BaseFilterSet - -from .models import Label, Node +from .models import Label class AssetByNodeFilterBackend(filters.BaseFilterBackend): diff --git a/apps/assets/migrations/0121_auto_20230725_1458.py b/apps/assets/migrations/0121_auto_20230725_1458.py new file mode 100644 index 000000000..9e50e4337 --- /dev/null +++ b/apps/assets/migrations/0121_auto_20230725_1458.py @@ -0,0 +1,100 @@ +# Generated by Django 4.1.10 on 2023-07-25 06:58 + +from django.db import migrations +import json + + +def migrate_platforms_sftp_protocol(apps, schema_editor): + platform_protocol_cls = apps.get_model('assets', 'PlatformProtocol') + platform_cls = apps.get_model('assets', 'Platform') + ssh_protocols = platform_protocol_cls.objects \ + .filter(name='ssh', setting__sftp_enabled=True) \ + .exclude(name__in=('Gateway', 'RemoteAppHost')) \ + .filter(platform__type='linux') + platforms_has_sftp = platform_cls.objects.filter(protocols__name='sftp') + + new_protocols = [] + print("\nPlatform add sftp protocol: ") + for protocol in ssh_protocols: + protocol_setting = protocol.setting or {} + if protocol.platform in platforms_has_sftp: + continue + + kwargs = { + 'name': 'sftp', + 'port': protocol.port, + 'primary': False, + 'required': False, + 'default': True, + 'public': True, + 'setting': { + 'sftp_home': protocol_setting.get('sftp_home', '/tmp'), + }, + 'platform': protocol.platform, + } + new_protocol = platform_protocol_cls(**kwargs) + new_protocols.append(new_protocol) + print(" - {}".format(protocol.platform.name)) + + new_protocols_dict = {(protocol.name, protocol.platform): protocol for protocol in new_protocols} + new_protocols = list(new_protocols_dict.values()) + platform_protocol_cls.objects.bulk_create(new_protocols, ignore_conflicts=True) + + +def migrate_assets_sftp_protocol(apps, schema_editor): + asset_cls = apps.get_model('assets', 'Asset') + platform_cls = apps.get_model('assets', 'Platform') + protocol_cls = apps.get_model('assets', 'Protocol') + sftp_platforms = list(platform_cls.objects.filter(protocols__name='sftp').values_list('id')) + + count = 0 + print("\nAsset add sftp protocol: ") + asset_ids = asset_cls.objects\ + .filter(platform__in=sftp_platforms)\ + .exclude(protocols__name='sftp')\ + .distinct()\ + .values_list('id', flat=True) + while True: + _asset_ids = asset_ids[count:count + 1000] + if not _asset_ids: + break + count += 1000 + + new_protocols = [] + ssh_protocols = protocol_cls.objects.filter(name='ssh', asset_id__in=_asset_ids).distinct() + ssh_protocols_map = {protocol.asset_id: protocol for protocol in ssh_protocols} + for asset_id, protocol in ssh_protocols_map.items(): + new_protocols.append(protocol_cls(name='sftp', port=protocol.port, asset_id=asset_id)) + protocol_cls.objects.bulk_create(new_protocols, ignore_conflicts=True) + print(" - Add {}".format(len(new_protocols))) + + +def migrate_telnet_regex(apps, schema_editor): + setting_cls = apps.get_model('settings', 'Setting') + setting = setting_cls.objects.filter(name='TERMINAL_TELNET_REGEX').first() + if not setting: + print("Not found telnet regex setting, skip") + return + try: + value = json.loads(setting.value) + except Exception: + print("Invalid telnet regex setting, skip") + return + platform_protocol_cls = apps.get_model('assets', 'PlatformProtocol') + telnets = platform_protocol_cls.objects.filter(name='telnet') + if telnets.count() > 0: + telnets.update(setting={'success_prompt': value}) + print("Migrate telnet regex setting success: ", telnets.count()) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0120_auto_20230630_1613'), + ] + + operations = [ + migrations.RunPython(migrate_platforms_sftp_protocol), + migrations.RunPython(migrate_assets_sftp_protocol), + migrations.RunPython(migrate_telnet_regex), + ] diff --git a/apps/assets/migrations/0122_auto_20230803_1553.py b/apps/assets/migrations/0122_auto_20230803_1553.py new file mode 100644 index 000000000..4f3071153 --- /dev/null +++ b/apps/assets/migrations/0122_auto_20230803_1553.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.10 on 2023-08-03 07:53 + +from django.db import migrations + + +def migrate_web_setting_safe_mode(apps, schema_editor): + platform_protocol_cls = apps.get_model('assets', 'PlatformProtocol') + protocols = platform_protocol_cls.objects.filter(name='http') + for protocol in protocols: + setting = protocol.setting or {} + setting['safe_mode'] = False + protocol.setting = setting + protocol.save(update_fields=['setting']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0121_auto_20230725_1458'), + ] + + operations = [ + migrations.RunPython(migrate_web_setting_safe_mode), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 7a564f787..6d845f05b 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -8,7 +8,7 @@ from collections import defaultdict from django.db import models from django.db.models import Q from django.forms import model_to_dict -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets import const from common.db.fields import EncryptMixin @@ -221,8 +221,11 @@ class Asset(NodesRelationMixin, AbsConnectivity, JSONFilterMixin, JMSOrgBaseMode return self.address def get_target_ssh_port(self): - protocol = self.protocols.all().filter(name='ssh').first() - return protocol.port if protocol else 22 + return self.get_protocol_port('ssh') + + def get_protocol_port(self, protocol): + protocol = self.protocols.all().filter(name=protocol).first() + return protocol.port if protocol else 0 @property def is_valid(self): diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 69187c828..a424cfcec 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .common import Asset diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index ba9c3ffcb..27e5ed74a 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -2,7 +2,7 @@ import uuid from celery import current_task from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.models.asset import Asset from assets.models.node import Node diff --git a/apps/assets/models/automations/gather_facts.py b/apps/assets/models/automations/gather_facts.py index cf11ea41b..5e179cd83 100644 --- a/apps/assets/models/automations/gather_facts.py +++ b/apps/assets/models/automations/gather_facts.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import AutomationTypes from .base import AssetBaseAutomation diff --git a/apps/assets/models/automations/ping.py b/apps/assets/models/automations/ping.py index a8df4e7c8..98aee079e 100644 --- a/apps/assets/models/automations/ping.py +++ b/apps/assets/models/automations/ping.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import AutomationTypes from .base import AssetBaseAutomation diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 8f5ed713f..c4866c22b 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -3,7 +3,7 @@ from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import Connectivity from common.utils import ( diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index dd0d7b0d1..ec6f1be3e 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -4,7 +4,7 @@ import uuid from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from orgs.mixins.models import OrgModelMixin diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 6dd5e3c4f..587f41d7c 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -3,7 +3,7 @@ import random from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from orgs.mixins.models import JMSOrgBaseModel diff --git a/apps/assets/models/favorite_asset.py b/apps/assets/models/favorite_asset.py index 92d52fe7f..052551402 100644 --- a/apps/assets/models/favorite_asset.py +++ b/apps/assets/models/favorite_asset.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py index 0fc6626df..c474da26d 100644 --- a/apps/assets/models/gateway.py +++ b/apps/assets/models/gateway.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import GATEWAY_NAME from assets.models.platform import Platform diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index 02db7f41f..ad3545a1e 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import uuid from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ __all__ = ['AssetGroup'] diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index 3aff63385..5dbd50361 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -2,7 +2,7 @@ # from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import lazyproperty from orgs.mixins.models import JMSOrgBaseModel diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 94ff9aba5..139295f69 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -10,8 +10,7 @@ from django.core.cache import cache from django.db import models, transaction from django.db.models import Q, Manager from django.db.transaction import atomic -from django.utils.translation import ugettext -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _, gettext from common.db.models import output_as_string from common.utils import get_logger @@ -163,7 +162,7 @@ class FamilyMixin: return key def get_next_child_preset_name(self): - name = ugettext("New node") + name = gettext("New node") values = [ child.value[child.value.rfind(' '):] for child in self.get_children() diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index a5e4da1da..ee3de1043 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -3,7 +3,7 @@ # from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import validate_ssh_private_key diff --git a/apps/assets/notifications.py b/apps/assets/notifications.py index 14e84768e..8f6577b8a 100644 --- a/apps/assets/notifications.py +++ b/apps/assets/notifications.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from notifications.notifications import UserMessage @@ -22,4 +22,4 @@ class BulkUpdatePlatformSkipAssetUserMsg(UserMessage): from assets.models import Asset user = User.objects.first() assets = Asset.objects.all()[:10] - return cls(user, assets) \ No newline at end of file + return cls(user, assets) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 69ccf8e21..b88d22ce7 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -3,7 +3,7 @@ from django.db.models import F from django.db.transaction import atomic -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import Account @@ -157,6 +157,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali def _extract_accounts(self): if not getattr(self, 'initial_data', None): return + if isinstance(self.initial_data, list): + return accounts = self.initial_data.pop('accounts', None) self._accounts = accounts @@ -259,7 +261,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali def is_valid(self, raise_exception=False): self._set_protocols_default() - return super().is_valid(raise_exception) + return super().is_valid(raise_exception=raise_exception) def validate_protocols(self, protocols_data): # 目的是去重 diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py index 386cb87e9..17a122fd6 100644 --- a/apps/assets/serializers/asset/database.py +++ b/apps/assets/serializers/asset/database.py @@ -1,6 +1,6 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework.serializers import ValidationError +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from rest_framework.serializers import ValidationError from assets.models import Database from assets.serializers.gateway import GatewayWithAccountSecretSerializer diff --git a/apps/assets/serializers/automations/base.py b/apps/assets/serializers/automations/base.py index d930fd1dc..f720ed077 100644 --- a/apps/assets/serializers/automations/base.py +++ b/apps/assets/serializers/automations/base.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework import serializers from assets.models import Asset, Node, BaseAutomation, AutomationExecution diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 74eb1117a..a22d1a9ad 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import ObjectRelatedField diff --git a/apps/assets/serializers/gateway.py b/apps/assets/serializers/gateway.py index 259bc13f8..e596ef5a3 100644 --- a/apps/assets/serializers/gateway.py +++ b/apps/assets/serializers/gateway.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from .asset.common import AccountSecretSerializer diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py index 48bc0885f..3d913aeea 100644 --- a/apps/assets/serializers/label.py +++ b/apps/assets/serializers/label.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models import Count -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index fb4c12d67..ac161318b 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- +from django.utils.translation import gettext as _ from rest_framework import serializers -from django.utils.translation import ugettext as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import Asset, Node diff --git a/apps/assets/tasks/ping_gateway.py b/apps/assets/tasks/ping_gateway.py index 789a81c87..05ab7fb0c 100644 --- a/apps/assets/tasks/ping_gateway.py +++ b/apps/assets/tasks/ping_gateway.py @@ -1,6 +1,6 @@ # ~*~ coding: utf-8 ~*~ from celery import shared_task -from django.utils.translation import gettext_noop, ugettext_lazy as _ +from django.utils.translation import gettext_noop, gettext_lazy as _ from assets.const import AutomationTypes from common.utils import get_logger diff --git a/apps/assets/tasks/utils.py b/apps/assets/tasks/utils.py index a60f1b494..d65351eaa 100644 --- a/apps/assets/tasks/utils.py +++ b/apps/assets/tasks/utils.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from common.utils import get_logger - logger = get_logger(__file__) __all__ = [ 'check_asset_can_run_ansible', 'clean_ansible_task_hosts', diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index a281bfe80..dbfd20ecf 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -22,7 +22,12 @@ class KubernetesClient: @property def api(self): configuration = client.Configuration() - configuration.host = self.url + scheme = urlparse(self.url).scheme + if not self.server: + host = self.url + else: + host = f'{scheme}://127.0.0.1:{self.server.local_bind_port}' + configuration.host = host configuration.verify_ssl = False configuration.api_key = {"authorization": "Bearer " + self.token} c = api_client.ApiClient(configuration=configuration) @@ -100,7 +105,7 @@ class KubernetesTree: i = str(self.asset.id) name = str(self.asset) node = self.create_tree_node( - i, i, name, 'asset', is_open=True, + i, i, name, 'asset', icon='k8s', is_open=True, ) return node diff --git a/apps/audits/apps.py b/apps/audits/apps.py index 6e198176b..ddaa535a7 100644 --- a/apps/audits/apps.py +++ b/apps/audits/apps.py @@ -1,7 +1,7 @@ from django.apps import AppConfig from django.conf import settings -from django.utils.translation import ugettext_lazy as _ from django.db.models.signals import post_save +from django.utils.translation import gettext_lazy as _ class AuditsConfig(AppConfig): @@ -9,8 +9,8 @@ class AuditsConfig(AppConfig): verbose_name = _('Audits') def ready(self): - from . import signal_handlers - from . import tasks + from . import signal_handlers # noqa + from . import tasks # noqa if settings.SYSLOG_ENABLE: post_save.connect(signal_handlers.on_audits_log_create) diff --git a/apps/audits/backends/db.py b/apps/audits/backends/db.py index 0c0f46a4a..ac589931d 100644 --- a/apps/audits/backends/db.py +++ b/apps/audits/backends/db.py @@ -1,5 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from audits.models import OperateLog diff --git a/apps/audits/const.py b/apps/audits/const.py index ae8679f01..a2832ef9d 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ from django.db.models import TextChoices, IntegerChoices +from django.utils.translation import gettext_lazy as _ DEFAULT_CITY = _("Unknown") diff --git a/apps/audits/handler.py b/apps/audits/handler.py index 6ac055778..7d9e331cb 100644 --- a/apps/audits/handler.py +++ b/apps/audits/handler.py @@ -3,7 +3,7 @@ from datetime import datetime from django.core.cache import cache from django.db import transaction -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.local import encrypted_field_set from common.utils import get_request_ip, get_logger diff --git a/apps/audits/models.py b/apps/audits/models.py index a0de58d05..42e712dbd 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -1,11 +1,11 @@ import os import uuid +from django.conf import settings from django.db import models from django.db.models import Q -from django.conf import settings from django.utils import timezone -from django.utils.translation import gettext, ugettext_lazy as _ +from django.utils.translation import gettext, gettext_lazy as _ from common.db.encoder import ModelJSONFieldEncoder from common.utils import lazyproperty diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 7b7573232..a90651bc9 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from orgs.mixins.serializers import BulkOrgResourceModelSerializer + from audits.backends.db import OperateLogStore from common.serializers.fields import LabeledChoiceField from common.utils import reverse, i18n_trans from common.utils.timezone import as_current_tz from ops.serializers.job import JobExecutionSerializer +from orgs.mixins.serializers import BulkOrgResourceModelSerializer from terminal.models import Session from . import models from .const import ( diff --git a/apps/audits/signal_handlers/login_log.py b/apps/audits/signal_handlers/login_log.py index a73482b20..f74d55e8e 100644 --- a/apps/audits/signal_handlers/login_log.py +++ b/apps/audits/signal_handlers/login_log.py @@ -5,7 +5,7 @@ from django.contrib.auth import BACKEND_SESSION_KEY from django.dispatch import receiver from django.utils import timezone, translation from django.utils.functional import LazyObject -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.request import Request from authentication.signals import post_auth_failed, post_auth_success diff --git a/apps/audits/signal_handlers/operate_log.py b/apps/audits/signal_handlers/operate_log.py index 302e89190..826c11c8b 100644 --- a/apps/audits/signal_handlers/operate_log.py +++ b/apps/audits/signal_handlers/operate_log.py @@ -14,6 +14,7 @@ from audits.handler import ( from audits.utils import model_to_dict_for_operate_log as model_to_dict from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, SKIP_SIGNAL from common.signals import django_ready +from jumpserver.utils import current_request from ..const import MODELS_NEED_RECORD, ActionChoices M2M_ACTION = { @@ -73,6 +74,10 @@ def signal_of_operate_log_whether_continue( condition = False if instance and getattr(instance, SKIP_SIGNAL, False): condition = False + # 不记录组件的操作日志 + user = current_request.user if current_request else None + if not user or getattr(user, 'is_service_account', False): + condition = False # 终端模型的 create 事件由系统产生,不记录 if instance._meta.object_name == 'Terminal' and created: condition = False diff --git a/apps/authentication/api/access_key.py b/apps/authentication/api/access_key.py index bbda04c02..9253f449d 100644 --- a/apps/authentication/api/access_key.py +++ b/apps/authentication/api/access_key.py @@ -2,12 +2,13 @@ # from rest_framework.viewsets import ModelViewSet -from .. import serializers + from rbac.permissions import RBACPermission +from ..serializers import AccessKeySerializer class AccessKeyViewSet(ModelViewSet): - serializer_class = serializers.AccessKeySerializer + serializer_class = AccessKeySerializer search_fields = ['^id', '^secret'] permission_classes = [RBACPermission] diff --git a/apps/authentication/api/confirm.py b/apps/authentication/api/confirm.py index d3a32a7c8..0923875a0 100644 --- a/apps/authentication/api/confirm.py +++ b/apps/authentication/api/confirm.py @@ -2,10 +2,10 @@ # import time -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from rest_framework import status from rest_framework.generics import RetrieveAPIView, CreateAPIView from rest_framework.response import Response -from rest_framework import status from common.permissions import IsValidUser, UserConfirmation from ..const import ConfirmType diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index a98855f85..e424fe7e3 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -7,7 +7,7 @@ from django.conf import settings from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status, serializers from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied, ValidationError @@ -64,6 +64,15 @@ class RDPFileClientProtocolURLMixin: 'use redirection server name:i': '0', 'smart sizing:i': '1', } + # 设置多屏显示 + multi_mon = is_true(self.request.query_params.get('multi_mon')) + if multi_mon: + rdp_options['use multimon:i'] = '1' + + # 设置多屏显示 + multi_mon = is_true(self.request.query_params.get('multi_mon')) + if multi_mon: + rdp_options['use multimon:i'] = '1' # 设置磁盘挂载 drives_redirect = is_true(self.request.query_params.get('drives_redirect')) @@ -117,12 +126,16 @@ class RDPFileClientProtocolURLMixin: return filename, content @staticmethod - def get_connect_filename(prefix_name): - prefix_name = prefix_name.replace('/', '_') - prefix_name = prefix_name.replace('\\', '_') - prefix_name = prefix_name.replace('.', '_') + def escape_name(name): + name = name.replace('/', '_') + name = name.replace('\\', '_') + name = name.replace('.', '_') + name = urllib.parse.quote(name) + return name + + def get_connect_filename(self, prefix_name): filename = f'{prefix_name}-jumpserver' - filename = urllib.parse.quote(filename) + filename = self.escape_name(filename) return filename @staticmethod @@ -136,15 +149,25 @@ class RDPFileClientProtocolURLMixin: connect_method_dict = ConnectMethodUtil.get_connect_method( token.connect_method, token.protocol, _os ) + asset = token.asset if connect_method_dict is None: raise ValueError('Connect method not support: {}'.format(connect_method_name)) + account = token.account or token.input_username + datetime = timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H:%M:%S') + name = account + '@' + str(asset) + '[' + datetime + ']' data = { - 'id': str(token.id), - 'value': token.value, + 'version': 2, + 'id': str(token.id), # 兼容老的,未来几个版本删掉 + 'value': token.value, # 兼容老的,未来几个版本删掉 + 'name': self.escape_name(name), 'protocol': token.protocol, - 'command': '', - 'file': {} + 'token': { + 'id': str(token.id), + 'value': token.value, + }, + 'file': {}, + 'command': '' } if connect_method_name == NativeClient.mstsc or connect_method_dict['type'] == 'applet': @@ -159,10 +182,24 @@ class RDPFileClientProtocolURLMixin: else: endpoint = self.get_smart_endpoint( protocol=connect_method_dict['endpoint_protocol'], - asset=token.asset + asset=asset ) - cmd = NativeClient.get_launch_command(connect_method_name, token, endpoint) - data.update({'command': cmd}) + data.update({ + 'asset': { + 'id': str(asset.id), + 'category': asset.category, + 'type': asset.type, + 'name': asset.name, + 'address': asset.address, + 'info': { + **asset.spec_info, + } + }, + 'endpoint': { + 'host': endpoint.host, + 'port': endpoint.get_port(token.asset, token.protocol), + } + }) return data def get_smart_endpoint(self, protocol, asset=None): @@ -306,9 +343,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView if account.username != AliasAccount.INPUT: data['input_username'] = '' - elif account.username == AliasAccount.USER: - data['input_username'] = user.username - ticket = self._validate_acl(user, asset, account) if ticket: data['from_ticket'] = ticket diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index fe81149e3..3436c2eeb 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -1,20 +1,18 @@ # -*- coding: utf-8 -*- # -import time -from django.utils.translation import ugettext as _ -from django.conf import settings from django.shortcuts import get_object_or_404 -from rest_framework.permissions import AllowAny +from django.utils.translation import gettext as _ from rest_framework.generics import CreateAPIView -from rest_framework.serializers import ValidationError +from rest_framework.permissions import AllowAny from rest_framework.response import Response +from rest_framework.serializers import ValidationError -from common.utils import get_logger from common.exceptions import UnexpectError +from common.utils import get_logger from users.models.user import User -from .. import serializers from .. import errors +from .. import serializers from ..mixins import AuthMixin logger = get_logger(__name__) diff --git a/apps/authentication/api/password.py b/apps/authentication/api/password.py index 53f47c03c..86801bc6c 100644 --- a/apps/authentication/api/password.py +++ b/apps/authentication/api/password.py @@ -1,24 +1,24 @@ -from django.http import HttpResponseRedirect -from rest_framework.generics import CreateAPIView -from rest_framework.response import Response -from rest_framework.permissions import AllowAny -from django.utils.translation import ugettext as _ -from django.template.loader import render_to_string from django.core.cache import cache +from django.http import HttpResponseRedirect from django.shortcuts import reverse +from django.template.loader import render_to_string +from django.utils.translation import gettext as _ +from rest_framework.generics import CreateAPIView +from rest_framework.permissions import AllowAny +from rest_framework.response import Response -from common.utils.verify_code import SendAndVerifyCodeUtil -from common.permissions import IsValidUser -from common.utils.random import random_string -from common.utils import get_object_or_none +from authentication.errors import PasswordInvalid +from authentication.mixins import AuthMixin +from authentication.mixins import authenticate from authentication.serializers import ( PasswordVerifySerializer, ResetPasswordCodeSerializer ) +from common.permissions import IsValidUser +from common.utils import get_object_or_none +from common.utils.random import random_string +from common.utils.verify_code import SendAndVerifyCodeUtil from settings.utils import get_login_title from users.models import User -from authentication.mixins import authenticate -from authentication.errors import PasswordInvalid -from authentication.mixins import AuthMixin class UserResetPasswordSendCodeApi(CreateAPIView): @@ -52,7 +52,11 @@ class UserResetPasswordSendCodeApi(CreateAPIView): other_args = {} target = serializer.validated_data[form_type] - query_key = 'phone' if form_type == 'sms' else form_type + if form_type == 'sms': + query_key = 'phone' + target = target.lstrip('+') + else: + query_key = form_type user, err = self.is_valid_user(username=username, **{query_key: target}) if not user: return Response({'error': err}, status=400) diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py index 615bb255e..5a1c1966a 100644 --- a/apps/authentication/apps.py +++ b/apps/authentication/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class AuthenticationConfig(AppConfig): @@ -7,9 +7,7 @@ class AuthenticationConfig(AppConfig): verbose_name = _('Authentication') def ready(self): - from . import signal_handlers - from . import notifications - from . import tasks - + from . import signal_handlers # noqa + from . import tasks # noqa + from . import notifications # noqa super().ready() - diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py index d05e097d5..3f2b05940 100644 --- a/apps/authentication/backends/custom.py +++ b/apps/authentication/backends/custom.py @@ -1,10 +1,10 @@ from django.conf import settings -from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ -from common.utils import get_logger from django.contrib.auth import get_user_model -from authentication.signals import user_auth_failed, user_auth_success +from django.utils.module_loading import import_string +from django.utils.translation import gettext_lazy as _ +from authentication.signals import user_auth_failed, user_auth_success +from common.utils import get_logger from .base import JMSModelBackend logger = get_logger(__file__) diff --git a/apps/authentication/backends/drf.py b/apps/authentication/backends/drf.py index 10c191b1b..8d7d60e99 100644 --- a/apps/authentication/backends/drf.py +++ b/apps/authentication/backends/drf.py @@ -1,22 +1,20 @@ # -*- coding: utf-8 -*- # -import uuid import time +import uuid -from django.core.cache import cache -from django.utils.translation import ugettext as _ -from six import text_type from django.contrib.auth import get_user_model - +from django.core.cache import cache +from django.utils.translation import gettext as _ from rest_framework import HTTP_HEADER_ENCODING from rest_framework import authentication, exceptions +from six import text_type + from common.auth import signature - from common.utils import get_object_or_none, make_signature, http_to_unixtime +from .base import JMSBaseAuthBackend from ..models import AccessKey, PrivateToken -from .base import JMSBaseAuthBackend, JMSModelBackend - UserModel = get_user_model() @@ -200,4 +198,3 @@ class SignatureAuthentication(signature.SignatureAuthentication): return user, secret except (AccessKey.DoesNotExist, exceptions.ValidationError): return None, None - diff --git a/apps/authentication/backends/oauth2/signals.py b/apps/authentication/backends/oauth2/signals.py index b82ae90d7..4f192a9ad 100644 --- a/apps/authentication/backends/oauth2/signals.py +++ b/apps/authentication/backends/oauth2/signals.py @@ -1,7 +1,3 @@ from django.dispatch import Signal - -oauth2_create_or_update_user = Signal( - providing_args=['request', 'user', 'created', 'name', 'username', 'email'] -) - +oauth2_create_or_update_user = Signal() diff --git a/apps/authentication/backends/oidc/middleware.py b/apps/authentication/backends/oidc/middleware.py index c2ad33637..4481951e4 100644 --- a/apps/authentication/backends/oidc/middleware.py +++ b/apps/authentication/backends/oidc/middleware.py @@ -1,15 +1,14 @@ import time + import requests import requests.exceptions - -from django.core.exceptions import MiddlewareNotUsed from django.conf import settings from django.contrib import auth +from django.core.exceptions import MiddlewareNotUsed + from common.utils import get_logger - -from .utils import validate_and_return_id_token from .decorator import ssl_verification - +from .utils import validate_and_return_id_token logger = get_logger(__file__) @@ -25,11 +24,16 @@ class OIDCRefreshIDTokenMiddleware: def __call__(self, request): # Refreshes tokens only in the applicable cases. - if request.method == 'GET' and not request.is_ajax() and request.user.is_authenticated and settings.AUTH_OPENID: + if request.method == 'GET' and not self.is_ajax(request) and \ + request.user.is_authenticated and settings.AUTH_OPENID: self.refresh_token(request) response = self.get_response(request) return response + @staticmethod + def is_ajax(request): + return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' + @ssl_verification def refresh_token(self, request): """ Refreshes the token of the current user. """ diff --git a/apps/authentication/backends/oidc/signals.py b/apps/authentication/backends/oidc/signals.py index 2f4c071e4..5346dba03 100644 --- a/apps/authentication/backends/oidc/signals.py +++ b/apps/authentication/backends/oidc/signals.py @@ -9,8 +9,4 @@ from django.dispatch import Signal - -openid_create_or_update_user = Signal( - providing_args=['request', 'user', 'created', 'name', 'username', 'email'] -) - +openid_create_or_update_user = Signal() diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py index 88088245d..c638aeef6 100644 --- a/apps/authentication/backends/oidc/views.py +++ b/apps/authentication/backends/oidc/views.py @@ -11,8 +11,8 @@ import base64 import hashlib -import time import secrets +import time from django.conf import settings from django.contrib import auth @@ -20,13 +20,12 @@ from django.core.exceptions import SuspiciousOperation from django.http import HttpResponseRedirect, QueryDict from django.urls import reverse from django.utils.crypto import get_random_string -from django.utils.http import is_safe_url, urlencode +from django.utils.http import url_has_allowed_host_and_scheme, urlencode from django.views.generic import View from authentication.utils import build_absolute_uri_for_oidc from .utils import get_logger - logger = get_logger(__file__) @@ -102,7 +101,7 @@ class OIDCAuthRequestView(View): logger.debug(log_prompt.format('Stores next url in the session')) next_url = request.GET.get('next') request.session['oidc_auth_next_url'] = next_url \ - if is_safe_url(url=next_url, allowed_hosts=(request.get_host(), )) else None + if url_has_allowed_host_and_scheme(url=next_url, allowed_hosts=(request.get_host(),)) else None # Redirects the user to authorization endpoint. logger.debug(log_prompt.format('Construct redirect url')) @@ -145,15 +144,15 @@ class OIDCAuthCallbackView(View): # missing or if no state can be retrieved from the current session. if ( - ((nonce and settings.AUTH_OPENID_USE_NONCE) or not settings.AUTH_OPENID_USE_NONCE) - and - ( - (state and settings.AUTH_OPENID_USE_STATE and 'state' in callback_params) - or - (not settings.AUTH_OPENID_USE_STATE) - ) - and - ('code' in callback_params) + ((nonce and settings.AUTH_OPENID_USE_NONCE) or not settings.AUTH_OPENID_USE_NONCE) + and + ( + (state and settings.AUTH_OPENID_USE_STATE and 'state' in callback_params) + or + (not settings.AUTH_OPENID_USE_STATE) + ) + and + ('code' in callback_params) ): # Ensures that the passed state values is the same as the one that was previously # generated when forging the authorization request. This is necessary to mitigate diff --git a/apps/authentication/backends/saml2/signals.py b/apps/authentication/backends/saml2/signals.py index 3dcdd9d35..72feddbbe 100644 --- a/apps/authentication/backends/saml2/signals.py +++ b/apps/authentication/backends/saml2/signals.py @@ -1,4 +1,3 @@ from django.dispatch import Signal - -saml2_create_or_update_user = Signal(providing_args=('user', 'created', 'request', 'attrs')) +saml2_create_or_update_user = Signal() diff --git a/apps/authentication/confirm/password.py b/apps/authentication/confirm/password.py index 944ab8f24..dc49576a3 100644 --- a/apps/authentication/confirm/password.py +++ b/apps/authentication/confirm/password.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from authentication.mixins import authenticate from .base import BaseConfirm diff --git a/apps/authentication/confirm/relogin.py b/apps/authentication/confirm/relogin.py index 447a17ab0..2b27ef5fe 100644 --- a/apps/authentication/confirm/relogin.py +++ b/apps/authentication/confirm/relogin.py @@ -1,7 +1,7 @@ from datetime import datetime from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .base import BaseConfirm diff --git a/apps/authentication/errors/failed.py b/apps/authentication/errors/failed.py index 3e0848524..f6d8004c6 100644 --- a/apps/authentication/errors/failed.py +++ b/apps/authentication/errors/failed.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils.translation import gettext_lazy as _ from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil -from ..signals import post_auth_failed from . import const +from ..signals import post_auth_failed class AuthFailedNeedLogMixin: diff --git a/apps/authentication/errors/mfa.py b/apps/authentication/errors/mfa.py index 288c2060e..40b97df92 100644 --- a/apps/authentication/errors/mfa.py +++ b/apps/authentication/errors/mfa.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.exceptions import JMSException diff --git a/apps/authentication/errors/redirect.py b/apps/authentication/errors/redirect.py index 466ec708d..5e7c5412d 100644 --- a/apps/authentication/errors/redirect.py +++ b/apps/authentication/errors/redirect.py @@ -1,5 +1,5 @@ -from django.utils.translation import ugettext_lazy as _ from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from common.exceptions import JMSException from . import const diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index 747b821ea..90429bc82 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # +from captcha.fields import CaptchaField, CaptchaTextInput from django import forms from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from captcha.fields import CaptchaField, CaptchaTextInput +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger, decrypt_password diff --git a/apps/authentication/mfa/base.py b/apps/authentication/mfa/base.py index 2158b8fe1..8bcaaf3a0 100644 --- a/apps/authentication/mfa/base.py +++ b/apps/authentication/mfa/base.py @@ -1,6 +1,6 @@ import abc -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class BaseMFA(abc.ABC): @@ -69,4 +69,3 @@ class BaseMFA(abc.ABC): @staticmethod def help_text_of_disable(): return '' - diff --git a/apps/authentication/mfa/custom.py b/apps/authentication/mfa/custom.py index 3b671f709..0819dcfaa 100644 --- a/apps/authentication/mfa/custom.py +++ b/apps/authentication/mfa/custom.py @@ -1,6 +1,6 @@ from django.conf import settings from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from .base import BaseMFA diff --git a/apps/authentication/mfa/radius.py b/apps/authentication/mfa/radius.py index 9f1250234..2055e9776 100644 --- a/apps/authentication/mfa/radius.py +++ b/apps/authentication/mfa/radius.py @@ -1,5 +1,5 @@ -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils.translation import gettext_lazy as _ from .base import BaseMFA from ..backends.radius import RadiusBackend diff --git a/apps/authentication/mfa/sms.py b/apps/authentication/mfa/sms.py index aa426bf57..d114b648e 100644 --- a/apps/authentication/mfa/sms.py +++ b/apps/authentication/mfa/sms.py @@ -1,8 +1,8 @@ -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils.translation import gettext_lazy as _ -from .base import BaseMFA from common.utils.verify_code import SendAndVerifyCodeUtil +from .base import BaseMFA sms_failed_msg = _("SMS verify code invalid") diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 43f7bdc14..b4ee86ed4 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -5,7 +5,7 @@ from django.contrib.auth import logout as auth_logout from django.http import HttpResponse from django.shortcuts import redirect, reverse, render from django.utils.deprecation import MiddlewareMixin -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from apps.authentication import mixins from authentication.signals import post_auth_failed diff --git a/apps/authentication/migrations/0007_connectiontoken.py b/apps/authentication/migrations/0007_connectiontoken.py index 86341ff5b..6c0b437da 100644 --- a/apps/authentication/migrations/0007_connectiontoken.py +++ b/apps/authentication/migrations/0007_connectiontoken.py @@ -1,10 +1,10 @@ # Generated by Django 3.1.12 on 2022-02-11 06:01 +import uuid from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('authentication', '0006_auto_20211227_1059'), ] @@ -13,7 +13,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ConnectionToken', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), diff --git a/apps/authentication/migrations/0011_auto_20220705_1940.py b/apps/authentication/migrations/0011_auto_20220705_1940.py index 527003d9e..f965c9486 100644 --- a/apps/authentication/migrations/0011_auto_20220705_1940.py +++ b/apps/authentication/migrations/0011_auto_20220705_1940.py @@ -1,14 +1,13 @@ # Generated by Django 3.2.12 on 2022-07-05 11:40 -import authentication.models +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion -import uuid + +import authentication.models class Migration(migrations.Migration): - dependencies = [ ('applications', '0021_auto_20220629_1826'), ('assets', '0091_auto_20220629_1826'), @@ -20,7 +19,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='connectiontoken', name='application', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='applications.application', verbose_name='Application'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='connection_tokens', to='applications.application', + verbose_name='Application'), ), migrations.AddField( model_name='connectiontoken', @@ -30,7 +31,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='connectiontoken', name='asset', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.asset', verbose_name='Asset'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='connection_tokens', to='assets.asset', verbose_name='Asset'), ), migrations.AddField( model_name='connectiontoken', @@ -55,7 +57,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='connectiontoken', name='system_user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.systemuser', verbose_name='System user'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='connection_tokens', to='assets.systemuser', + verbose_name='System user'), ), migrations.AddField( model_name='connectiontoken', @@ -65,25 +69,24 @@ class Migration(migrations.Migration): migrations.AddField( model_name='connectiontoken', name='type', - field=models.CharField(choices=[('asset', 'Asset'), ('application', 'Application')], default='asset', max_length=16, verbose_name='Type'), + field=models.CharField(choices=[('asset', 'Asset'), ('application', 'Application')], default='asset', + max_length=16, verbose_name='Type'), ), migrations.AddField( model_name='connectiontoken', name='user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to=settings.AUTH_USER_MODEL, verbose_name='User'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='connection_tokens', to=settings.AUTH_USER_MODEL, verbose_name='User'), ), migrations.AddField( model_name='connectiontoken', name='user_display', field=models.CharField(default='', max_length=128, verbose_name='User display'), ), - migrations.AlterField( - model_name='connectiontoken', - name='id', - field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False), - ), migrations.AlterModelOptions( name='connectiontoken', - options={'ordering': ('-date_expired',), 'permissions': [('view_connectiontokensecret', 'Can view connection token secret')], 'verbose_name': 'Connection token'}, + options={'ordering': ('-date_expired',), + 'permissions': [('view_connectiontokensecret', 'Can view connection token secret')], + 'verbose_name': 'Connection token'}, ), ] diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index e859fe4de..f7d3856b0 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -15,7 +15,7 @@ from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured from django.shortcuts import reverse, redirect, get_object_or_404 from django.utils.http import urlencode -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework.request import Request from acls.models import LoginACL @@ -460,6 +460,7 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost self._check_password_require_reset_or_not(user) self._check_passwd_is_too_simple(user, password) self._check_passwd_need_update(user) + user.cache_login_password_if_need(password) # 校验login-mfa, 如果登录页面上显示 mfa 的话 self._check_login_page_mfa_if_need(user) diff --git a/apps/authentication/models/access_key.py b/apps/authentication/models/access_key.py index d0e6fb0ef..77fa67c74 100644 --- a/apps/authentication/models/access_key.py +++ b/apps/authentication/models/access_key.py @@ -2,7 +2,7 @@ import uuid from django.conf import settings from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ import common.db.models diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index ce5b4829f..6ea5474c4 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -6,10 +6,10 @@ from django.conf import settings from django.core.cache import cache from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import PermissionDenied -from accounts.const import AliasAccount +from accounts.models import VirtualAccount from assets.const import Protocol from assets.const.host import GATEWAY_NAME from common.db.fields import EncryptTextField @@ -191,7 +191,7 @@ class ConnectionToken(JMSOrgBaseModel): raise JMSException({'error': 'No host account available'}) host, account, lock_key, ttl = bulk_get(host_account, ('host', 'account', 'lock_key', 'ttl')) - gateway = host.gateway.select_gateway() if host.domain else None + gateway = host.domain.select_gateway() if host.domain else None data = { 'id': account.id, @@ -216,18 +216,14 @@ class ConnectionToken(JMSOrgBaseModel): @lazyproperty def account_object(self): - from accounts.models import Account if not self.asset: return None if self.account.startswith('@'): - account = Account.get_special_account(self.account) - account.asset = self.asset - account.org_id = self.asset.org_id - - if self.account in [AliasAccount.INPUT, AliasAccount.USER]: - account.username = self.input_username - account.secret = self.input_secret + account = VirtualAccount.get_special_account( + self.account, self.user, self.asset, input_username=self.input_username, + input_secret=self.input_secret, from_permed=False + ) else: account = self.asset.accounts.filter(name=self.account).first() if not account.secret and self.input_secret: diff --git a/apps/authentication/models/private_token.py b/apps/authentication/models/private_token.py index 8d83d1e0a..bb5f1da87 100644 --- a/apps/authentication/models/private_token.py +++ b/apps/authentication/models/private_token.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.authtoken.models import Token diff --git a/apps/authentication/models/sso_token.py b/apps/authentication/models/sso_token.py index c8ec1e9eb..61002a1a8 100644 --- a/apps/authentication/models/sso_token.py +++ b/apps/authentication/models/sso_token.py @@ -1,7 +1,7 @@ import uuid from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import BaseCreateUpdateModel, CASCADE_SIGNAL_SKIP diff --git a/apps/authentication/models/temp_token.py b/apps/authentication/models/temp_token.py index d76a30a42..33d86cbc1 100644 --- a/apps/authentication/models/temp_token.py +++ b/apps/authentication/models/temp_token.py @@ -1,7 +1,7 @@ -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ - from django.db import models +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + from common.db.models import JMSBaseModel diff --git a/apps/authentication/notifications.py b/apps/authentication/notifications.py index 364ac20dd..e52f29a94 100644 --- a/apps/authentication/notifications.py +++ b/apps/authentication/notifications.py @@ -1,9 +1,9 @@ -from django.utils.translation import ugettext as _ from django.template.loader import render_to_string +from django.utils.translation import gettext as _ -from notifications.notifications import UserMessage from common.utils import get_logger from common.utils.timezone import local_now_display +from notifications.notifications import UserMessage logger = get_logger(__file__) diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index a3d56b90c..33fbe842e 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.const import SecretType diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index bc3d051e1..4579e73ee 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers import CommonModelSerializer diff --git a/apps/authentication/serializers/password_mfa.py b/apps/authentication/serializers/password_mfa.py index d83260748..3e733b6ec 100644 --- a/apps/authentication/serializers/password_mfa.py +++ b/apps/authentication/serializers/password_mfa.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/authentication/signals.py b/apps/authentication/signals.py index 556bba614..7c8b8e229 100644 --- a/apps/authentication/signals.py +++ b/apps/authentication/signals.py @@ -1,9 +1,7 @@ from django.dispatch import Signal +post_auth_success = Signal() +post_auth_failed = Signal() -post_auth_success = Signal(providing_args=('user', 'request')) -post_auth_failed = Signal(providing_args=('username', 'request', 'reason')) - - -user_auth_success = Signal(providing_args=('user', 'request', 'backend', 'create')) -user_auth_failed = Signal(providing_args=('username', 'request', 'reason', 'backend')) +user_auth_success = Signal() +user_auth_failed = Signal() diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index a0db9061c..b53afa423 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -4,12 +4,12 @@ import ipaddress from urllib.parse import urljoin, urlparse from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from common.utils import validate_ip, get_ip_city, get_request_ip -from common.utils import get_logger -from audits.models import UserLoginLog from audits.const import DEFAULT_CITY +from audits.models import UserLoginLog +from common.utils import get_logger +from common.utils import validate_ip, get_ip_city, get_request_ip from .notifications import DifferentCityLoginMessage logger = get_logger(__file__) diff --git a/apps/authentication/views/base.py b/apps/authentication/views/base.py index e98e41e70..7969502c0 100644 --- a/apps/authentication/views/base.py +++ b/apps/authentication/views/base.py @@ -1,26 +1,23 @@ from functools import lru_cache -from rest_framework.request import Request -from django.utils.translation import ugettext_lazy as _ -from django.utils.module_loading import import_string from django.conf import settings from django.db.utils import IntegrityError +from django.utils.module_loading import import_string +from django.utils.translation import gettext_lazy as _ from django.views import View +from rest_framework.request import Request from authentication import errors from authentication.mixins import AuthMixin -from users.models import User -from common.utils.django import reverse, get_object_or_none from common.utils import get_logger - +from common.utils.django import reverse, get_object_or_none +from users.models import User from .mixins import FlashMessageMixin - logger = get_logger(__file__) class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, View): - client_type_path = '' client_auth_params = {} user_type = '' diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 77cec63fd..819f1d055 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -4,7 +4,7 @@ from django.conf import settings from django.db.utils import IntegrityError from django.http.request import HttpRequest from django.http.response import HttpResponseRedirect -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.exceptions import APIException from rest_framework.permissions import AllowAny, IsAuthenticated @@ -13,16 +13,15 @@ from authentication import errors from authentication.const import ConfirmType from authentication.mixins import AuthMixin from authentication.notifications import OAuthBindMessage -from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin from common.permissions import UserConfirmation from common.sdk.im.dingtalk import URL, DingTalk from common.utils import get_logger from common.utils.common import get_request_ip from common.utils.django import get_object_or_none, reverse from common.utils.random import random_string +from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin from users.models import User from users.views import UserVerifyPasswordView - from .base import BaseLoginCallbackView from .mixins import METAMixin, FlashMessageMixin diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index 62660764a..0f62ef75d 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -4,22 +4,21 @@ from django.conf import settings from django.db.utils import IntegrityError from django.http.request import HttpRequest from django.http.response import HttpResponseRedirect -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.exceptions import APIException from rest_framework.permissions import AllowAny, IsAuthenticated from authentication.const import ConfirmType from authentication.notifications import OAuthBindMessage -from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin from common.permissions import UserConfirmation from common.sdk.im.feishu import URL, FeiShu from common.utils import get_logger from common.utils.common import get_request_ip from common.utils.django import reverse from common.utils.random import random_string +from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin from users.views import UserVerifyPasswordView - from .base import BaseLoginCallbackView from .mixins import FlashMessageMixin @@ -166,4 +165,3 @@ class FeiShuQRLoginCallbackView(FeiShuQRMixin, BaseLoginCallbackView): msg_client_err = _('FeiShu Error') msg_user_not_bound_err = _('FeiShu is not bound') msg_not_found_user_from_client_err = _('Failed to get user from FeiShu') - diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 8b5696dc9..459abb3cf 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -16,7 +16,7 @@ from django.shortcuts import reverse, redirect from django.templatetags.static import static from django.urls import reverse_lazy from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _, get_language +from django.utils.translation import gettext as _, get_language from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index e127dbeee..238d0b3e6 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -1,28 +1,28 @@ -from django.http.response import HttpResponseRedirect -from django.utils.translation import ugettext_lazy as _ from urllib.parse import urlencode -from django.views import View -from django.conf import settings -from django.http.request import HttpRequest -from django.db.utils import IntegrityError -from rest_framework.permissions import IsAuthenticated, AllowAny -from rest_framework.exceptions import APIException -from users.models import User -from users.views import UserVerifyPasswordView -from common.utils import get_logger -from common.utils.random import random_string -from common.utils.django import reverse, get_object_or_none +from django.conf import settings +from django.db.utils import IntegrityError +from django.http.request import HttpRequest +from django.http.response import HttpResponseRedirect +from django.utils.translation import gettext_lazy as _ +from django.views import View +from rest_framework.exceptions import APIException +from rest_framework.permissions import IsAuthenticated, AllowAny + +from authentication import errors +from authentication.const import ConfirmType +from authentication.mixins import AuthMixin +from authentication.notifications import OAuthBindMessage +from common.permissions import UserConfirmation from common.sdk.im.wecom import URL from common.sdk.im.wecom import WeCom -from common.views.mixins import UserConfirmRequiredExceptionMixin, PermissionsMixin +from common.utils import get_logger from common.utils.common import get_request_ip -from common.permissions import UserConfirmation -from authentication import errors -from authentication.mixins import AuthMixin -from authentication.const import ConfirmType -from authentication.notifications import OAuthBindMessage - +from common.utils.django import reverse, get_object_or_none +from common.utils.random import random_string +from common.views.mixins import UserConfirmRequiredExceptionMixin, PermissionsMixin +from users.models import User +from users.views import UserVerifyPasswordView from .base import BaseLoginCallbackView from .mixins import METAMixin, FlashMessageMixin diff --git a/apps/common/api/action.py b/apps/common/api/action.py index 8d7829d9e..738ad3db4 100644 --- a/apps/common/api/action.py +++ b/apps/common/api/action.py @@ -2,7 +2,7 @@ # from typing import Callable -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response diff --git a/apps/common/apps.py b/apps/common/apps.py index 89e735a09..5fb85992a 100644 --- a/apps/common/apps.py +++ b/apps/common/apps.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import sys + from django.apps import AppConfig @@ -8,8 +9,8 @@ class CommonConfig(AppConfig): name = 'common' def ready(self): - from . import signal_handlers - from . import tasks + from . import signal_handlers # noqa + from . import tasks # noqa from .signals import django_ready excludes = ['migrate', 'compilemessages', 'makemigrations'] for i in excludes: diff --git a/apps/common/const/choices.py b/apps/common/const/choices.py index 9b6c817f3..6db9254b3 100644 --- a/apps/common/const/choices.py +++ b/apps/common/const/choices.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ ADMIN = 'Admin' USER = 'User' diff --git a/apps/common/const/common.py b/apps/common/const/common.py index 33a889ecb..ab149fef5 100644 --- a/apps/common/const/common.py +++ b/apps/common/const/common.py @@ -1,6 +1,6 @@ import re -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ create_success_msg = _("%(name)s was created successfully") update_success_msg = _("%(name)s was updated successfully") diff --git a/apps/common/db/encoder.py b/apps/common/db/encoder.py index d773491a1..2bc9332f3 100644 --- a/apps/common/db/encoder.py +++ b/apps/common/db/encoder.py @@ -6,9 +6,9 @@ from datetime import datetime from django.conf import settings from django.db import models from django.utils import timezone as dj_timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -lazy_type = type(_('ugettext_lazy')) +lazy_type = type(_('gettext_lazy')) class ModelJSONFieldEncoder(json.JSONEncoder): diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index dee051da5..ad969d52e 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -11,12 +11,12 @@ from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from django.db.models import Q, Manager, QuerySet -from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.utils.encoders import JSONEncoder from common.local import add_encrypted_field_set -from common.utils import signer, crypto, contains_ip +from common.utils import contains_ip +from .utils import Encryptor from .validators import PortRangeValidator __all__ = [ @@ -139,20 +139,11 @@ class EncryptMixin: EncryptMixin要放在最前面 """ - def decrypt_from_signer(self, value): - return signer.unsign(value) or "" - def from_db_value(self, value, expression, connection, context=None): if value is None: return value - value = force_text(value) - plain_value = crypto.decrypt(value) - - # 如果没有解开,使用原来的signer解密 - if not plain_value: - plain_value = self.decrypt_from_signer(value) - + plain_value = Encryptor(value).decrypt() # 可能和Json mix,所以要先解密,再json sp = super() if hasattr(sp, "from_db_value"): @@ -167,9 +158,9 @@ class EncryptMixin: sp = super() if hasattr(sp, "get_prep_value"): value = sp.get_prep_value(value) - value = force_text(value) + # 替换新的加密方式 - return crypto.encrypt(value) + return Encryptor(value).encrypt() class EncryptTextField(EncryptMixin, models.TextField): diff --git a/apps/common/db/mixins.py b/apps/common/db/mixins.py index 43c5f8a65..0d4e1c9bc 100644 --- a/apps/common/db/mixins.py +++ b/apps/common/db/mixins.py @@ -3,7 +3,7 @@ from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ __all__ = [ "NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet", diff --git a/apps/common/db/models.py b/apps/common/db/models.py index d01a37266..a3d51bcb7 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -15,7 +15,7 @@ from django.db import models from django.db import transaction from django.db.models import F, ExpressionWrapper, CASCADE from django.db.models import QuerySet -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from ..const.signals import SKIP_SIGNAL diff --git a/apps/common/db/utils.py b/apps/common/db/utils.py index 23384f2c8..69c82e583 100644 --- a/apps/common/db/utils.py +++ b/apps/common/db/utils.py @@ -1,8 +1,9 @@ from contextlib import contextmanager from django.db import connections +from django.utils.encoding import force_str -from common.utils import get_logger +from common.utils import get_logger, signer, crypto logger = get_logger(__file__) @@ -55,3 +56,19 @@ def safe_db_connection(): close_old_connections() yield close_old_connections() + + +class Encryptor: + def __init__(self, value): + self.value = force_str(value) + + def decrypt(self): + plain_value = crypto.decrypt(self.value) + + # 如果没有解开,使用原来的signer解密 + if not plain_value: + plain_value = signer.unsign(self.value) or "" + return plain_value + + def encrypt(self): + return crypto.encrypt(self.value) diff --git a/apps/common/decorators.py b/apps/common/decorators.py index 4881c1517..353407ab1 100644 --- a/apps/common/decorators.py +++ b/apps/common/decorators.py @@ -67,12 +67,18 @@ class EventLoopThread(threading.Thread): _loop_thread = EventLoopThread() _loop_thread.setDaemon(True) _loop_thread.start() -executor = ThreadPoolExecutor(max_workers=10, - thread_name_prefix='debouncer') +executor = ThreadPoolExecutor( + max_workers=10, + thread_name_prefix='debouncer' +) _loop_debouncer_func_task_cache = {} _loop_debouncer_func_args_cache = {} +def get_loop(): + return _loop_thread.get_loop() + + def cancel_or_remove_debouncer_task(cache_key): task = _loop_debouncer_func_task_cache.get(cache_key, None) if not task: diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 731daf878..9caa84c28 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -7,8 +7,8 @@ from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_str +from django.utils.translation import gettext_lazy as _ from rest_framework import exceptions, serializers from rest_framework.fields import empty from rest_framework.metadata import SimpleMetadata @@ -81,7 +81,7 @@ class SimpleMetadataWithFilters(SimpleMetadata): field_info["choices"] = [ { "value": choice_value, - "label": force_text(choice_label, strings_only=True), + "label": force_str(choice_label, strings_only=True), } for choice_value, choice_label in dict(field.choices).items() ] @@ -109,7 +109,7 @@ class SimpleMetadataWithFilters(SimpleMetadata): for attr in self.attrs: value = getattr(field, attr, None) if value is not None and value != "": - field_info[attr] = force_text(value, strings_only=True) + field_info[attr] = force_str(value, strings_only=True) if getattr(field, "child", None): field_info["child"] = self.get_field_info(field.child) diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 43d736f21..6d4b70788 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -3,7 +3,7 @@ import codecs import json import re -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework import status from rest_framework.exceptions import ParseError, APIException diff --git a/apps/common/drf/renders/base.py b/apps/common/drf/renders/base.py index a9b3b5c8a..cdc1626ff 100644 --- a/apps/common/drf/renders/base.py +++ b/apps/common/drf/renders/base.py @@ -4,7 +4,7 @@ import re from datetime import datetime import pyzipper -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.renderers import BaseRenderer from rest_framework.utils import encoders, json diff --git a/apps/common/management/commands/services/services/flower.py b/apps/common/management/commands/services/services/flower.py index 94dd8e2f0..db859a78a 100644 --- a/apps/common/management/commands/services/services/flower.py +++ b/apps/common/management/commands/services/services/flower.py @@ -1,18 +1,10 @@ -from ..hands import * from .base import BaseService +from ..hands import * __all__ = ['FlowerService'] class FlowerService(BaseService): - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - @property - def db_file(self): - return os.path.join(BASE_DIR, 'data', 'flower.db') - @property def cmd(self): print("\n- Start Flower as Task Monitor") @@ -24,11 +16,9 @@ class FlowerService(BaseService): '-A', 'ops', 'flower', '-logging=info', - '-db={}'.format(self.db_file), '--url_prefix=/core/flower', '--auto_refresh=False', '--max_tasks=1000', - '--persistent=True', '--state_save_interval=600000' ] return cmd diff --git a/apps/common/plugins/es.py b/apps/common/plugins/es.py index a5e027c90..95f878263 100644 --- a/apps/common/plugins/es.py +++ b/apps/common/plugins/es.py @@ -2,8 +2,12 @@ # import datetime import inspect +import sys -from collections.abc import Iterable +if sys.version_info.major == 3 and sys.version_info.minor >= 10: + from collections.abc import Iterable +else: + from collections import Iterable from functools import reduce, partial from itertools import groupby from uuid import UUID @@ -19,7 +23,6 @@ from common.utils import get_logger from common.utils.timezone import local_now_date_display from common.exceptions import JMSException - logger = get_logger(__file__) @@ -88,7 +91,7 @@ class ES(object): try: # 获取索引信息,如果没有定义,直接返回 - data = self.es.indices.get_mapping(self.index) + data = self.es.indices.get_mapping(index=self.index) except NotFoundError: return False @@ -128,7 +131,7 @@ class ES(object): } try: - self.es.indices.create(self.index, body=mappings) + self.es.indices.create(index=self.index, body=mappings) except RequestError as e: if e.error == 'resource_already_exists_exception': logger.warning(e) diff --git a/apps/common/sdk/im/dingtalk/__init__.py b/apps/common/sdk/im/dingtalk/__init__.py index 4f54e1ea2..65d466008 100644 --- a/apps/common/sdk/im/dingtalk/__init__.py +++ b/apps/common/sdk/im/dingtalk/__init__.py @@ -1,10 +1,10 @@ -import time -import hmac import base64 +import hmac +import time -from common.utils import get_logger -from common.sdk.im.utils import digest, as_request from common.sdk.im.mixin import BaseRequest +from common.sdk.im.utils import digest, as_request +from common.utils import get_logger from users.utils import construct_user_email logger = get_logger(__file__) diff --git a/apps/common/sdk/im/feishu/__init__.py b/apps/common/sdk/im/feishu/__init__.py index 087a2fb88..99194e5c1 100644 --- a/apps/common/sdk/im/feishu/__init__.py +++ b/apps/common/sdk/im/feishu/__init__.py @@ -116,9 +116,13 @@ class FeiShu(RequestMixin): 'receive_id_type': 'user_id' } + """ + https://open.feishu.cn/document/common-capabilities/message-card/message-cards-content + /using-markdown-tags + """ body = { - 'msg_type': 'text', - 'content': json.dumps({'text': msg}) + 'msg_type': 'interactive', + 'content': json.dumps({'elements': [{'tag': 'markdown', 'content': msg}]}) } invalid_users = [] diff --git a/apps/common/sdk/im/wecom/__init__.py b/apps/common/sdk/im/wecom/__init__.py index b58e4ab3b..8262cbeb5 100644 --- a/apps/common/sdk/im/wecom/__init__.py +++ b/apps/common/sdk/im/wecom/__init__.py @@ -1,12 +1,12 @@ from typing import Iterable, AnyStr -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import APIException -from users.utils import construct_user_email -from common.utils.common import get_logger -from common.sdk.im.utils import digest, update_values from common.sdk.im.mixin import RequestMixin, BaseRequest +from common.sdk.im.utils import digest, update_values +from common.utils.common import get_logger +from users.utils import construct_user_email logger = get_logger(__name__) @@ -112,13 +112,13 @@ class WeCom(RequestMixin): update_values(extra_params, kwargs) body = { - "touser": '|'.join(users), - "msgtype": "text", - "agentid": self._agentid, - "text": { - "content": msg - }, - **extra_params + "touser": '|'.join(users), + "msgtype": "text", + "agentid": self._agentid, + "text": { + "content": msg + }, + **extra_params } if markdown: body['msgtype'] = 'markdown' @@ -184,4 +184,3 @@ class WeCom(RequestMixin): return { 'username': username, 'name': name, 'email': email } - diff --git a/apps/common/sdk/sms/cmpp2.py b/apps/common/sdk/sms/cmpp2.py index 4d81ee1a0..344cf829f 100644 --- a/apps/common/sdk/sms/cmpp2.py +++ b/apps/common/sdk/sms/cmpp2.py @@ -4,16 +4,14 @@ import struct import time from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from common.utils import get_logger from common.exceptions import JMSException +from common.utils import get_logger from .base import BaseSMSClient - logger = get_logger(__file__) - CMPP_CONNECT = 0x00000001 # 请求连接 CMPP_CONNECT_RESP = 0x80000001 # 请求连接应答 CMPP_TERMINATE = 0x00000002 # 终止连接 @@ -99,10 +97,10 @@ class CMPPSubmitRequestInstance(CMPPBaseRequestInstance): self.length = 126 + 21 * dest_usr_tl + len(_msg_content) self.command_id = CMPP_SUBMIT self.body = msg_id + pk_total + pk_number + registered_delivery \ - + msg_level + service_id + fee_user_type + fee_terminal_id \ - + tp_pid + tp_udhi + msg_fmt + _msg_src + fee_type + fee_code \ - + valid_time + at_time + src_id + _dest_usr_tl + _dest_terminal_id \ - + _msg_length + _msg_content + reserve + + msg_level + service_id + fee_user_type + fee_terminal_id \ + + tp_pid + tp_udhi + msg_fmt + _msg_src + fee_type + fee_code \ + + valid_time + at_time + src_id + _dest_usr_tl + _dest_terminal_id \ + + _msg_length + _msg_content + reserve class CMPPTerminateRequestInstance(CMPPBaseRequestInstance): @@ -161,7 +159,7 @@ class CMPPResponseInstance(object): src_terminal_id = body[42:63] registered_delivery = struct.unpack('!B', body[63:64]) msg_length = struct.unpack('!B', body[64:65]) - msg_content = body[65:msg_length[0]+65] + msg_content = body[65:msg_length[0] + 65] return { 'Msg_Id': msg_id, 'Dest_Id': dest_id, 'Service_Id': service_id, 'TP_pid': tp_pid, 'TP_udhi': tp_udhi, 'Msg_Fmt': msg_fmt, diff --git a/apps/common/serializers/dynamic.py b/apps/common/serializers/dynamic.py index a42529b5a..4442fd139 100644 --- a/apps/common/serializers/dynamic.py +++ b/apps/common/serializers/dynamic.py @@ -62,6 +62,8 @@ def create_serializer_class(serializer_name, fields_info): data['required'] = False data = set_default_by_type(field_type, data, field_info) data = set_default_if_need(data, i) + if field_type in ['int', 'bool', 'list'] and "allow_blank" in data.keys(): + data.pop('allow_blank') field_name = data.pop('name') field_class = type_field_map.get(field_type, serializers.CharField) serializer_fields[field_name] = field_class(**data) diff --git a/apps/common/serializers/mixin.py b/apps/common/serializers/mixin.py index 4d3e6ddc8..d665d6346 100644 --- a/apps/common/serializers/mixin.py +++ b/apps/common/serializers/mixin.py @@ -1,5 +1,10 @@ -from collections import Iterable, defaultdict, OrderedDict +import sys +from collections import defaultdict, OrderedDict +if sys.version_info.major >= 3 and sys.version_info.minor >= 10: + from collections.abc import Iterable +else: + from collections import Iterable from django.core.exceptions import ObjectDoesNotExist from django.db.models import NOT_PROVIDED from rest_framework import serializers diff --git a/apps/common/tasks.py b/apps/common/tasks.py index a3130ead6..a7d9aacdd 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -1,9 +1,9 @@ import os -from django.utils.translation import ugettext_lazy as _ -from django.core.mail import send_mail, EmailMultiAlternatives -from django.conf import settings from celery import shared_task +from django.conf import settings +from django.core.mail import send_mail, EmailMultiAlternatives +from django.utils.translation import gettext_lazy as _ from .utils import get_logger diff --git a/apps/common/utils/ip/geoip/utils.py b/apps/common/utils/ip/geoip/utils.py index 64752a3a1..693a0305c 100644 --- a/apps/common/utils/ip/geoip/utils.py +++ b/apps/common/utils/ip/geoip/utils.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -import os import ipaddress +import os import geoip2.database -from geoip2.errors import GeoIP2Error -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils.translation import gettext_lazy as _ +from geoip2.errors import GeoIP2Error __all__ = ['get_ip_city_by_geoip'] reader = None @@ -36,5 +36,3 @@ def get_ip_city_by_geoip(ip): lang = 'zh-CN' city = city_names.get(lang, _("Unknown")) return city - - diff --git a/apps/common/utils/ip/utils.py b/apps/common/utils/ip/utils.py index 14851ff27..4931bccbb 100644 --- a/apps/common/utils/ip/utils.py +++ b/apps/common/utils/ip/utils.py @@ -113,7 +113,6 @@ def get_ip_city(ip): def lookup_domain(domain): try: - return socket.gethostbyname(domain) + return socket.gethostbyname(domain), '' except Exception as e: - print("Cannot resolve %s: Unknown host, %s" % (domain, e)) - return None + return None, f'Cannot resolve {domain}: Unknown host, {e}' diff --git a/apps/common/utils/timezone.py b/apps/common/utils/timezone.py index 17e44b7bf..a74ebe73a 100644 --- a/apps/common/utils/timezone.py +++ b/apps/common/utils/timezone.py @@ -1,22 +1,11 @@ -import pytz -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta + from django.utils import timezone as dj_timezone from rest_framework.fields import DateTimeField -max = datetime.max.replace(tzinfo=timezone.utc) - - -def astimezone(dt: datetime, tzinfo: pytz.tzinfo.DstTzInfo): - assert dj_timezone.is_aware(dt) - return tzinfo.normalize(dt.astimezone(tzinfo)) - - -def as_china_cst(dt: datetime): - return astimezone(dt, pytz.timezone('Asia/Shanghai')) - def as_current_tz(dt: datetime): - return astimezone(dt, dj_timezone.get_current_timezone()) + return dt.astimezone(dj_timezone.get_current_timezone()) def utc_now(): diff --git a/apps/common/validators.py b/apps/common/validators.py index 5bb3e4bdb..7e33d7d76 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -3,14 +3,13 @@ import re import phonenumbers - from django.core.validators import RegexValidator -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from phonenumbers.phonenumberutil import NumberParseException +from rest_framework import serializers from rest_framework.validators import ( UniqueTogetherValidator, ValidationError ) -from rest_framework import serializers -from phonenumbers.phonenumberutil import NumberParseException from common.utils.strings import no_special_chars diff --git a/apps/jumpserver/asgi.py b/apps/jumpserver/asgi.py index a71974685..c2db9fdf6 100644 --- a/apps/jumpserver/asgi.py +++ b/apps/jumpserver/asgi.py @@ -1,4 +1,5 @@ import os + import django from channels.routing import get_default_application diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 33f0dbaf4..60d665a04 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -21,7 +21,7 @@ from urllib.parse import urljoin, urlparse import yaml from django.urls import reverse_lazy -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -220,6 +220,9 @@ class Config(dict): 'ANNOUNCEMENT_ENABLED': True, 'ANNOUNCEMENT': {}, + # Security + 'X_FRAME_OPTIONS': 'DENY', + # 未使用的配置 'CAPTCHA_TEST_MODE': None, 'DISPLAY_PER_PAGE': 25, @@ -234,7 +237,7 @@ class Config(dict): 'LOGIN_URL': reverse_lazy('authentication:login'), 'CONNECTION_TOKEN_ONETIME_EXPIRATION': 5 * 60, # 默认(new) - 'CONNECTION_TOKEN_EXPIRATION': 5 * 60, # 默认(old) + 'CONNECTION_TOKEN_EXPIRATION': 5 * 60, # 默认(old) 'CONNECTION_TOKEN_REUSABLE_EXPIRATION': 60 * 60 * 24 * 30, # 最大(new) 'CONNECTION_TOKEN_EXPIRATION_MAX': 60 * 60 * 24 * 30, # 最大(old) @@ -251,6 +254,16 @@ class Config(dict): # 临时密码 'AUTH_TEMP_TOKEN': False, + # Vault + 'VAULT_ENABLED': False, + 'VAULT_HCP_HOST': '', + 'VAULT_HCP_TOKEN': '', + 'VAULT_HCP_MOUNT_POINT': 'jumpserver', + + # Cache login password + 'CACHE_LOGIN_PASSWORD_ENABLED': False, + 'CACHE_LOGIN_PASSWORD_TTL': 60 * 60 * 24, + # Auth LDAP settings 'AUTH_LDAP': False, 'AUTH_LDAP_SERVER_URI': 'ldap://localhost:389', @@ -443,7 +456,6 @@ class Config(dict): 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', 'TERMINAL_SESSION_KEEP_DURATION': 200, 'TERMINAL_HOST_KEY': '', - 'TERMINAL_TELNET_REGEX': '', 'TERMINAL_COMMAND_STORAGE': {}, # Luna 页面 # 默认图形化分辨率 @@ -466,6 +478,7 @@ class Config(dict): 'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True, 'SECURITY_VIEW_AUTH_NEED_MFA': True, 'SECURITY_MAX_IDLE_TIME': 30, + 'SECURITY_MAX_SESSION_TIME': 24, 'SECURITY_PASSWORD_EXPIRATION_TIME': 9999, 'SECURITY_PASSWORD_MIN_LENGTH': 6, 'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': 6, @@ -482,6 +495,7 @@ class Config(dict): 'SECURITY_LUNA_REMEMBER_AUTH': True, 'SECURITY_WATERMARK_ENABLED': True, 'SECURITY_MFA_VERIFY_TTL': 3600, + 'SECURITY_UNCOMMON_USERS_TTL': 30, 'VERIFY_CODE_TTL': 60, 'SECURITY_SESSION_SHARE': True, 'SECURITY_CHECK_DIFFERENT_CITY_LOGIN': True, @@ -514,9 +528,9 @@ class Config(dict): 'TIME_ZONE': 'Asia/Shanghai', 'FORCE_SCRIPT_NAME': '', 'SESSION_COOKIE_SECURE': False, + 'DOMAINS': '', 'CSRF_COOKIE_SECURE': False, 'REFERER_CHECK_ENABLED': False, - 'CSRF_TRUSTED_ORIGINS': '', 'SESSION_ENGINE': 'cache', 'SESSION_SAVE_EVERY_REQUEST': True, 'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False, @@ -548,6 +562,7 @@ class Config(dict): 'TICKET_AUTHORIZE_DEFAULT_TIME': 7, 'TICKET_AUTHORIZE_DEFAULT_TIME_UNIT': 'day', 'PERIOD_TASK_ENABLED': True, + 'TERMINAL_TELNET_REGEX': '', # 导航栏 帮助 'HELP_DOCUMENT_URL': 'https://docs.jumpserver.org/zh/v3/', @@ -561,6 +576,9 @@ class Config(dict): # FTP 文件上传下载备份阈值,单位(M),当值小于等于0时,不备份 'FTP_FILE_MAX_STORE': 100, + + # API 请求次数限制 + 'MAX_LIMIT_PER_PAGE': 100 } old_config_map = { diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py index 9966228dc..7b92674bb 100644 --- a/apps/jumpserver/context_processor.py +++ b/apps/jumpserver/context_processor.py @@ -2,7 +2,7 @@ # from django.conf import settings from django.templatetags.static import static -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ default_interface = dict(( ('logo_logout', static('img/logo.png')), diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index a4738649f..9a49189c8 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -6,16 +6,12 @@ import re import time import pytz -from channels.db import database_sync_to_async from django.conf import settings from django.core.exceptions import MiddlewareNotUsed -from django.core.handlers.asgi import ASGIRequest from django.http.response import HttpResponseForbidden from django.shortcuts import HttpResponse from django.utils import timezone -from authentication.backends.drf import (SignatureAuthentication, - AccessTokenAuthentication) from .utils import set_current_request @@ -146,35 +142,3 @@ class EndMiddleware: response = self.get_response(request) request._e_time_end = time.time() return response - - -@database_sync_to_async -def get_signature_user(scope): - headers = dict(scope["headers"]) - if not headers.get(b'authorization'): - return - if scope['type'] == 'websocket': - scope['method'] = 'GET' - try: - # 因为 ws 使用的是 scope,所以需要转换成 request 对象,用于认证校验 - request = ASGIRequest(scope, None) - backends = [SignatureAuthentication(), - AccessTokenAuthentication()] - for backend in backends: - user, _ = backend.authenticate(request) - if user: - return user - except Exception as e: - print(e) - return None - - -class WsSignatureAuthMiddleware: - def __init__(self, app): - self.app = app - - async def __call__(self, scope, receive, send): - user = await get_signature_user(scope) - if user: - scope['user'] = user - return await self.app(scope, receive, send) diff --git a/apps/jumpserver/rewriting/db.py b/apps/jumpserver/rewriting/db.py index 6bc1b157b..1ccd49d8f 100644 --- a/apps/jumpserver/rewriting/db.py +++ b/apps/jumpserver/rewriting/db.py @@ -15,16 +15,18 @@ def atomic(using=None, savepoint=False): return db_atomic(using=using, savepoint=savepoint) -class OneToOneField(models.OneToOneField): +class OneToOneField(models.OneToOneField, ForeignKey): def __init__(self, *args, **kwargs): - kwargs['db_constraint'] = False - super().__init__(*args, **kwargs) + kwargs['unique'] = False + if os.getenv('DB_CONSTRAINT', '1') == '0': + kwargs['db_constraint'] = False + ForeignKey.__init__(self, *args, **kwargs) def set_db_constraint(): if os.getenv('DB_CONSTRAINT', '1') != '0': return - if sys.argv == 2 and sys.argv[1] == 'makemigrations': + if len(sys.argv) == 2 and sys.argv[1] == 'makemigrations': return print("Set foreignkey db constraint False") transaction.atomic = atomic diff --git a/apps/jumpserver/rewriting/pagination.py b/apps/jumpserver/rewriting/pagination.py index cd6ad2642..a924d854a 100644 --- a/apps/jumpserver/rewriting/pagination.py +++ b/apps/jumpserver/rewriting/pagination.py @@ -3,4 +3,4 @@ from rest_framework.pagination import LimitOffsetPagination class MaxLimitOffsetPagination(LimitOffsetPagination): - max_limit = settings.MAX_LIMIT_PER_PAGE or 100 + max_limit = settings.MAX_LIMIT_PER_PAGE diff --git a/apps/jumpserver/routing.py b/apps/jumpserver/routing.py index 965a2b71b..a23e2978b 100644 --- a/apps/jumpserver/routing.py +++ b/apps/jumpserver/routing.py @@ -1,21 +1,66 @@ from channels.auth import AuthMiddlewareStack +from channels.db import database_sync_to_async from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application +from django.core.handlers.asgi import ASGIRequest -from ops.urls.ws_urls import urlpatterns as ops_urlpatterns +from authentication.backends.drf import ( + SignatureAuthentication, + AccessTokenAuthentication +) from notifications.urls.ws_urls import urlpatterns as notifications_urlpatterns +from ops.urls.ws_urls import urlpatterns as ops_urlpatterns from settings.urls.ws_urls import urlpatterns as setting_urlpatterns from terminal.urls.ws_urls import urlpatterns as terminal_urlpatterns -from .middleware import WsSignatureAuthMiddleware +__all__ = ['urlpatterns', 'application'] + +urlpatterns = ops_urlpatterns + \ + notifications_urlpatterns + \ + setting_urlpatterns + \ + terminal_urlpatterns + + +@database_sync_to_async +def get_signature_user(scope): + headers = dict(scope["headers"]) + if not headers.get(b'authorization'): + return + if scope['type'] == 'websocket': + scope['method'] = 'GET' + try: + # 因为 ws 使用的是 scope,所以需要转换成 request 对象,用于认证校验 + request = ASGIRequest(scope, None) + backends = [SignatureAuthentication(), + AccessTokenAuthentication()] + for backend in backends: + user, _ = backend.authenticate(request) + if user: + return user + except Exception as e: + print(e) + return None + + +class WsSignatureAuthMiddleware: + def __init__(self, app): + self.app = app + + async def __call__(self, scope, receive, send): + user = await get_signature_user(scope) + if user: + scope['user'] = user + return await self.app(scope, receive, send) -urlpatterns = [] -urlpatterns += ops_urlpatterns + \ - notifications_urlpatterns + \ - setting_urlpatterns + \ - terminal_urlpatterns application = ProtocolTypeRouter({ - 'websocket': WsSignatureAuthMiddleware(AuthMiddlewareStack(URLRouter(urlpatterns))), + # Django's ASGI application to handle traditional HTTP requests "http": get_asgi_application(), + + # WebSocket chat handler + "websocket": WsSignatureAuthMiddleware( + AuthMiddlewareStack( + URLRouter(urlpatterns) + ) + ), }) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index fa3217f50..1654ae63c 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -174,6 +174,12 @@ AUTH_OAUTH2_LOGOUT_URL_NAME = "authentication:oauth2:logout" # 临时 token AUTH_TEMP_TOKEN = CONFIG.AUTH_TEMP_TOKEN +# Vault +VAULT_ENABLED = CONFIG.VAULT_ENABLED +VAULT_HCP_HOST = CONFIG.VAULT_HCP_HOST +VAULT_HCP_TOKEN = CONFIG.VAULT_HCP_TOKEN +VAULT_HCP_MOUNT_POINT = CONFIG.VAULT_HCP_MOUNT_POINT + # Other setting # 这个是 User Login Private Token TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 2bc547e57..5bd7fb84e 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -6,7 +6,7 @@ from redis.sentinel import SentinelManagedSSLConnection if platform.system() == 'Darwin' and platform.machine() == 'arm64': import pymysql - pymysql.version_info = (1, 4, 2, "final", 0) + # pymysql.version_info = (1, 4, 2, "final", 0) pymysql.install_as_MySQLdb() from django.urls import reverse_lazy @@ -65,14 +65,50 @@ APPLET_DOWNLOAD_HOST = CONFIG.APPLET_DOWNLOAD_HOST # https://docs.djangoproject.com/en/4.1/ref/settings/ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS -CSRF_TRUSTED_ORIGINS = CONFIG.CSRF_TRUSTED_ORIGINS.split(',') if CONFIG.CSRF_TRUSTED_ORIGINS else [] - # LOG LEVEL LOG_LEVEL = CONFIG.LOG_LEVEL +DOMAINS = CONFIG.DOMAINS or 'localhost' +for name in ['SERVER_NAME', 'CORE_HOST']: + env = os.environ.get(name) + if not env: + continue + DOMAINS += ',{}'.format(env) +if CONFIG.SITE_URL: + DOMAINS += ',{}'.format(CONFIG.SITE_URL) -ALLOWED_HOSTS = ['*'] +ALLOWED_DOMAINS = DOMAINS.split(',') if DOMAINS else ['localhost:8080'] +ALLOWED_DOMAINS = [host.strip() for host in ALLOWED_DOMAINS] +ALLOWED_DOMAINS = [host.replace('http://', '').replace('https://', '') for host in ALLOWED_DOMAINS if host] +ALLOWED_DOMAINS = [host.split('/')[0] for host in ALLOWED_DOMAINS if host] +DEBUG_HOSTS = ('127.0.0.1', 'localhost', 'core') +DEBUG_PORT = ['8080', '80', ] +if DEBUG: + DEBUG_PORT.extend(['4200', '9528']) +DEBUG_HOST_PORTS = ['{}:{}'.format(host, port) for host in DEBUG_HOSTS for port in DEBUG_PORT] +ALLOWED_DOMAINS.extend(DEBUG_HOST_PORTS) + +ALLOWED_HOSTS = list(set(['.' + host.split(':')[0] for host in ALLOWED_DOMAINS])) +print("ALLOWED_HOSTS: ", ) +for host in ALLOWED_HOSTS: + print(' - ' + host.lstrip('.')) + +# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS +CSRF_TRUSTED_ORIGINS = [] +for host_port in ALLOWED_DOMAINS: + origin = host_port.strip('.') + if origin.startswith('http'): + CSRF_TRUSTED_ORIGINS.append(origin) + continue + is_local_origin = origin.split(':')[0] in DEBUG_HOSTS + for schema in ['https', 'http']: + if is_local_origin and schema == 'https': + continue + CSRF_TRUSTED_ORIGINS.append('{}://*.{}'.format(schema, origin)) + +# print("CSRF_TRUSTED_ORIGINS: ") +# for origin in CSRF_TRUSTED_ORIGINS: +# print(' - ' + origin) # Max post update field num DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000 @@ -94,7 +130,6 @@ INSTALLED_APPS = [ 'acls.apps.AclsConfig', 'notifications.apps.NotificationsConfig', 'rbac.apps.RBACConfig', - 'jms_oidc_rp', 'rest_framework', 'rest_framework_swagger', 'drf_yasg', @@ -141,6 +176,9 @@ MIDDLEWARE = [ 'jumpserver.middleware.EndMiddleware', ] +if DEBUG or DEBUG_DEV: + INSTALLED_APPS.insert(0, 'daphne') + ROOT_URLCONF = 'jumpserver.urls' TEMPLATES = [ @@ -296,6 +334,8 @@ AUTH_USER_MODEL = 'users.User' FILE_UPLOAD_PERMISSIONS = 0o644 FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755 +X_FRAME_OPTIONS = CONFIG.X_FRAME_OPTIONS + # Cache use redis REDIS_SSL_KEY = exist_or_default(os.path.join(CERTS_DIR, 'redis_client.key'), None) REDIS_SSL_CERT = exist_or_default(os.path.join(CERTS_DIR, 'redis_client.crt'), None) diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index b9be9c75b..9688f2c82 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -35,6 +35,7 @@ FTP_FILE_MAX_STORE = CONFIG.FTP_FILE_MAX_STORE SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = CONFIG.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute +SECURITY_MAX_SESSION_TIME = CONFIG.SECURITY_MAX_SESSION_TIME # Unit: hour SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION SECURITY_COMMAND_BLACKLIST = CONFIG.SECURITY_COMMAND_BLACKLIST SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day @@ -54,6 +55,7 @@ SECURITY_PASSWORD_RULES = [ ] VERIFY_CODE_TTL = CONFIG.VERIFY_CODE_TTL SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL +SECURITY_UNCOMMON_USERS_TTL = CONFIG.SECURITY_UNCOMMON_USERS_TTL SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED @@ -73,6 +75,11 @@ SECURITY_LOGIN_IP_WHITE_LIST = CONFIG.SECURITY_LOGIN_IP_WHITE_LIST SECURITY_LOGIN_IP_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_IP_LIMIT_COUNT SECURITY_LOGIN_IP_LIMIT_TIME = CONFIG.SECURITY_LOGIN_IP_LIMIT_TIME # Unit: minute +# Authentication settings +# 缓存后可以给账号使用 +CACHE_LOGIN_PASSWORD_ENABLED = CONFIG.CACHE_LOGIN_PASSWORD_ENABLED +CACHE_LOGIN_PASSWORD_TTL = CONFIG.CACHE_LOGIN_PASSWORD_TTL + # Terminal other setting TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH @@ -82,11 +89,10 @@ TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION TERMINAL_HOST_KEY = CONFIG.TERMINAL_HOST_KEY TERMINAL_HEADER_TITLE = CONFIG.TERMINAL_HEADER_TITLE -TERMINAL_TELNET_REGEX = CONFIG.TERMINAL_TELNET_REGEX +# TERMINAL_TELNET_REGEX = CONFIG.TERMINAL_TELNET_REGEX # 默认图形化分辨率 TERMINAL_GRAPHICAL_RESOLUTION = CONFIG.TERMINAL_GRAPHICAL_RESOLUTION - # Asset user auth external backend, default AuthBook backend BACKEND_ASSET_USER_AUTH_VAULT = False diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 0c35ba322..3679320c4 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -116,7 +116,8 @@ else: host=CONFIG.REDIS_HOST, port=CONFIG.REDIS_PORT, db=CONFIG.REDIS_DB_WS ) REDIS_LAYERS_SSL_PARAMS.pop('ssl', None) - REDIS_LAYERS_HOST['address'] = '{}?{}'.format(REDIS_LAYERS_ADDRESS, urlencode(REDIS_LAYERS_SSL_PARAMS)) + REDIS_LAYERS_HOST['address'] = '{}?{}'.format(REDIS_LAYERS_ADDRESS, + urlencode(REDIS_LAYERS_SSL_PARAMS)) CHANNEL_LAYERS = { 'default': { diff --git a/apps/jumpserver/views/celery_flower.py b/apps/jumpserver/views/celery_flower.py index 3d98d3bef..fd6d3fac6 100644 --- a/apps/jumpserver/views/celery_flower.py +++ b/apps/jumpserver/views/celery_flower.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- # -from django.http import HttpResponse from django.conf import settings -from django.utils.translation import ugettext as _ +from django.http import HttpResponse +from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt - from proxy.views import proxy_view flower_url = settings.FLOWER_URL @@ -24,4 +23,3 @@ def celery_flower_view(request, path): '

{}
'.format(e) response = HttpResponse(msg) return response - diff --git a/apps/jumpserver/views/other.py b/apps/jumpserver/views/other.py index 984898158..038a6fb06 100644 --- a/apps/jumpserver/views/other.py +++ b/apps/jumpserver/views/other.py @@ -2,13 +2,13 @@ # import re -from django.http import HttpResponseRedirect, JsonResponse, Http404 from django.conf import settings -from django.views.generic import View, TemplateView -from django.shortcuts import redirect -from django.utils.translation import ugettext_lazy as _ -from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse +from django.http import HttpResponseRedirect, JsonResponse, Http404 +from django.shortcuts import redirect +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import View, TemplateView from rest_framework.views import APIView from common.views.http import HttpResponseTemporaryRedirect diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 4a4ff2f0b..2e46fa34c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d100b8957a5be7b3819b1f47bbd0e0f358c4d77112afb2aa8072f034372a412 -size 148962 +oid sha256:810d46e14e09a2309a8d898bc391f33082bfc5c164dec246bd95cea8436e33ee +size 154540 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 5797de8e5..e541c9c8c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-20 18:40+0800\n" +"POT-Creation-Date: 2023-08-17 15:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: accounts/api/automations/base.py:79 +#: accounts/api/automations/base.py:79 tickets/api/ticket.py:112 msgid "The parameter 'action' must be [{}]" msgstr "パラメータ 'action' は [{}] でなければなりません。" @@ -45,7 +45,7 @@ msgid "Access key" msgstr "アクセスキー" #: accounts/const/account.py:9 assets/models/_user.py:48 -#: authentication/models/sso_token.py:14 +#: authentication/models/sso_token.py:14 settings/serializers/feature.py:50 msgid "Token" msgstr "トークン" @@ -53,12 +53,12 @@ msgstr "トークン" msgid "API key" msgstr "" -#: accounts/const/account.py:14 common/db/fields.py:244 +#: accounts/const/account.py:14 common/db/fields.py:235 #: settings/serializers/terminal.py:14 msgid "All" msgstr "すべて" -#: accounts/const/account.py:15 +#: accounts/const/account.py:15 accounts/models/virtual.py:26 msgid "Manual input" msgstr "手動入力" @@ -70,32 +70,32 @@ msgstr "動的コード" msgid "Anonymous account" msgstr "匿名ユーザー" -#: accounts/const/account.py:21 users/models/user.py:699 +#: accounts/const/account.py:25 users/models/user.py:733 msgid "Local" msgstr "ローカル" -#: accounts/const/account.py:22 +#: accounts/const/account.py:26 msgid "Collected" msgstr "集めました" -#: accounts/const/account.py:23 accounts/serializers/account/account.py:27 +#: accounts/const/account.py:27 accounts/serializers/account/account.py:27 #: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "テンプレート" -#: accounts/const/account.py:27 ops/const.py:45 +#: accounts/const/account.py:31 ops/const.py:45 msgid "Skip" msgstr "スキップ" -#: accounts/const/account.py:28 audits/const.py:24 rbac/tree.py:230 +#: accounts/const/account.py:32 audits/const.py:24 rbac/tree.py:233 #: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" -#: accounts/const/account.py:29 +#: accounts/const/account.py:33 #: accounts/serializers/automations/change_secret.py:156 audits/const.py:54 #: audits/signal_handlers/activity_log.py:33 common/const/choices.py:19 -#: ops/const.py:58 terminal/const.py:77 xpack/plugins/cloud/const.py:43 +#: ops/const.py:61 terminal/const.py:77 xpack/plugins/cloud/const.py:43 msgid "Failed" msgstr "失敗しました" @@ -187,125 +187,126 @@ msgstr "作成してプッシュ" msgid "Only create" msgstr "作成のみ" -#: accounts/models/account.py:49 +#: accounts/const/vault.py:8 assets/const/category.py:12 +#: assets/models/asset/database.py:9 assets/models/asset/database.py:24 +msgid "Database" +msgstr "データベース" + +#: accounts/const/vault.py:9 settings/serializers/feature.py:41 +msgid "HCP Vault" +msgstr "HashiCorp Vault" + +#: accounts/models/account.py:48 #: accounts/models/automations/gather_account.py:16 -#: accounts/serializers/account/account.py:200 -#: accounts/serializers/account/account.py:237 +#: accounts/serializers/account/account.py:202 +#: accounts/serializers/account/account.py:247 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/serializers/base.py:123 assets/models/asset/common.py:93 -#: assets/models/asset/common.py:331 assets/models/cmd_filter.py:36 +#: assets/models/asset/common.py:334 assets/models/cmd_filter.py:36 #: assets/serializers/domain.py:19 assets/serializers/label.py:27 #: audits/models.py:53 authentication/models/connection_token.py:36 #: perms/models/asset_permission.py:64 perms/serializers/permission.py:34 -#: terminal/backends/command/models.py:18 terminal/models/session/session.py:31 -#: terminal/notifications.py:155 terminal/serializers/command.py:18 +#: terminal/backends/command/models.py:17 terminal/models/session/session.py:31 +#: terminal/notifications.py:155 terminal/serializers/command.py:17 +#: terminal/serializers/session.py:26 #: terminal/templates/terminal/_msg_command_warning.html:4 -#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212 +#: terminal/templates/terminal/_msg_session_sharing.html:4 +#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:253 msgid "Asset" msgstr "資産" -#: accounts/models/account.py:53 accounts/models/account.py:126 -#: accounts/serializers/account/account.py:208 -#: accounts/serializers/account/account.py:247 +#: accounts/models/account.py:52 accounts/models/template.py:15 +#: accounts/serializers/account/account.py:209 +#: accounts/serializers/account/account.py:257 #: accounts/serializers/account/template.py:16 #: authentication/serializers/connect_token_secret.py:49 msgid "Su from" msgstr "から切り替え" -#: accounts/models/account.py:55 settings/serializers/auth/cas.py:20 +#: accounts/models/account.py:54 settings/serializers/auth/cas.py:20 #: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:34 msgid "Version" msgstr "バージョン" -#: accounts/models/account.py:57 accounts/serializers/account/account.py:203 -#: users/models/user.py:804 +#: accounts/models/account.py:56 accounts/serializers/account/account.py:204 +#: users/models/user.py:838 msgid "Source" msgstr "ソース" -#: accounts/models/account.py:58 +#: accounts/models/account.py:57 msgid "Source ID" msgstr "ソース ID" -#: accounts/models/account.py:61 +#: accounts/models/account.py:60 #: accounts/serializers/automations/change_secret.py:113 #: accounts/serializers/automations/change_secret.py:133 #: acls/serializers/base.py:124 assets/serializers/asset/common.py:125 #: assets/serializers/gateway.py:28 audits/models.py:54 ops/models/base.py:18 #: perms/models/asset_permission.py:70 perms/serializers/permission.py:39 -#: terminal/backends/command/models.py:19 terminal/models/session/session.py:33 +#: terminal/backends/command/models.py:18 terminal/models/session/session.py:33 #: terminal/templates/terminal/_msg_command_warning.html:8 -#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85 +#: terminal/templates/terminal/_msg_session_sharing.html:8 +#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:89 msgid "Account" msgstr "アカウント" -#: accounts/models/account.py:67 +#: accounts/models/account.py:66 msgid "Can view asset account secret" msgstr "資産アカウントの秘密を表示できます" -#: accounts/models/account.py:68 +#: accounts/models/account.py:67 msgid "Can view asset history account" msgstr "資産履歴アカウントを表示できます" -#: accounts/models/account.py:69 +#: accounts/models/account.py:68 msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" -#: accounts/models/account.py:70 +#: accounts/models/account.py:69 msgid "Can verify account" msgstr "アカウントを確認できます" -#: accounts/models/account.py:71 +#: accounts/models/account.py:70 msgid "Can push account" msgstr "アカウントをプッシュできます" -#: accounts/models/account.py:130 -msgid "Account template" -msgstr "アカウント テンプレート" - -#: accounts/models/account.py:135 -msgid "Can view asset account template secret" -msgstr "アセット アカウント テンプレートのパスワードを表示できます" - -#: accounts/models/account.py:136 -msgid "Can change asset account template secret" -msgstr "アセット アカウント テンプレートのパスワードを変更できます" - #: accounts/models/automations/backup_account.py:27 -#: accounts/models/automations/change_secret.py:64 -#: accounts/serializers/account/backup.py:34 -#: accounts/serializers/automations/change_secret.py:57 -msgid "Recipient" -msgstr "受信者" +msgid "Recipient part one" +msgstr "受信者 1" -#: accounts/models/automations/backup_account.py:36 -#: accounts/models/automations/backup_account.py:102 +#: accounts/models/automations/backup_account.py:31 +msgid "Recipient part two" +msgstr "受信者 2" + +#: accounts/models/automations/backup_account.py:40 +#: accounts/models/automations/backup_account.py:110 msgid "Account backup plan" msgstr "アカウントバックアップ計画" -#: accounts/models/automations/backup_account.py:83 +#: accounts/models/automations/backup_account.py:91 #: assets/models/automations/base.py:115 audits/models.py:60 -#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:194 +#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:228 #: ops/templates/ops/celery_task_log.html:75 -#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:137 +#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:140 #: terminal/models/session/session.py:44 #: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 msgid "Date start" msgstr "開始日" -#: accounts/models/automations/backup_account.py:86 +#: accounts/models/automations/backup_account.py:94 #: authentication/templates/authentication/_msg_oauth_bind.html:11 #: notifications/notifications.py:186 msgid "Time" msgstr "時間" -#: accounts/models/automations/backup_account.py:90 +#: accounts/models/automations/backup_account.py:98 msgid "Account backup snapshot" msgstr "アカウントのバックアップスナップショット" -#: accounts/models/automations/backup_account.py:94 +#: accounts/models/automations/backup_account.py:102 #: accounts/serializers/account/backup.py:42 #: accounts/serializers/automations/base.py:55 #: assets/models/automations/base.py:122 @@ -313,19 +314,19 @@ msgstr "アカウントのバックアップスナップショット" msgid "Trigger mode" msgstr "トリガーモード" -#: accounts/models/automations/backup_account.py:97 audits/models.py:194 -#: terminal/models/session/sharing.py:111 xpack/plugins/cloud/models.py:168 +#: accounts/models/automations/backup_account.py:105 audits/models.py:194 +#: terminal/models/session/sharing.py:121 xpack/plugins/cloud/models.py:205 msgid "Reason" msgstr "理由" -#: accounts/models/automations/backup_account.py:99 +#: accounts/models/automations/backup_account.py:107 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:134 -#: ops/serializers/job.py:56 terminal/serializers/session.py:43 +#: ops/serializers/job.py:56 terminal/serializers/session.py:46 msgid "Is success" msgstr "成功は" -#: accounts/models/automations/backup_account.py:107 +#: accounts/models/automations/backup_account.py:115 msgid "Account backup execution" msgstr "アカウントバックアップの実行" @@ -366,7 +367,7 @@ msgid "Can add push account execution" msgstr "プッシュ アカウントの作成の実行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:419 +#: accounts/serializers/account/account.py:429 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 #: authentication/serializers/connect_token_secret.py:41 @@ -375,8 +376,7 @@ msgid "Secret type" msgstr "鍵の種類" #: accounts/models/automations/change_secret.py:20 -#: accounts/models/automations/change_secret.py:89 accounts/models/base.py:38 -#: accounts/serializers/account/base.py:19 +#: accounts/models/mixins/vault.py:48 accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 @@ -396,13 +396,23 @@ msgstr "パスワードルール" msgid "SSH key change strategy" msgstr "SSHキープッシュ方式" +#: accounts/models/automations/change_secret.py:64 +#: accounts/serializers/account/backup.py:34 +#: accounts/serializers/automations/change_secret.py:57 +msgid "Recipient" +msgstr "受信者" + #: accounts/models/automations/change_secret.py:71 msgid "Change secret automation" msgstr "自動暗号化" #: accounts/models/automations/change_secret.py:88 msgid "Old secret" -msgstr "以前のパスワード" +msgstr "オリジナルキー" + +#: accounts/models/automations/change_secret.py:89 +msgid "New secret" +msgstr "新しい鍵" #: accounts/models/automations/change_secret.py:90 msgid "Date started" @@ -410,15 +420,15 @@ msgstr "開始日" #: accounts/models/automations/change_secret.py:91 #: assets/models/automations/base.py:116 ops/models/base.py:56 -#: ops/models/celery.py:64 ops/models/job.py:195 -#: terminal/models/applet/host.py:138 +#: ops/models/celery.py:64 ops/models/job.py:229 +#: terminal/models/applet/host.py:141 msgid "Date finished" msgstr "終了日" #: accounts/models/automations/change_secret.py:93 -#: accounts/serializers/account/account.py:239 assets/const/automation.py:8 -#: authentication/views/base.py:29 authentication/views/base.py:30 -#: authentication/views/base.py:31 common/const/choices.py:20 +#: accounts/serializers/account/account.py:249 assets/const/automation.py:8 +#: authentication/views/base.py:26 authentication/views/base.py:27 +#: authentication/views/base.py:28 common/const/choices.py:20 msgid "Error" msgstr "間違い" @@ -436,13 +446,14 @@ msgstr "最終ログイン日" #: accounts/models/automations/gather_account.py:17 #: accounts/models/automations/push_account.py:15 accounts/models/base.py:34 -#: acls/serializers/base.py:19 acls/serializers/base.py:50 -#: assets/models/_user.py:23 audits/models.py:179 authentication/forms.py:25 -#: authentication/forms.py:27 authentication/models/temp_token.py:9 +#: accounts/serializers/account/virtual.py:21 acls/serializers/base.py:19 +#: acls/serializers/base.py:50 assets/models/_user.py:23 audits/models.py:179 +#: authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/forms/profile.py:115 -#: users/models/user.py:751 users/templates/users/_msg_user_created.html:12 +#: users/models/user.py:785 users/templates/users/_msg_user_created.html:12 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" msgstr "ユーザー名" @@ -470,7 +481,7 @@ msgstr "トリガー方式" #: accounts/models/automations/push_account.py:16 acls/models/base.py:41 #: acls/serializers/base.py:57 assets/models/cmd_filter.py:81 -#: audits/models.py:87 audits/serializers.py:82 +#: audits/models.py:87 audits/serializers.py:83 #: authentication/serializers/connect_token_secret.py:116 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" @@ -484,42 +495,84 @@ msgstr "アカウントプッシュ" msgid "Verify asset account" msgstr "アカウントの確認" -#: accounts/models/base.py:33 acls/models/base.py:35 acls/models/base.py:96 -#: acls/models/command_acl.py:21 acls/serializers/base.py:35 -#: applications/models.py:9 assets/models/_user.py:22 -#: assets/models/asset/common.py:91 assets/models/asset/common.py:149 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:18 -#: assets/models/group.py:17 assets/models/label.py:18 -#: assets/models/platform.py:15 assets/models/platform.py:88 -#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:110 -#: assets/serializers/platform.py:223 +#: accounts/models/base.py:33 accounts/serializers/account/virtual.py:20 +#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21 +#: acls/serializers/base.py:35 applications/models.py:9 +#: assets/models/_user.py:22 assets/models/asset/common.py:91 +#: assets/models/asset/common.py:149 assets/models/cmd_filter.py:21 +#: assets/models/domain.py:18 assets/models/group.py:17 +#: assets/models/label.py:18 assets/models/platform.py:15 +#: assets/models/platform.py:88 assets/serializers/asset/common.py:146 +#: assets/serializers/platform.py:110 assets/serializers/platform.py:223 #: authentication/serializers/connect_token_secret.py:110 ops/mixin.py:21 -#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 -#: ops/models/job.py:94 ops/models/playbook.py:23 ops/serializers/job.py:20 -#: orgs/models.py:80 perms/models/asset_permission.py:56 rbac/models/role.py:29 -#: settings/models.py:33 settings/serializers/sms.py:6 +#: ops/models/adhoc.py:20 ops/models/celery.py:15 ops/models/celery.py:57 +#: ops/models/job.py:126 ops/models/playbook.py:28 ops/serializers/job.py:20 +#: orgs/models.py:82 perms/models/asset_permission.py:56 rbac/models/role.py:29 +#: settings/models.py:32 settings/serializers/msg.py:82 #: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12 -#: terminal/models/component/endpoint.py:92 -#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 +#: terminal/models/component/endpoint.py:94 +#: terminal/models/component/storage.py:26 terminal/models/component/task.py:13 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33 -#: users/models/group.py:13 users/models/user.py:753 -#: xpack/plugins/cloud/models.py:28 +#: users/models/group.py:13 users/models/user.py:787 +#: xpack/plugins/cloud/models.py:32 xpack/plugins/cloud/models.py:273 +#: xpack/plugins/cloud/serializers/task.py:68 msgid "Name" msgstr "名前" -#: accounts/models/base.py:39 +#: accounts/models/base.py:38 msgid "Privileged" msgstr "特権アカウント" -#: accounts/models/base.py:40 assets/models/asset/common.py:156 +#: accounts/models/base.py:39 assets/models/asset/common.py:156 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:114 #: terminal/models/applet/applet.py:39 -#: terminal/models/component/endpoint.py:103 users/serializers/user.py:169 +#: terminal/models/component/endpoint.py:105 users/serializers/user.py:169 msgid "Is active" msgstr "アクティブです。" +#: accounts/models/template.py:19 xpack/plugins/cloud/models.py:325 +msgid "Account template" +msgstr "アカウント テンプレート" + +#: accounts/models/template.py:24 +msgid "Can view asset account template secret" +msgstr "アセット アカウント テンプレートのパスワードを表示できます" + +#: accounts/models/template.py:25 +msgid "Can change asset account template secret" +msgstr "アセット アカウント テンプレートのパスワードを変更できます" + +#: accounts/models/virtual.py:13 +msgid "Alias" +msgstr "別名" + +#: accounts/models/virtual.py:14 +msgid "Secret from login" +msgstr "ログインからのパスワード" + +#: accounts/models/virtual.py:27 +msgid "Same with user" +msgstr "ユーザーと同じユーザー名" + +#: accounts/models/virtual.py:36 +msgid "Non-asset account, Input username/password on connect" +msgstr "" +"アセットアカウントではない場合、接続時にユーザー名/パスワードを入力します" + +#: accounts/models/virtual.py:37 +msgid "The account username name same with user on connect" +msgstr "接続時にユーザー名と同じユーザー名を使用します" + +#: accounts/models/virtual.py:38 +msgid "" +"Connect asset without using a username and password, and it only supports " +"web-based and custom-type assets" +msgstr "" +"ユーザー名とパスワードを使用せずにアセットに接続します。Webベースとカスタムタ" +"イプのアセットのみをサポートします" + #: accounts/notifications.py:8 msgid "Notification of account backup route task results" msgstr "アカウントバックアップルートタスクの結果の通知" @@ -569,84 +622,83 @@ msgstr "今すぐプッシュ" msgid "Exist policy" msgstr "アカウントの存在ポリシー" -#: accounts/serializers/account/account.py:180 applications/models.py:11 +#: accounts/serializers/account/account.py:182 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:89 #: assets/serializers/asset/common.py:121 assets/serializers/cagegory.py:8 #: assets/serializers/platform.py:128 assets/serializers/platform.py:224 -#: perms/serializers/user_permission.py:26 settings/models.py:35 +#: perms/serializers/user_permission.py:26 settings/models.py:34 #: tickets/models/ticket/apply_application.py:13 msgid "Category" msgstr "カテゴリ" -#: accounts/serializers/account/account.py:181 +#: accounts/serializers/account/account.py:183 #: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24 #: acls/serializers/command_acl.py:18 applications/models.py:14 #: assets/models/_user.py:50 assets/models/automations/base.py:20 #: assets/models/cmd_filter.py:74 assets/models/platform.py:90 #: assets/serializers/asset/common.py:122 assets/serializers/platform.py:112 -#: assets/serializers/platform.py:127 audits/serializers.py:48 -#: authentication/serializers/connect_token_secret.py:123 ops/models/job.py:105 +#: assets/serializers/platform.py:127 audits/serializers.py:49 +#: authentication/serializers/connect_token_secret.py:123 ops/models/job.py:137 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:38 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 -#: terminal/serializers/session.py:20 terminal/serializers/storage.py:224 -#: terminal/serializers/storage.py:236 tickets/models/comment.py:26 +#: terminal/serializers/session.py:21 terminal/serializers/storage.py:226 +#: terminal/serializers/storage.py:238 tickets/models/comment.py:26 #: tickets/models/flow.py:56 tickets/models/ticket/apply_application.py:16 #: tickets/models/ticket/general.py:275 tickets/serializers/flow.py:53 #: tickets/serializers/ticket/ticket.py:19 msgid "Type" msgstr "タイプ" -#: accounts/serializers/account/account.py:196 +#: accounts/serializers/account/account.py:198 msgid "Asset not found" msgstr "資産が存在しません" -#: accounts/serializers/account/account.py:201 -#: accounts/serializers/account/base.py:64 +#: accounts/serializers/account/account.py:238 msgid "Has secret" msgstr "エスクローされたパスワード" -#: accounts/serializers/account/account.py:238 ops/models/celery.py:60 +#: accounts/serializers/account/account.py:248 ops/models/celery.py:60 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14 #: tickets/serializers/ticket/ticket.py:21 msgid "State" msgstr "状態" -#: accounts/serializers/account/account.py:240 +#: accounts/serializers/account/account.py:250 msgid "Changed" msgstr "編集済み" -#: accounts/serializers/account/account.py:250 +#: accounts/serializers/account/account.py:260 #: accounts/serializers/automations/base.py:22 acls/models/base.py:97 #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 ops/models/base.py:17 -#: ops/models/job.py:107 ops/serializers/job.py:21 +#: ops/models/job.py:139 ops/serializers/job.py:21 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 msgid "Assets" msgstr "資産" -#: accounts/serializers/account/account.py:305 +#: accounts/serializers/account/account.py:315 msgid "Account already exists" msgstr "アカウントはすでに存在しています" -#: accounts/serializers/account/account.py:355 +#: accounts/serializers/account/account.py:365 #, python-format msgid "Asset does not support this secret type: %s" msgstr "アセットはアカウント タイプをサポートしていません: %s" -#: accounts/serializers/account/account.py:387 +#: accounts/serializers/account/account.py:397 msgid "Account has exist" msgstr "アカウントはすでに存在しています" -#: accounts/serializers/account/account.py:420 +#: accounts/serializers/account/account.py:430 #: authentication/serializers/connect_token_secret.py:156 #: authentication/templates/authentication/_access_key_modal.html:30 #: perms/models/perm_node.py:21 users/serializers/group.py:31 msgid "ID" msgstr "ID" -#: accounts/serializers/account/account.py:427 acls/serializers/base.py:116 +#: accounts/serializers/account/account.py:437 acls/serializers/base.py:116 #: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49 #: audits/models.py:85 audits/models.py:163 #: authentication/models/connection_token.py:32 @@ -654,17 +706,18 @@ msgstr "ID" #: notifications/models/notification.py:12 #: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 #: perms/serializers/permission.py:30 rbac/builtin.py:123 -#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:17 -#: terminal/models/session/session.py:29 terminal/models/session/sharing.py:32 -#: terminal/notifications.py:156 terminal/notifications.py:205 -#: terminal/serializers/command.py:17 +#: rbac/models/rolebinding.py:49 rbac/serializers/rolebinding.py:17 +#: terminal/backends/command/models.py:16 terminal/models/session/session.py:29 +#: terminal/models/session/sharing.py:33 terminal/notifications.py:156 +#: terminal/notifications.py:205 terminal/serializers/command.py:16 #: terminal/templates/terminal/_msg_command_warning.html:6 -#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:947 -#: users/models/user.py:978 users/serializers/group.py:18 +#: terminal/templates/terminal/_msg_session_sharing.html:6 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:981 +#: users/models/user.py:1017 users/serializers/group.py:18 msgid "User" msgstr "ユーザー" -#: accounts/serializers/account/account.py:428 +#: accounts/serializers/account/account.py:438 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:158 terminal/notifications.py:207 msgid "Date" @@ -695,12 +748,12 @@ msgstr "資産タイプ" msgid "Key password" msgstr "キーパスワード" -#: accounts/serializers/account/base.py:80 -#: assets/serializers/asset/common.py:309 +#: accounts/serializers/account/base.py:78 +#: assets/serializers/asset/common.py:311 msgid "Spec info" msgstr "特別情報" -#: accounts/serializers/account/base.py:82 +#: accounts/serializers/account/base.py:80 msgid "" "Tip: If no username is required for authentication, fill in `null`, If AD " "account, like `username@domain`" @@ -708,6 +761,28 @@ msgstr "" "ヒント: 認証にユーザー名が必要ない場合は、`null`を入力します。ADアカウントの" "場合は、`username@domain`のようになります。" +#: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 +#: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 +#: ops/models/job.py:145 ops/models/playbook.py:31 rbac/models/role.py:37 +#: settings/models.py:37 terminal/models/applet/applet.py:44 +#: terminal/models/applet/applet.py:284 terminal/models/applet/host.py:142 +#: terminal/models/component/endpoint.py:24 +#: terminal/models/component/endpoint.py:104 +#: terminal/models/session/session.py:46 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:297 users/models/user.py:826 +#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/models.py:109 +msgid "Comment" +msgstr "コメント" + +#: accounts/serializers/account/virtual.py:22 +msgid "" +"Current only support login from AD/LDAP. Secret priority: Same account in " +"asset secret > Login secret > Manual input" +msgstr "" +"現在、AD/LDAPからのログインのみをサポートしています。シークレットの優先順位: " +"資産シークレット内の同じアカウント > ログインシークレット > 手動入力" + #: accounts/serializers/automations/base.py:23 #: assets/models/asset/common.py:155 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21 @@ -744,9 +819,9 @@ msgstr "自動タスク実行履歴" #: accounts/serializers/automations/change_secret.py:155 audits/const.py:53 #: audits/models.py:59 audits/signal_handlers/activity_log.py:33 -#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:40 -#: terminal/const.py:76 terminal/models/session/sharing.py:107 -#: tickets/views/approve.py:114 +#: common/const/choices.py:18 ops/const.py:59 ops/serializers/celery.py:40 +#: terminal/const.py:76 terminal/models/session/sharing.py:117 +#: tickets/views/approve.py:115 msgid "Success" msgstr "成功" @@ -766,6 +841,10 @@ msgstr "資産の口座番号を収集する" msgid "Push accounts to assets" msgstr "アカウントをアセットにプッシュ:" +#: accounts/tasks/vault.py:31 +msgid "Sync secret to vault" +msgstr "秘密をVaultに同期する" + #: accounts/tasks/verify_account.py:49 msgid "Verify asset account availability" msgstr "アセット アカウントの可用性を確認する" @@ -774,19 +853,19 @@ msgstr "アセット アカウントの可用性を確認する" msgid "Verify accounts connectivity" msgstr "アカウント接続のテスト" -#: accounts/utils.py:43 +#: accounts/utils.py:41 msgid "Password can not contains `{{` " msgstr "パスワードには '{{' を含まない" -#: accounts/utils.py:46 +#: accounts/utils.py:44 msgid "Password can not contains `'` " msgstr "パスワードには `'` を含まない" -#: accounts/utils.py:48 +#: accounts/utils.py:46 msgid "Password can not contains `\"` " msgstr "パスワードには `\"` を含まない" -#: accounts/utils.py:54 +#: accounts/utils.py:52 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" @@ -812,12 +891,14 @@ msgid "Warning" msgstr "警告" #: acls/models/base.py:37 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:95 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:97 +#: xpack/plugins/cloud/models.py:275 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:38 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:96 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:98 +#: xpack/plugins/cloud/models.py:276 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -829,7 +910,7 @@ msgstr "レビュー担当者" #: acls/models/base.py:43 authentication/models/access_key.py:17 #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:27 +#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" msgstr "アクティブ" @@ -839,14 +920,14 @@ msgid "Users" msgstr "ユーザー" #: acls/models/base.py:98 assets/models/automations/base.py:17 -#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:308 +#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:310 #: rbac/tree.py:35 msgid "Accounts" msgstr "アカウント" #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60 #: ops/serializers/job.py:55 terminal/const.py:84 -#: terminal/models/session/session.py:42 terminal/serializers/command.py:19 +#: terminal/models/session/session.py:42 terminal/serializers/command.py:18 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 #: terminal/templates/terminal/_msg_command_warning.html:23 @@ -854,11 +935,12 @@ msgid "Command" msgstr "コマンド" #: acls/models/command_acl.py:17 assets/models/cmd_filter.py:59 +#: xpack/plugins/cloud/models.py:291 msgid "Regex" msgstr "正規情報" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 +#: settings/serializers/feature.py:17 xpack/plugins/license/models.py:30 msgid "Content" msgstr "コンテンツ" @@ -950,7 +1032,7 @@ msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" #: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:22 +#: xpack/plugins/cloud/serializers/task.py:133 msgid "IP address invalid: `{}`" msgstr "IPアドレスが無効: '{}'" @@ -978,7 +1060,7 @@ msgstr "期間" msgid "Applications" msgstr "アプリケーション" -#: applications/models.py:16 xpack/plugins/cloud/models.py:33 +#: applications/models.py:16 xpack/plugins/cloud/models.py:37 #: xpack/plugins/cloud/serializers/account.py:63 msgid "Attrs" msgstr "ツールバーの" @@ -997,7 +1079,7 @@ msgstr "" "資産を直接作成することはできません。ホストまたはその他を作成する必要がありま" "す" -#: assets/api/domain.py:62 +#: assets/api/domain.py:63 msgid "Number required" msgstr "必要な数" @@ -1040,7 +1122,7 @@ msgid "Unable to connect to port {port} on {address}" msgstr "{port} のポート {address} に接続できません" #: assets/automations/ping_gateway/manager.py:58 -#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:48 +#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:47 msgid "Authentication failed" msgstr "認証に失敗しました" @@ -1076,18 +1158,19 @@ msgstr "資産情報の収集" msgid "Disabled" msgstr "無効" -#: assets/const/base.py:34 settings/serializers/basic.py:27 +#: assets/const/base.py:34 settings/serializers/basic.py:6 msgid "Basic" msgstr "基本" -#: assets/const/base.py:35 assets/const/protocol.py:193 +#: assets/const/base.py:35 assets/const/protocol.py:230 #: assets/models/asset/web.py:13 msgid "Script" msgstr "脚本" #: assets/const/category.py:10 assets/models/asset/host.py:8 #: settings/serializers/auth/radius.py:16 settings/serializers/auth/sms.py:67 -#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17 +#: settings/serializers/feature.py:47 terminal/models/component/endpoint.py:13 +#: terminal/serializers/applet.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -1096,11 +1179,6 @@ msgstr "ホスト" msgid "Device" msgstr "インターネット機器" -#: assets/const/category.py:12 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 -msgid "Database" -msgstr "データベース" - #: assets/const/category.py:13 msgid "Cloud service" msgstr "クラウド サービス" @@ -1152,10 +1230,6 @@ msgstr "" msgid "Other" msgstr "その他" -#: assets/const/protocol.py:43 -msgid "SFTP enabled" -msgstr "SFTP が有効" - #: assets/const/protocol.py:48 msgid "SFTP home" msgstr "SFTP ルート パス" @@ -1172,7 +1246,7 @@ msgstr "コンソールセッションに接続" msgid "Any" msgstr "任意" -#: assets/const/protocol.py:66 settings/serializers/security.py:151 +#: assets/const/protocol.py:66 settings/serializers/security.py:227 msgid "Security" msgstr "セキュリティ" @@ -1184,33 +1258,77 @@ msgstr "接続に使用するセキュリティ レイヤー" msgid "AD domain" msgstr "AD ドメイン" -#: assets/const/protocol.py:92 assets/models/asset/database.py:10 -#: settings/serializers/email.py:37 +#: assets/const/protocol.py:88 +msgid "Username prompt" +msgstr "ユーザー名プロンプト" + +#: assets/const/protocol.py:89 +msgid "We will send username when we see this prompt" +msgstr "このプロンプトが表示されたらユーザー名を送信します" + +#: assets/const/protocol.py:94 +msgid "Password prompt" +msgstr "パスワードプロンプト" + +#: assets/const/protocol.py:95 +msgid "We will send password when we see this prompt" +msgstr "このプロンプトが表示されたらパスワードを送信します" + +#: assets/const/protocol.py:100 +msgid "Success prompt" +msgstr "成功プロンプト" + +#: assets/const/protocol.py:101 +msgid "We will consider login success when we see this prompt" +msgstr "このプロンプトが表示されたらログイン成功とみなします" + +#: assets/const/protocol.py:112 assets/models/asset/database.py:10 +#: settings/serializers/msg.py:40 msgid "Use SSL" msgstr "SSLの使用" -#: assets/const/protocol.py:149 +#: assets/const/protocol.py:147 +msgid "SYSDBA" +msgstr "SYSDBA" + +#: assets/const/protocol.py:148 +msgid "Connect as SYSDBA" +msgstr "SYSDBA として接続" + +#: assets/const/protocol.py:177 msgid "Auth username" msgstr "ユーザー名で認証する" -#: assets/const/protocol.py:170 assets/models/asset/web.py:9 +#: assets/const/protocol.py:200 +msgid "Safe mode" +msgstr "安全モード" + +#: assets/const/protocol.py:202 +msgid "" +"When safe mode is enabled, some operations will be disabled, such as: New " +"tab, right click, visit other website, etc." +msgstr "" +"安全モードが有効になっている場合、新しいタブ、右クリック、他のウェブサイトへ" +"のアクセスなど、一部の操作が無効になります" + +#: assets/const/protocol.py:207 assets/models/asset/web.py:9 #: assets/serializers/asset/info/spec.py:16 msgid "Autofill" msgstr "自動充填" -#: assets/const/protocol.py:178 assets/models/asset/web.py:10 +#: assets/const/protocol.py:215 assets/models/asset/web.py:10 msgid "Username selector" msgstr "ユーザー名ピッカー" -#: assets/const/protocol.py:183 assets/models/asset/web.py:11 +#: assets/const/protocol.py:220 assets/models/asset/web.py:11 msgid "Password selector" msgstr "パスワードセレクター" -#: assets/const/protocol.py:188 assets/models/asset/web.py:12 +#: assets/const/protocol.py:225 assets/models/asset/web.py:12 msgid "Submit selector" msgstr "ボタンセレクターを確認する" -#: assets/const/protocol.py:211 +#: assets/const/protocol.py:248 msgid "API mode" msgstr "APIモード" @@ -1234,34 +1352,21 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:27 assets/models/cmd_filter.py:40 -#: assets/models/cmd_filter.py:88 assets/models/group.py:20 -#: common/db/models.py:36 ops/models/adhoc.py:27 ops/models/job.py:113 -#: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:248 -#: terminal/models/applet/host.py:139 terminal/models/component/endpoint.py:24 -#: terminal/models/component/endpoint.py:102 -#: terminal/models/session/session.py:46 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:297 users/models/user.py:792 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:111 -msgid "Comment" -msgstr "コメント" - #: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/cmd_filter.py:41 assets/models/group.py:19 -#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:193 -#: users/models/user.py:979 +#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:227 +#: users/models/user.py:1018 msgid "Date created" msgstr "作成された日付" #: assets/models/_user.py:29 assets/models/cmd_filter.py:42 -#: common/db/models.py:35 users/models/user.py:813 +#: common/db/models.py:35 users/models/user.py:847 msgid "Date updated" msgstr "更新日" #: assets/models/_user.py:30 assets/models/cmd_filter.py:44 #: assets/models/cmd_filter.py:91 assets/models/group.py:18 -#: common/db/models.py:32 users/models/user.py:799 +#: common/db/models.py:32 users/models/user.py:833 #: users/serializers/group.py:29 msgid "Created by" msgstr "によって作成された" @@ -1288,8 +1393,8 @@ msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:52 authentication/models/connection_token.py:41 #: authentication/serializers/connect_token_secret.py:111 -#: terminal/models/applet/applet.py:41 terminal/serializers/session.py:18 -#: terminal/serializers/session.py:39 terminal/serializers/storage.py:68 +#: terminal/models/applet/applet.py:41 terminal/serializers/session.py:19 +#: terminal/serializers/session.py:42 terminal/serializers/storage.py:70 msgid "Protocol" msgstr "プロトコル" @@ -1353,14 +1458,13 @@ msgstr "アドレス" #: assets/models/asset/common.py:151 assets/models/platform.py:119 #: authentication/serializers/connect_token_secret.py:115 -#: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:196 +#: perms/serializers/user_permission.py:24 xpack/plugins/cloud/models.py:321 msgid "Platform" msgstr "プラットフォーム" #: assets/models/asset/common.py:153 assets/models/domain.py:21 #: authentication/serializers/connect_token_secret.py:133 -#: perms/serializers/user_permission.py:29 +#: perms/serializers/user_permission.py:29 xpack/plugins/cloud/models.py:323 msgid "Domain" msgstr "ドメイン" @@ -1368,7 +1472,7 @@ msgstr "ドメイン" msgid "Labels" msgstr "ラベル" -#: assets/models/asset/common.py:158 assets/serializers/asset/common.py:310 +#: assets/models/asset/common.py:158 assets/serializers/asset/common.py:312 #: assets/serializers/asset/host.py:11 msgid "Gathered info" msgstr "資産ハードウェア情報の収集" @@ -1377,19 +1481,19 @@ msgstr "資産ハードウェア情報の収集" msgid "Custom info" msgstr "カスタム属性" -#: assets/models/asset/common.py:334 +#: assets/models/asset/common.py:337 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset/common.py:335 +#: assets/models/asset/common.py:338 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset/common.py:336 +#: assets/models/asset/common.py:339 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset/common.py:337 +#: assets/models/asset/common.py:340 msgid "Can change asset nodes" msgstr "資産ノードを変更できます" @@ -1417,7 +1521,7 @@ msgstr "証明書チェックを無視" msgid "Proxy" msgstr "プロキシー" -#: assets/models/automations/base.py:22 ops/models/job.py:189 +#: assets/models/automations/base.py:22 ops/models/job.py:223 #: settings/serializers/auth/sms.py:99 msgid "Parameters" msgstr "パラメータ" @@ -1431,13 +1535,13 @@ msgid "Asset automation task" msgstr "アセットの自動化タスク" #: assets/models/automations/base.py:113 audits/models.py:199 -#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:186 -#: terminal/models/applet/applet.py:247 terminal/models/applet/host.py:136 +#: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:220 +#: terminal/models/applet/applet.py:283 terminal/models/applet/host.py:139 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 -#: terminal/serializers/applet_host.py:107 tickets/models/ticket/general.py:283 +#: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283 #: tickets/serializers/super_ticket.py:13 -#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164 -#: xpack/plugins/cloud/models.py:216 +#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:201 +#: xpack/plugins/cloud/models.py:257 msgid "Status" msgstr "ステータス" @@ -1459,7 +1563,7 @@ msgstr "確認済みの日付" #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61 #: perms/serializers/permission.py:32 users/models/group.py:25 -#: users/models/user.py:759 +#: users/models/user.py:793 msgid "User group" msgstr "ユーザーグループ" @@ -1509,15 +1613,15 @@ msgstr "デフォルト" msgid "Default asset group" msgstr "デフォルトアセットグループ" -#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:964 +#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1003 msgid "System" msgstr "システム" -#: assets/models/label.py:19 assets/models/node.py:545 +#: assets/models/label.py:19 assets/models/node.py:544 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:29 #: authentication/serializers/connect_token_secret.py:122 -#: common/serializers/common.py:86 settings/models.py:34 +#: common/serializers/common.py:86 settings/models.py:33 msgid "Value" msgstr "値" @@ -1526,36 +1630,36 @@ msgstr "値" #: assets/serializers/platform.py:111 #: authentication/serializers/connect_token_secret.py:121 #: common/serializers/common.py:85 perms/serializers/user_permission.py:28 -#: settings/serializers/sms.py:7 +#: settings/serializers/msg.py:83 msgid "Label" msgstr "ラベル" -#: assets/models/node.py:166 +#: assets/models/node.py:165 msgid "New node" msgstr "新しいノード" -#: assets/models/node.py:473 audits/backends/db.py:55 audits/backends/db.py:56 +#: assets/models/node.py:472 audits/backends/db.py:55 audits/backends/db.py:56 msgid "empty" msgstr "空" -#: assets/models/node.py:544 perms/models/perm_node.py:28 +#: assets/models/node.py:543 perms/models/perm_node.py:28 msgid "Key" msgstr "キー" -#: assets/models/node.py:546 assets/serializers/node.py:20 +#: assets/models/node.py:545 assets/serializers/node.py:20 msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:550 perms/models/perm_node.py:30 +#: assets/models/node.py:549 perms/models/perm_node.py:30 msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:559 perms/serializers/permission.py:35 -#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96 +#: assets/models/node.py:558 perms/serializers/permission.py:35 +#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:322 msgid "Node" msgstr "ノード" -#: assets/models/node.py:562 +#: assets/models/node.py:561 msgid "Can match node" msgstr "ノードを一致させることができます" @@ -1572,12 +1676,12 @@ msgid "Public" msgstr "開ける" #: assets/models/platform.py:21 assets/serializers/platform.py:48 -#: settings/serializers/settings.py:67 +#: settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "設定" -#: assets/models/platform.py:38 audits/const.py:48 settings/models.py:37 +#: assets/models/platform.py:38 audits/const.py:48 settings/models.py:36 #: terminal/serializers/applet_host.py:33 msgid "Enabled" msgstr "有効化" @@ -1692,7 +1796,8 @@ msgstr "" #: assets/serializers/asset/common.py:124 assets/serializers/platform.py:129 #: authentication/serializers/connect_token_secret.py:29 #: authentication/serializers/connect_token_secret.py:72 -#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 +#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:324 +#: xpack/plugins/cloud/serializers/task.py:31 msgid "Protocols" msgstr "プロトコル" @@ -1702,19 +1807,19 @@ msgid "Node path" msgstr "ノードパスです" #: assets/serializers/asset/common.py:145 -#: assets/serializers/asset/common.py:311 +#: assets/serializers/asset/common.py:313 msgid "Auto info" msgstr "自動情報" -#: assets/serializers/asset/common.py:234 +#: assets/serializers/asset/common.py:236 msgid "Platform not exist" msgstr "プラットフォームが存在しません" -#: assets/serializers/asset/common.py:270 +#: assets/serializers/asset/common.py:272 msgid "port out of range (0-65535)" msgstr "ポート番号が範囲外です (0-65535)" -#: assets/serializers/asset/common.py:277 +#: assets/serializers/asset/common.py:279 msgid "Protocol is required: {}" msgstr "プロトコルが必要です: {}" @@ -1722,8 +1827,8 @@ msgstr "プロトコルが必要です: {}" msgid "Default database" msgstr "デフォルト・データベース" -#: assets/serializers/asset/database.py:28 common/db/fields.py:579 -#: common/db/fields.py:584 common/serializers/fields.py:104 +#: assets/serializers/asset/database.py:28 common/db/fields.py:570 +#: common/db/fields.py:575 common/serializers/fields.py:104 #: tickets/serializers/ticket/common.py:58 #: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:79 @@ -1805,7 +1910,7 @@ msgstr "制約" msgid "Types" msgstr "タイプ" -#: assets/serializers/gateway.py:23 common/validators.py:35 +#: assets/serializers/gateway.py:23 common/validators.py:34 msgid "This field must be unique." msgstr "このフィールドは一意である必要があります。" @@ -1934,19 +2039,19 @@ msgstr "ノード配下のアセットが接続できるかテストする" msgid "Test gateways connectivity" msgstr "ゲートウェイ接続のテスト。" -#: assets/tasks/utils.py:17 +#: assets/tasks/utils.py:16 msgid "Asset has been disabled, skipped: {}" msgstr "資産が無効化されました。スキップ: {}" -#: assets/tasks/utils.py:21 +#: assets/tasks/utils.py:20 msgid "Asset may not be support ansible, skipped: {}" msgstr "資産はサポートできない場合があります。スキップ: {}" -#: assets/tasks/utils.py:39 +#: assets/tasks/utils.py:38 msgid "For security, do not push user {}" msgstr "セキュリティのために、ユーザー {} をプッシュしないでください" -#: assets/tasks/utils.py:55 +#: assets/tasks/utils.py:54 msgid "No assets matched, stop task" msgstr "一致する資産がない、タスクを停止" @@ -1972,7 +2077,7 @@ msgstr "Rmdir" #: audits/const.py:14 audits/const.py:25 #: authentication/templates/authentication/_access_key_modal.html:65 -#: perms/const.py:17 rbac/tree.py:231 +#: perms/const.py:17 rbac/tree.py:234 msgid "Delete" msgstr "削除" @@ -1996,14 +2101,15 @@ msgstr "ダウンロード" msgid "Rename dir" msgstr "マップディレクトリ" -#: audits/const.py:23 rbac/tree.py:229 +#: audits/const.py:23 rbac/tree.py:232 #: terminal/templates/terminal/_msg_command_warning.html:18 +#: terminal/templates/terminal/_msg_session_sharing.html:10 msgid "View" msgstr "表示" #: audits/const.py:26 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:228 +#: rbac/tree.py:231 msgid "Create" msgstr "作成" @@ -2022,8 +2128,8 @@ msgid "Change password" msgstr "パスワードを変更する" #: audits/const.py:35 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:163 -#: terminal/serializers/session.py:46 terminal/serializers/session.py:55 +#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:164 +#: terminal/serializers/session.py:49 terminal/serializers/session.py:63 msgid "Terminal" msgstr "ターミナル" @@ -2039,8 +2145,8 @@ msgstr "セッションログ" msgid "Login log" msgstr "ログインログ" -#: audits/const.py:43 terminal/models/applet/host.py:140 -#: terminal/models/component/task.py:24 +#: audits/const.py:43 terminal/models/applet/host.py:143 +#: terminal/models/component/task.py:22 msgid "Task" msgstr "タスク" @@ -2061,11 +2167,11 @@ msgid "Job audit log" msgstr "ジョブ監査ログ" #: audits/models.py:51 audits/models.py:95 audits/models.py:166 -#: terminal/models/session/session.py:38 terminal/models/session/sharing.py:99 +#: terminal/models/session/session.py:38 terminal/models/session/sharing.py:109 msgid "Remote addr" msgstr "リモートaddr" -#: audits/models.py:56 audits/serializers.py:33 +#: audits/models.py:56 audits/serializers.py:34 msgid "Operate" msgstr "操作" @@ -2077,9 +2183,9 @@ msgstr "ファイル名" msgid "File" msgstr "書類" -#: audits/models.py:62 terminal/backends/command/models.py:22 -#: terminal/models/session/replay.py:9 terminal/models/session/sharing.py:18 -#: terminal/models/session/sharing.py:81 +#: audits/models.py:62 terminal/backends/command/models.py:21 +#: terminal/models/session/replay.py:9 terminal/models/session/sharing.py:19 +#: terminal/models/session/sharing.py:91 #: terminal/templates/terminal/_msg_command_alert.html:10 #: terminal/templates/terminal/_msg_command_warning.html:17 #: tickets/models/ticket/command_confirm.py:15 @@ -2090,17 +2196,17 @@ msgstr "セッション" msgid "File transfer log" msgstr "ファイル転送ログ" -#: audits/models.py:89 audits/serializers.py:84 +#: audits/models.py:89 audits/serializers.py:85 msgid "Resource Type" msgstr "リソースタイプ" #: audits/models.py:90 audits/models.py:93 audits/models.py:139 -#: audits/serializers.py:83 +#: audits/serializers.py:84 msgid "Resource" msgstr "リソース" #: audits/models.py:96 audits/models.py:142 audits/models.py:168 -#: terminal/serializers/command.py:76 +#: terminal/serializers/command.py:75 msgid "Datetime" msgstr "時間" @@ -2142,13 +2248,13 @@ msgstr "ログインIP" msgid "Login city" msgstr "ログイン都市" -#: audits/models.py:188 audits/serializers.py:63 +#: audits/models.py:188 audits/serializers.py:64 msgid "User agent" msgstr "ユーザーエージェント" -#: audits/models.py:191 audits/serializers.py:47 +#: audits/models.py:191 audits/serializers.py:48 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:65 users/models/user.py:776 +#: users/forms/profile.py:65 users/models/user.py:810 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" @@ -2157,7 +2263,7 @@ msgstr "MFA" msgid "Date login" msgstr "日付ログイン" -#: audits/models.py:203 audits/serializers.py:65 +#: audits/models.py:203 audits/serializers.py:66 msgid "Authentication backend" msgstr "認証バックエンド" @@ -2165,11 +2271,11 @@ msgstr "認証バックエンド" msgid "User login log" msgstr "ユーザーログインログ" -#: audits/serializers.py:64 +#: audits/serializers.py:65 msgid "Reason display" msgstr "理由表示" -#: audits/serializers.py:132 +#: audits/serializers.py:133 #, python-format msgid "User %s %s this resource" msgstr "ユーザー %s %s が現在のリソースをサブスクライブしました。" @@ -2193,7 +2299,7 @@ msgstr "ユーザー %s が現在のリソースでタスク (%s) を実行し msgid "SSH Key" msgstr "SSHキー" -#: audits/signal_handlers/login_log.py:29 settings/serializers/auth/sso.py:10 +#: audits/signal_handlers/login_log.py:29 settings/serializers/auth/sso.py:13 msgid "SSO" msgstr "SSO" @@ -2204,22 +2310,22 @@ msgstr "認証トークン" #: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73 #: authentication/views/login.py:75 authentication/views/wecom.py:159 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 -#: users/models/user.py:706 users/models/user.py:814 +#: users/models/user.py:740 users/models/user.py:848 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:123 +#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:122 #: authentication/views/login.py:87 notifications/backends/__init__.py:14 #: settings/serializers/auth/feishu.py:10 -#: settings/serializers/auth/feishu.py:13 users/models/user.py:708 -#: users/models/user.py:816 +#: settings/serializers/auth/feishu.py:13 users/models/user.py:742 +#: users/models/user.py:850 msgid "FeiShu" msgstr "本を飛ばす" -#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:160 +#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:159 #: authentication/views/login.py:81 notifications/backends/__init__.py:12 -#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:707 -#: users/models/user.py:815 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:741 +#: users/models/user.py:849 msgid "DingTalk" msgstr "DingTalk" @@ -2240,33 +2346,33 @@ msgstr "外部ストレージへのFTPファイルのアップロード" msgid "This action require verify your MFA" msgstr "この操作には、MFAを検証する必要があります" -#: authentication/api/connection_token.py:221 +#: authentication/api/connection_token.py:258 msgid "Reusable connection token is not allowed, global setting not enabled" msgstr "" "再使用可能な接続トークンの使用は許可されていません。グローバル設定は有効に" "なっていません" -#: authentication/api/connection_token.py:301 +#: authentication/api/connection_token.py:338 msgid "Anonymous account is not supported for this asset" msgstr "匿名アカウントはこのプロパティではサポートされていません" -#: authentication/api/connection_token.py:323 +#: authentication/api/connection_token.py:357 msgid "Account not found" msgstr "アカウントが見つかりません" -#: authentication/api/connection_token.py:326 +#: authentication/api/connection_token.py:360 msgid "Permission expired" msgstr "承認の有効期限が切れています" -#: authentication/api/connection_token.py:340 +#: authentication/api/connection_token.py:374 msgid "ACL action is reject: {}({})" msgstr "ACL アクションは拒否です: {}({})" -#: authentication/api/connection_token.py:344 +#: authentication/api/connection_token.py:378 msgid "ACL action is review" msgstr "ACL アクションはレビューです" -#: authentication/api/mfa.py:59 +#: authentication/api/mfa.py:57 msgid "Current user not support mfa type: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" @@ -2287,7 +2393,7 @@ msgstr "" "ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ" "さい。" -#: authentication/api/password.py:60 +#: authentication/api/password.py:64 #: authentication/templates/authentication/login.html:305 #: users/templates/users/forgot_password.html:27 #: users/templates/users/forgot_password.html:28 @@ -2305,56 +2411,56 @@ msgstr "認証" msgid "User invalid, disabled or expired" msgstr "ユーザーが無効、無効、または期限切れです" -#: authentication/backends/drf.py:56 +#: authentication/backends/drf.py:54 msgid "Invalid signature header. No credentials provided." msgstr "署名ヘッダーが無効です。資格情報は提供されていません。" -#: authentication/backends/drf.py:59 +#: authentication/backends/drf.py:57 msgid "Invalid signature header. Signature string should not contain spaces." msgstr "署名ヘッダーが無効です。署名文字列にはスペースを含まないでください。" -#: authentication/backends/drf.py:66 +#: authentication/backends/drf.py:64 msgid "Invalid signature header. Format like AccessKeyId:Signature" msgstr "署名ヘッダーが無効です。AccessKeyIdのような形式: Signature" -#: authentication/backends/drf.py:70 +#: authentication/backends/drf.py:68 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "" "署名ヘッダーが無効です。署名文字列に無効な文字を含めることはできません。" -#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 +#: authentication/backends/drf.py:88 authentication/backends/drf.py:104 msgid "Invalid signature." msgstr "署名が無効です。" -#: authentication/backends/drf.py:97 +#: authentication/backends/drf.py:95 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgstr "HTTP header: Date not provide or not" -#: authentication/backends/drf.py:102 +#: authentication/backends/drf.py:100 msgid "Expired, more than 15 minutes" msgstr "期限切れ、15分以上" -#: authentication/backends/drf.py:109 +#: authentication/backends/drf.py:107 msgid "User disabled." msgstr "ユーザーが無効になりました。" -#: authentication/backends/drf.py:127 +#: authentication/backends/drf.py:125 msgid "Invalid token header. No credentials provided." msgstr "無効なトークンヘッダー。資格情報は提供されていません。" -#: authentication/backends/drf.py:130 +#: authentication/backends/drf.py:128 msgid "Invalid token header. Sign string should not contain spaces." msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。" -#: authentication/backends/drf.py:137 +#: authentication/backends/drf.py:135 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" "無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。" -#: authentication/backends/drf.py:148 +#: authentication/backends/drf.py:146 msgid "Invalid token or cache refreshed." msgstr "無効なトークンまたはキャッシュの更新。" @@ -2503,12 +2609,12 @@ msgstr "企業の微信はすでにバインドされています" msgid "WeCom is not bound" msgstr "企業の微信をバインドしていません" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:210 -#: authentication/views/dingtalk.py:252 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:209 +#: authentication/views/dingtalk.py:251 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:167 +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:166 msgid "FeiShu is not bound" msgstr "本を飛ばすは拘束されていません" @@ -2644,7 +2750,7 @@ msgid "Please change your password" msgstr "パスワードを変更してください" #: authentication/models/connection_token.py:38 -#: terminal/serializers/storage.py:111 +#: terminal/serializers/storage.py:113 msgid "Account name" msgstr "アカウント名" @@ -2666,7 +2772,6 @@ msgid "Connect options" msgstr "接続アイテム" #: authentication/models/connection_token.py:44 -#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" @@ -2681,7 +2786,7 @@ msgstr "再利用可能" #: authentication/models/connection_token.py:47 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:74 #: tickets/models/ticket/apply_application.py:31 -#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:797 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:831 msgid "Date expired" msgstr "期限切れの日付" @@ -2718,11 +2823,11 @@ msgstr "ユーザーなしまたは期限切れのユーザー" msgid "No asset or inactive asset" msgstr "アセットがないか、有効化されていないアセット" -#: authentication/models/connection_token.py:269 +#: authentication/models/connection_token.py:265 msgid "Can view super connection token secret" msgstr "スーパー接続トークンのシークレットを表示できます" -#: authentication/models/connection_token.py:271 +#: authentication/models/connection_token.py:267 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2784,15 +2889,15 @@ msgstr "アクション" #: authentication/serializers/connection_token.py:42 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57 -#: users/serializers/user.py:96 users/serializers/user.py:172 +#: users/serializers/user.py:96 users/serializers/user.py:173 msgid "Is expired" msgstr "期限切れです" #: authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 -#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 -#: settings/serializers/email.py:50 users/forms/profile.py:102 -#: users/forms/profile.py:109 users/models/user.py:755 +#: notifications/backends/__init__.py:10 settings/serializers/msg.py:22 +#: settings/serializers/msg.py:57 users/forms/profile.py:102 +#: users/forms/profile.py:109 users/models/user.py:789 #: users/templates/users/forgot_password.html:117 #: users/views/profile/reset.py:73 msgid "Email" @@ -2830,14 +2935,13 @@ msgid "Show" msgstr "表示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:601 -#: users/serializers/profile.py:116 +#: users/models/user.py:635 users/serializers/profile.py:116 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "無効化" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:602 users/serializers/profile.py:117 +#: users/models/user.py:636 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2882,7 +2986,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:431 +#: jumpserver/conf.py:444 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2940,7 +3044,8 @@ msgid "request new one" msgstr "新しいものを要求する" #: authentication/templates/authentication/_msg_reset_password_code.html:12 -#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:83 +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:93 +#: terminal/templates/terminal/_msg_session_sharing.html:12 #: users/forms/profile.py:107 users/templates/users/forgot_password.html:66 msgid "Verify code" msgstr "コードの確認" @@ -3034,74 +3139,74 @@ msgstr "コピー成功" msgid "LAN" msgstr "ローカルエリアネットワーク" -#: authentication/views/base.py:64 +#: authentication/views/base.py:61 #: perms/templates/perms/_msg_permed_items_expire.html:21 msgid "If you have any question, please contact the administrator" msgstr "質問があったら、管理者に連絡して下さい" -#: authentication/views/dingtalk.py:42 +#: authentication/views/dingtalk.py:41 msgid "DingTalk Error, Please contact your system administrator" msgstr "DingTalkエラー、システム管理者に連絡してください" -#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:209 +#: authentication/views/dingtalk.py:44 authentication/views/dingtalk.py:208 msgid "DingTalk Error" msgstr "DingTalkエラー" -#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:51 +#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:50 #: authentication/views/wecom.py:57 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "システム設定が正しくありません。管理者に連絡してください" -#: authentication/views/dingtalk.py:61 +#: authentication/views/dingtalk.py:60 msgid "DingTalk is already bound" msgstr "DingTalkはすでにバインドされています" -#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:129 +#: authentication/views/dingtalk.py:128 authentication/views/wecom.py:129 msgid "Invalid user_id" msgstr "無効なuser_id" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:144 msgid "DingTalk query user failed" msgstr "DingTalkクエリユーザーが失敗しました" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:153 msgid "The DingTalk is already bound to another user" msgstr "DingTalkはすでに別のユーザーにバインドされています" -#: authentication/views/dingtalk.py:161 +#: authentication/views/dingtalk.py:160 msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:211 authentication/views/dingtalk.py:246 +#: authentication/views/dingtalk.py:210 authentication/views/dingtalk.py:245 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:253 +#: authentication/views/dingtalk.py:252 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" -#: authentication/views/feishu.py:39 authentication/views/feishu.py:166 +#: authentication/views/feishu.py:38 authentication/views/feishu.py:165 msgid "FeiShu Error" msgstr "FeiShuエラー" -#: authentication/views/feishu.py:67 +#: authentication/views/feishu.py:66 msgid "FeiShu is already bound" msgstr "FeiShuはすでにバインドされています" -#: authentication/views/feishu.py:108 +#: authentication/views/feishu.py:107 msgid "FeiShu query user failed" msgstr "FeiShuクエリユーザーが失敗しました" -#: authentication/views/feishu.py:117 +#: authentication/views/feishu.py:116 msgid "The FeiShu is already bound to another user" msgstr "FeiShuはすでに別のユーザーにバインドされています" -#: authentication/views/feishu.py:124 +#: authentication/views/feishu.py:123 msgid "Binding FeiShu successfully" msgstr "本を飛ばすのバインドに成功" -#: authentication/views/feishu.py:168 +#: authentication/views/feishu.py:167 msgid "Failed to get user from FeiShu" msgstr "本を飛ばすからユーザーを取得できませんでした" @@ -3190,7 +3295,7 @@ msgstr "の準備を" msgid "Pending" msgstr "未定" -#: common/const/choices.py:17 ops/const.py:55 +#: common/const/choices.py:17 ops/const.py:58 msgid "Running" msgstr "ランニング" @@ -3209,8 +3314,8 @@ msgid "%(name)s was updated successfully" msgstr "%(name)s は正常に更新されました" #: common/db/encoder.py:11 -msgid "ugettext_lazy" -msgstr "ugettext_lazy" +msgid "gettext_lazy" +msgstr "gettext_lazy" #: common/db/fields.py:106 msgid "Marshal dict data to char field" @@ -3236,11 +3341,11 @@ msgstr "チャーフィールドへのマーシャルデータ" msgid "Marshal data to text field" msgstr "テキストフィールドへのマーシャルデータ" -#: common/db/fields.py:176 +#: common/db/fields.py:167 msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" -#: common/db/fields.py:567 +#: common/db/fields.py:558 msgid "" "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " @@ -3250,19 +3355,19 @@ msgstr "" "{'type':'ids','ids':[]}或 #タイプ:属性、属性:[#名前:ip、照合:正確、" "値:1.1.1.1}" -#: common/db/fields.py:574 +#: common/db/fields.py:565 msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgstr "無効なタイプです。all、ids、またはattrsでなければなりません" -#: common/db/fields.py:577 +#: common/db/fields.py:568 msgid "Invalid ids for ids, should be a list" msgstr "無効なID、リストでなければなりません" -#: common/db/fields.py:582 common/db/fields.py:587 +#: common/db/fields.py:573 common/db/fields.py:578 msgid "Invalid attrs, should be a list of dict" msgstr "無効な属性、dictリストでなければなりません" -#: common/db/fields.py:589 +#: common/db/fields.py:580 msgid "Invalid attrs, should be has name and value" msgstr "名前と値が必要な無効な属性" @@ -3274,7 +3379,7 @@ msgstr "は破棄されます" msgid "discard time" msgstr "時間を捨てる" -#: common/db/models.py:33 users/models/user.py:800 +#: common/db/models.py:33 users/models/user.py:834 msgid "Updated by" msgstr "によって更新" @@ -3339,11 +3444,11 @@ msgstr "このアクションでは、MFAの確認が必要です。" msgid "Unexpect error occur" msgstr "予期しないエラーが発生します" -#: common/plugins/es.py:28 +#: common/plugins/es.py:31 msgid "Invalid elasticsearch config" msgstr "無効なElasticsearch構成" -#: common/plugins/es.py:33 +#: common/plugins/es.py:36 msgid "Not Support Elasticsearch8" msgstr "サポートされていません Elasticsearch8" @@ -3359,11 +3464,11 @@ msgstr "企業微信エラー、システム管理者に連絡してください msgid "Signature does not match" msgstr "署名が一致しない" -#: common/sdk/sms/cmpp2.py:46 +#: common/sdk/sms/cmpp2.py:44 msgid "sp_id is 6 bits" msgstr "SP idは6ビット" -#: common/sdk/sms/cmpp2.py:216 +#: common/sdk/sms/cmpp2.py:214 msgid "Failed to connect to the CMPP gateway server, err: {}" msgstr "接続ゲートウェイサーバエラー, 非: {}" @@ -3446,15 +3551,15 @@ msgstr "無効なアドレス。" msgid "Hello %s" msgstr "こんにちは %s" -#: common/validators.py:17 +#: common/validators.py:16 msgid "Special char not allowed" msgstr "特別なcharは許可されていません" -#: common/validators.py:43 +#: common/validators.py:42 msgid "Should not contains special characters" msgstr "特殊文字を含むべきではない" -#: common/validators.py:48 +#: common/validators.py:47 msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" @@ -3476,11 +3581,11 @@ msgstr "検索のエクスポート: %s" msgid "User %s view/export secret" msgstr "ユーザー %s がパスワードを閲覧/導き出しました" -#: jumpserver/conf.py:430 +#: jumpserver/conf.py:443 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:432 +#: jumpserver/conf.py:445 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -3488,7 +3593,7 @@ msgstr "アカウントが正常に作成されました" msgid "JumpServer Open Source Bastion Host" msgstr "JumpServer オープンソースの要塞ホスト" -#: jumpserver/views/celery_flower.py:23 +#: jumpserver/views/celery_flower.py:22 msgid "

Flower service unavailable, check it

" msgstr "

フラワーサービス利用不可、チェック

" @@ -3548,15 +3653,15 @@ msgstr "システムメッセージ" msgid "Publish the station message" msgstr "投稿サイトニュース" -#: ops/ansible/inventory.py:82 +#: ops/ansible/inventory.py:92 ops/models/job.py:60 msgid "No account available" msgstr "利用可能なアカウントがありません" -#: ops/ansible/inventory.py:247 +#: ops/ansible/inventory.py:260 msgid "Ansible disabled" msgstr "Ansible 無効" -#: ops/ansible/inventory.py:263 +#: ops/ansible/inventory.py:276 msgid "Skip hosts below:" msgstr "次のホストをスキップします: " @@ -3616,11 +3721,11 @@ msgstr "空欄" msgid "VCS" msgstr "VCS" -#: ops/const.py:38 ops/models/adhoc.py:45 +#: ops/const.py:38 ops/models/adhoc.py:44 msgid "Adhoc" msgstr "コマンド#コマンド#" -#: ops/const.py:39 ops/models/job.py:103 +#: ops/const.py:39 ops/models/job.py:135 msgid "Playbook" msgstr "Playbook" @@ -3640,7 +3745,19 @@ msgstr "PowerShell" msgid "Python" msgstr "Python" -#: ops/const.py:57 +#: ops/const.py:52 +msgid "MySQL" +msgstr "MySQL" + +#: ops/const.py:53 +msgid "PostgreSQL" +msgstr "PostgreSQL" + +#: ops/const.py:54 +msgid "SQLServer" +msgstr "SQLServer" + +#: ops/const.py:60 msgid "Timeout" msgstr "タイムアウト" @@ -3673,22 +3790,22 @@ msgstr "{} から {} までの範囲" msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" -#: ops/models/adhoc.py:22 +#: ops/models/adhoc.py:21 msgid "Pattern" msgstr "パターン" -#: ops/models/adhoc.py:24 ops/models/job.py:98 +#: ops/models/adhoc.py:23 ops/models/job.py:130 msgid "Module" msgstr "モジュール" -#: ops/models/adhoc.py:25 ops/models/celery.py:58 ops/models/job.py:97 -#: terminal/models/component/task.py:16 +#: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:129 +#: terminal/models/component/task.py:14 msgid "Args" msgstr "アルグ" -#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:106 ops/models/job.py:192 ops/models/playbook.py:25 -#: terminal/models/session/sharing.py:23 +#: ops/models/adhoc.py:25 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:138 ops/models/job.py:226 ops/models/playbook.py:30 +#: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "作成者" @@ -3704,12 +3821,12 @@ msgstr "最後の実行" msgid "Date last run" msgstr "最終実行日" -#: ops/models/base.py:51 ops/models/job.py:190 -#: xpack/plugins/cloud/models.py:162 +#: ops/models/base.py:51 ops/models/job.py:224 +#: xpack/plugins/cloud/models.py:199 msgid "Result" msgstr "結果" -#: ops/models/base.py:52 ops/models/job.py:191 +#: ops/models/base.py:52 ops/models/job.py:225 msgid "Summary" msgstr "概要" @@ -3725,11 +3842,11 @@ msgstr "Celery タスク#タスク#" msgid "Can view task monitor" msgstr "タスクモニターを表示できます" -#: ops/models/celery.py:59 terminal/models/component/task.py:17 +#: ops/models/celery.py:59 terminal/models/component/task.py:15 msgid "Kwargs" msgstr "クワーグ" -#: ops/models/celery.py:61 terminal/models/session/sharing.py:114 +#: ops/models/celery.py:61 terminal/models/session/sharing.py:124 #: tickets/const.py:25 msgid "Finished" msgstr "終了" @@ -3742,51 +3859,51 @@ msgstr "発売日" msgid "Celery Task Execution" msgstr "Celery タスク実行" -#: ops/models/job.py:100 +#: ops/models/job.py:132 msgid "Chdir" msgstr "実行ディレクトリ" -#: ops/models/job.py:101 +#: ops/models/job.py:133 msgid "Timeout (Seconds)" msgstr "タイムアウト(秒)" -#: ops/models/job.py:108 +#: ops/models/job.py:140 msgid "Use Parameter Define" msgstr "パラメータ定義を使用する" -#: ops/models/job.py:109 +#: ops/models/job.py:141 msgid "Parameters define" msgstr "パラメータ定義" -#: ops/models/job.py:110 +#: ops/models/job.py:142 msgid "Runas" msgstr "ユーザーとして実行" -#: ops/models/job.py:112 +#: ops/models/job.py:144 msgid "Runas policy" msgstr "ユーザー ポリシー" -#: ops/models/job.py:174 +#: ops/models/job.py:208 msgid "Job" msgstr "ジョブ#ジョブ#" -#: ops/models/job.py:197 +#: ops/models/job.py:231 msgid "Material" msgstr "Material" -#: ops/models/job.py:199 +#: ops/models/job.py:233 msgid "Material Type" msgstr "Material を選択してオプションを設定します。" -#: ops/models/job.py:480 +#: ops/models/job.py:533 msgid "Job Execution" msgstr "ジョブ実行" -#: ops/models/playbook.py:28 +#: ops/models/playbook.py:33 msgid "CreateMethod" msgstr "创建方式" -#: ops/models/playbook.py:29 +#: ops/models/playbook.py:34 msgid "VCS URL" msgstr "VCS URL" @@ -3826,7 +3943,7 @@ msgstr "保存後に実行" msgid "Job type" msgstr "タスクの種類" -#: ops/serializers/job.py:57 terminal/serializers/session.py:47 +#: ops/serializers/job.py:57 terminal/serializers/session.py:50 msgid "Is finished" msgstr "終了しました" @@ -3862,7 +3979,7 @@ msgstr "例外ジョブのクリーンアップ" msgid "Task log" msgstr "タスクログ" -#: ops/templates/ops/celery_task_log.html:71 +#: ops/templates/ops/celery_task_log.html:71 terminal/serializers/task.py:10 msgid "Task name" msgstr "タスク名" @@ -3917,46 +4034,51 @@ msgstr "" msgid "The organization have resource ({}) cannot be deleted" msgstr "組織のリソース ({}) は削除できません" -#: orgs/apps.py:7 rbac/tree.py:119 +#: orgs/apps.py:7 rbac/tree.py:122 msgid "App organizations" msgstr "アプリ組織" -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:89 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91 #: rbac/const.py:7 rbac/models/rolebinding.py:56 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 +#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/ldap.py:63 #: terminal/templates/terminal/_msg_command_warning.html:21 +#: terminal/templates/terminal/_msg_session_sharing.html:14 #: tickets/models/ticket/general.py:302 tickets/serializers/ticket/ticket.py:60 msgid "Organization" msgstr "組織" -#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:27 msgid "Org name" msgstr "組織名" -#: orgs/models.py:13 +#: orgs/models.py:14 msgid "GLOBAL" msgstr "グローバル組織" -#: orgs/models.py:15 +#: orgs/models.py:16 msgid "DEFAULT" msgstr "デフォルト組織" -#: orgs/models.py:17 +#: orgs/models.py:18 msgid "SYSTEM" msgstr "システム組織" -#: orgs/models.py:81 rbac/models/role.py:36 terminal/models/applet/applet.py:40 +#: orgs/models.py:83 rbac/models/role.py:36 terminal/models/applet/applet.py:40 msgid "Builtin" msgstr "ビルトイン" -#: orgs/models.py:91 +#: orgs/models.py:93 msgid "Can view root org" msgstr "グローバル組織を表示できます" -#: orgs/models.py:92 +#: orgs/models.py:94 msgid "Can view all joined org" msgstr "参加しているすべての組織を表示できます" +#: orgs/models.py:233 +msgid "Can not delete virtual org" +msgstr "" + #: orgs/tasks.py:9 msgid "Refresh organization cache" msgstr "組織キャッシュを更新する" @@ -4069,7 +4191,7 @@ msgstr "ロールはユーザーにバインドされており、破壊するこ msgid "Internal role, can't be update" msgstr "内部ロール、更新できません" -#: rbac/api/rolebinding.py:52 +#: rbac/api/rolebinding.py:45 msgid "{} at least one system role" msgstr "{} 少なくとも1つのシステムロール" @@ -4125,7 +4247,7 @@ msgstr "Webターミナルを表示できます" msgid "Can view file manager" msgstr "ファイルマネージャを表示できます" -#: rbac/models/permission.py:26 rbac/models/role.py:34 +#: rbac/models/permission.py:27 rbac/models/role.py:34 msgid "Permissions" msgstr "権限" @@ -4135,7 +4257,7 @@ msgid "Scope" msgstr "スコープ" #: rbac/models/role.py:46 rbac/models/rolebinding.py:52 -#: users/models/user.py:763 +#: users/models/user.py:797 msgid "Role" msgstr "ロール" @@ -4151,26 +4273,26 @@ msgstr "組織の役割" msgid "Role binding" msgstr "ロールバインディング" -#: rbac/models/rolebinding.py:153 +#: rbac/models/rolebinding.py:161 msgid "All organizations" msgstr "全ての組織" -#: rbac/models/rolebinding.py:182 +#: rbac/models/rolebinding.py:190 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "" "ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" -#: rbac/models/rolebinding.py:189 +#: rbac/models/rolebinding.py:197 msgid "Organization role binding" msgstr "組織の役割バインディング" -#: rbac/models/rolebinding.py:204 +#: rbac/models/rolebinding.py:212 msgid "System role binding" msgstr "システムロールバインディング" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 +#: rbac/serializers/permission.py:25 users/serializers/profile.py:132 msgid "Perms" msgstr "パーマ" @@ -4182,11 +4304,7 @@ msgstr "ユーザー数" msgid "Display name" msgstr "表示名" -#: rbac/serializers/rolebinding.py:22 -msgid "Role display" -msgstr "ロール表示" - -#: rbac/serializers/rolebinding.py:56 +#: rbac/serializers/rolebinding.py:60 msgid "Has bound this role" msgstr "この役割をバインドしました" @@ -4206,7 +4324,7 @@ msgstr "ワークスペースビュー" msgid "Audit view" msgstr "監査ビュー" -#: rbac/tree.py:27 settings/models.py:159 +#: rbac/tree.py:27 settings/models.py:158 msgid "System setting" msgstr "システム設定" @@ -4243,29 +4361,30 @@ msgid "My assets" msgstr "私の資産" #: rbac/tree.py:56 terminal/models/applet/applet.py:51 -#: terminal/models/applet/applet.py:244 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:280 terminal/models/applet/host.py:29 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "リモートアプリケーション" -#: rbac/tree.py:120 +#: rbac/tree.py:123 msgid "Ticket comment" msgstr "チケットコメント" -#: rbac/tree.py:121 tickets/models/ticket/general.py:307 +#: rbac/tree.py:124 settings/serializers/feature.py:58 +#: tickets/models/ticket/general.py:307 msgid "Ticket" msgstr "チケット" -#: rbac/tree.py:122 +#: rbac/tree.py:125 msgid "Common setting" msgstr "共通設定" -#: rbac/tree.py:123 +#: rbac/tree.py:126 msgid "View permission tree" msgstr "権限ツリーの表示" #: settings/api/dingtalk.py:31 settings/api/feishu.py:36 -#: settings/api/sms.py:155 settings/api/wecom.py:37 +#: settings/api/sms.py:153 settings/api/vault.py:40 settings/api/wecom.py:37 msgid "Test success" msgstr "テストの成功" @@ -4293,11 +4412,11 @@ msgstr "Ldapユーザーを取得するにはNone" msgid "Imported {} users successfully (Organization: {})" msgstr "{} 人のユーザーを正常にインポートしました (組織: {})" -#: settings/api/sms.py:137 +#: settings/api/sms.py:135 msgid "Invalid SMS platform" msgstr "無効なショートメッセージプラットフォーム" -#: settings/api/sms.py:143 +#: settings/api/sms.py:141 msgid "test_phone is required" msgstr "携帯番号をテストこのフィールドは必須です" @@ -4305,18 +4424,22 @@ msgstr "携帯番号をテストこのフィールドは必須です" msgid "Settings" msgstr "設定" -#: settings/models.py:36 +#: settings/models.py:35 msgid "Encrypted" msgstr "暗号化された" -#: settings/models.py:161 +#: settings/models.py:160 msgid "Can change email setting" msgstr "メール設定を変更できます" -#: settings/models.py:162 +#: settings/models.py:161 msgid "Can change auth setting" msgstr "資格認定の設定" +#: settings/models.py:162 +msgid "Can change vault setting" +msgstr "金庫の設定を変えることができます" + #: settings/models.py:163 msgid "Can change system msg sub setting" msgstr "システムmsgサブ设定を変更できます" @@ -4350,42 +4473,50 @@ msgid "Can change other setting" msgstr "他の設定を変えることができます" #: settings/serializers/auth/base.py:12 +msgid "LDAP Auth" +msgstr "LDAP 認証" + +#: settings/serializers/auth/base.py:13 msgid "CAS Auth" msgstr "CAS 認証" -#: settings/serializers/auth/base.py:13 +#: settings/serializers/auth/base.py:14 msgid "OPENID Auth" msgstr "OPENID 認証" -#: settings/serializers/auth/base.py:14 -msgid "RADIUS Auth" -msgstr "RADIUS 認証" - #: settings/serializers/auth/base.py:15 -msgid "DingTalk Auth" -msgstr "くぎ 認証" - -#: settings/serializers/auth/base.py:16 -msgid "FeiShu Auth" -msgstr "飛本 認証" - -#: settings/serializers/auth/base.py:17 -msgid "WeCom Auth" -msgstr "企業微信 認証" - -#: settings/serializers/auth/base.py:18 -msgid "SSO Auth" -msgstr "SSO Token 認証" - -#: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" msgstr "SAML2 認証" -#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 +#: settings/serializers/auth/base.py:16 +msgid "OAuth2 Auth" +msgstr "OAuth2 認証" + +#: settings/serializers/auth/base.py:17 +msgid "RADIUS Auth" +msgstr "RADIUS 認証" + +#: settings/serializers/auth/base.py:18 +msgid "DingTalk Auth" +msgstr "くぎ 認証" + +#: settings/serializers/auth/base.py:19 +msgid "FeiShu Auth" +msgstr "飛本 認証" + +#: settings/serializers/auth/base.py:20 +msgid "WeCom Auth" +msgstr "企業微信 認証" + +#: settings/serializers/auth/base.py:21 +msgid "SSO Auth" +msgstr "SSO Token 認証" + +#: settings/serializers/auth/base.py:24 msgid "Forgot password url" msgstr "パスワードのURLを忘れた" -#: settings/serializers/auth/base.py:28 +#: settings/serializers/auth/base.py:30 msgid "Enable login redirect msg" msgstr "ログインリダイレクトの有効化msg" @@ -4406,7 +4537,7 @@ msgid "Proxy server url" msgstr "コールバックアドレス" #: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:54 -#: settings/serializers/auth/saml2.py:34 +#: settings/serializers/auth/saml2.py:33 msgid "Logout completely" msgstr "同期ログアウト" @@ -4418,7 +4549,7 @@ msgstr "ユーザー名のプロパティ" msgid "Enable attributes map" msgstr "属性マップの有効化" -#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:32 msgid "Rename attr" msgstr "マッピングのプロパティ" @@ -4538,7 +4669,7 @@ msgid "Provider end session endpoint" msgstr "プロバイダーのセッション終了エンドポイント" #: settings/serializers/auth/oauth2.py:59 settings/serializers/auth/oidc.py:98 -#: settings/serializers/auth/saml2.py:35 +#: settings/serializers/auth/saml2.py:34 msgid "Always update user" msgstr "常にユーザーを更新" @@ -4634,31 +4765,31 @@ msgstr "Radius認証の有効化" msgid "OTP in Radius" msgstr "Radius のOTP" -#: settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/saml2.py:10 msgid "SAML2" msgstr "SAML2" -#: settings/serializers/auth/saml2.py:14 +#: settings/serializers/auth/saml2.py:13 msgid "Enable SAML2 Auth" msgstr "SAML2認証の有効化" -#: settings/serializers/auth/saml2.py:17 +#: settings/serializers/auth/saml2.py:16 msgid "IDP metadata URL" msgstr "IDP metadata アドレス" -#: settings/serializers/auth/saml2.py:20 +#: settings/serializers/auth/saml2.py:19 msgid "IDP metadata XML" msgstr "IDP metadata XML" -#: settings/serializers/auth/saml2.py:23 +#: settings/serializers/auth/saml2.py:22 msgid "SP advanced settings" msgstr "詳細設定" -#: settings/serializers/auth/saml2.py:27 +#: settings/serializers/auth/saml2.py:26 msgid "SP private key" msgstr "SP プライベートキー" -#: settings/serializers/auth/saml2.py:31 +#: settings/serializers/auth/saml2.py:30 msgid "SP cert" msgstr "SP 証明書" @@ -4672,7 +4803,7 @@ msgstr "SMSプロバイダ / プロトコル" #: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:45 #: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 -#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:69 +#: settings/serializers/auth/sms.py:73 settings/serializers/msg.py:76 msgid "Signature" msgstr "署名" @@ -4742,20 +4873,20 @@ msgstr "請求方法です" msgid "The value in the parameter must contain %s" msgstr "パラメータの値には必ず %s が含まれます" -#: settings/serializers/auth/sso.py:13 +#: settings/serializers/auth/sso.py:16 msgid "Enable SSO auth" msgstr "SSO Token認証の有効化" -#: settings/serializers/auth/sso.py:14 +#: settings/serializers/auth/sso.py:17 msgid "Other service can using SSO token login to JumpServer without password" msgstr "" "他のサービスはパスワードなしでJumpServerへのSSOトークンログインを使用できます" -#: settings/serializers/auth/sso.py:17 +#: settings/serializers/auth/sso.py:20 msgid "SSO auth key TTL" msgstr "Token有効期間" -#: settings/serializers/auth/sso.py:17 +#: settings/serializers/auth/sso.py:20 #: xpack/plugins/cloud/serializers/account_attrs.py:193 msgid "Unit: second" msgstr "単位: 秒" @@ -4765,56 +4896,46 @@ msgid "Enable WeCom Auth" msgstr "企業微信認証の有効化" #: settings/serializers/basic.py:9 -msgid "Subject" -msgstr "件名" - -#: settings/serializers/basic.py:13 -msgid "More url" -msgstr "もっとURL" - -#: settings/serializers/basic.py:30 msgid "Site url" msgstr "サイトURL" -#: settings/serializers/basic.py:31 -msgid "eg: http://dev.jumpserver.org:8080" -msgstr "例えば: http://dev.jumpserver.org:8080" +#: settings/serializers/basic.py:10 +msgid "" +"Email links or other system callbacks are used to access it, eg: http://dev." +"jumpserver.org:8080" +msgstr "" -#: settings/serializers/basic.py:34 +#: settings/serializers/basic.py:13 msgid "User guide url" msgstr "ユーザーガイドurl" -#: settings/serializers/basic.py:35 +#: settings/serializers/basic.py:14 msgid "User first login update profile done redirect to it" msgstr "ユーザーの最初のログイン更新プロファイルがリダイレクトされました" -#: settings/serializers/basic.py:39 -msgid "" -"The forgot password url on login page, If you use ldap or cas external " -"authentication, you can set it" -msgstr "" -"ログインページでパスワードのURLを忘れてしまいました。ldapまたはcasの外部認証" -"を使用している場合は、設定できます。" - -#: settings/serializers/basic.py:43 +#: settings/serializers/basic.py:17 msgid "Global organization name" msgstr "グローバル組織名" -#: settings/serializers/basic.py:44 +#: settings/serializers/basic.py:18 msgid "The name of global organization to display" msgstr "表示するグローバル組織の名前" -#: settings/serializers/basic.py:46 -msgid "Enable announcement" -msgstr "アナウンスの有効化" +#: settings/serializers/basic.py:21 +msgid "Help Docs URL" +msgstr "ドキュメントリンク" -#: settings/serializers/basic.py:47 -msgid "Announcement" -msgstr "発表" +#: settings/serializers/basic.py:22 +msgid "default: http://docs.jumpserver.org" +msgstr "デフォルト: http://docs.jumpserver.org" -#: settings/serializers/basic.py:48 -msgid "Enable tickets" -msgstr "チケットを有効にする" +#: settings/serializers/basic.py:25 +msgid "Help Support URL" +msgstr "サポートリンク" + +#: settings/serializers/basic.py:26 +msgid "default: http://www.jumpserver.org/support/" +msgstr "デフォルト: http://www.jumpserver.org/support/" #: settings/serializers/cleaning.py:8 msgid "Period clean" @@ -4844,7 +4965,7 @@ msgstr "クラウド同期レコードは日数を保持します(天)" msgid "Session keep duration (day)" msgstr "セッション維持期間(天)" -#: settings/serializers/cleaning.py:32 +#: settings/serializers/cleaning.py:33 msgid "" "Session, record, command will be delete if more than duration, only in " "database, OSS will not be affected." @@ -4852,69 +4973,142 @@ msgstr "" "この期間を超えるセッション、録音、およびコマンド レコードは削除されます (デー" "タベースのバックアップに影響し、OSS などには影響しません)" -#: settings/serializers/cleaning.py:36 +#: settings/serializers/cleaning.py:37 msgid "Activity log keep days (day)" msgstr "活動ログは日数を保持します(天)" -#: settings/serializers/email.py:21 +#: settings/serializers/feature.py:16 +msgid "Subject" +msgstr "件名" + +#: settings/serializers/feature.py:20 +msgid "More url" +msgstr "もっとURL" + +#: settings/serializers/feature.py:34 settings/serializers/feature.py:37 +msgid "Announcement" +msgstr "発表" + +#: settings/serializers/feature.py:36 +msgid "Enable announcement" +msgstr "アナウンスの有効化" + +#: settings/serializers/feature.py:44 +msgid "Enable Vault" +msgstr "有効化 Vault" + +#: settings/serializers/feature.py:53 +msgid "Mount Point" +msgstr "マウントポイント" + +#: settings/serializers/feature.py:60 +msgid "Enable tickets" +msgstr "チケットを有効にする" + +#: settings/serializers/feature.py:63 +msgid "Ticket authorize default time" +msgstr "デフォルト製造オーダ承認時間" + +#: settings/serializers/feature.py:66 +msgid "day" +msgstr "日" + +#: settings/serializers/feature.py:66 +msgid "hour" +msgstr "時" + +#: settings/serializers/feature.py:67 +msgid "Ticket authorize default time unit" +msgstr "デフォルト製造オーダ承認時間単位" + +#: settings/serializers/feature.py:72 +msgid "Feature" +msgstr "機能" + +#: settings/serializers/feature.py:75 +msgid "Operation center" +msgstr "職業センター" + +#: settings/serializers/feature.py:76 +msgid "Allow user run batch command or not using ansible" +msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" + +#: settings/serializers/feature.py:80 +msgid "Operation center command blacklist" +msgstr "オペレーション センター コマンド ブラックリスト" + +#: settings/serializers/feature.py:81 +msgid "Commands that are not allowed execute." +msgstr "実行が許可されていないコマンド" + +#: settings/serializers/msg.py:24 msgid "SMTP host" msgstr "SMTPホスト" -#: settings/serializers/email.py:22 +#: settings/serializers/msg.py:25 msgid "SMTP port" msgstr "SMTPポート" -#: settings/serializers/email.py:23 +#: settings/serializers/msg.py:26 msgid "SMTP account" msgstr "SMTPアカウント" -#: settings/serializers/email.py:25 +#: settings/serializers/msg.py:28 msgid "SMTP password" msgstr "SMTPパスワード" -#: settings/serializers/email.py:26 +#: settings/serializers/msg.py:29 msgid "Tips: Some provider use token except password" msgstr "ヒント: 一部のプロバイダーはパスワード以外のトークンを使用します" -#: settings/serializers/email.py:29 +#: settings/serializers/msg.py:32 msgid "Send user" msgstr "ユーザーを送信" -#: settings/serializers/email.py:30 +#: settings/serializers/msg.py:33 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "" "ヒント: 送信メールアカウント、送信アカウントとしてのデフォルトのSMTPアカウン" "ト" -#: settings/serializers/email.py:33 +#: settings/serializers/msg.py:36 msgid "Test recipient" msgstr "テスト受信者" -#: settings/serializers/email.py:34 +#: settings/serializers/msg.py:37 msgid "Tips: Used only as a test mail recipient" msgstr "ヒント: テストメールの受信者としてのみ使用" -#: settings/serializers/email.py:38 +#: settings/serializers/msg.py:41 msgid "If SMTP port is 465, may be select" msgstr "SMTPポートが465の場合は、" -#: settings/serializers/email.py:41 +#: settings/serializers/msg.py:44 msgid "Use TLS" msgstr "TLSの使用" -#: settings/serializers/email.py:42 +#: settings/serializers/msg.py:45 msgid "If SMTP port is 587, may be select" msgstr "SMTPポートが587の場合は、" -#: settings/serializers/email.py:45 +#: settings/serializers/msg.py:48 msgid "Subject prefix" msgstr "件名プレフィックス" -#: settings/serializers/email.py:54 +#: settings/serializers/msg.py:51 +msgid "Email suffix" +msgstr "メールのサフィックス" + +#: settings/serializers/msg.py:52 +msgid "" +"This is used by default if no email is returned during SSO authentication" +msgstr "これは、SSO認証中にメールが返されない場合にデフォルトで使用されます。" + +#: settings/serializers/msg.py:61 msgid "Create user email subject" msgstr "ユーザーメール件名の作成" -#: settings/serializers/email.py:55 +#: settings/serializers/msg.py:62 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" @@ -4922,20 +5116,20 @@ msgstr "" "ヒント: ユーザーを作成するときに、メールの件名を送信します (例: アカウントを" "正常に作成)" -#: settings/serializers/email.py:59 +#: settings/serializers/msg.py:66 msgid "Create user honorific" msgstr "ユーザー敬語の作成" -#: settings/serializers/email.py:60 +#: settings/serializers/msg.py:67 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "" "ヒント: ユーザーを作成するときは、メールの敬語を送信します (例: こんにちは)" -#: settings/serializers/email.py:64 +#: settings/serializers/msg.py:71 msgid "Create user email content" msgstr "ユーザーのメールコンテンツを作成する" -#: settings/serializers/email.py:66 +#: settings/serializers/msg.py:73 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -4944,155 +5138,30 @@ msgstr "" "ヒント:ユーザーの作成時にパスワード設定メールの内容を送信し、{username}{name}" "{email}ラベルをサポートします。" -#: settings/serializers/email.py:70 +#: settings/serializers/msg.py:77 msgid "Tips: Email signature (eg:jumpserver)" msgstr "ヒント: メール署名 (例: jumpserver)" -#: settings/serializers/other.py:6 +#: settings/serializers/other.py:8 msgid "More..." msgstr "詳細..." -#: settings/serializers/other.py:9 -msgid "Email suffix" -msgstr "メールのサフィックス" - -#: settings/serializers/other.py:10 -msgid "" -"This is used by default if no email is returned during SSO authentication" -msgstr "これは、SSO認証中にメールが返されない場合にデフォルトで使用されます。" - -#: settings/serializers/other.py:14 -msgid "OTP issuer name" -msgstr "OTP発行者名" - -#: settings/serializers/other.py:18 -msgid "OTP valid window" -msgstr "OTP有効なウィンドウ" - -#: settings/serializers/other.py:22 +#: settings/serializers/other.py:11 msgid "Perm ungroup node" msgstr "グループ化されていないノードを表示" -#: settings/serializers/other.py:23 +#: settings/serializers/other.py:12 msgid "Perm single to ungroup node" msgstr "" "グループ化されていないノードに個別に許可された資産を配置し、資産が存在する" "ノードが表示されないようにしますが、そのノードが許可されていないという質問に" "質問" -#: settings/serializers/other.py:28 -msgid "Ticket authorize default time" -msgstr "デフォルト製造オーダ承認時間" - -#: settings/serializers/other.py:31 -msgid "day" -msgstr "日" - -#: settings/serializers/other.py:31 -msgid "hour" -msgstr "時" - -#: settings/serializers/other.py:32 -msgid "Ticket authorize default time unit" -msgstr "デフォルト製造オーダ承認時間単位" - -#: settings/serializers/other.py:35 -msgid "Help Docs URL" -msgstr "ドキュメントリンク" - -#: settings/serializers/other.py:36 -msgid "default: http://docs.jumpserver.org" -msgstr "デフォルト: http://docs.jumpserver.org" - -#: settings/serializers/other.py:40 -msgid "Help Support URL" -msgstr "サポートリンク" - -#: settings/serializers/other.py:41 -msgid "default: http://www.jumpserver.org/support/" -msgstr "デフォルト: http://www.jumpserver.org/support/" - -#: settings/serializers/security.py:10 -msgid "Password minimum length" -msgstr "パスワードの最小長" - -#: settings/serializers/security.py:14 -msgid "Admin user password minimum length" -msgstr "管理者ユーザーパスワードの最小長" - -#: settings/serializers/security.py:17 -msgid "Must contain capital" -msgstr "資本を含める必要があります" - -#: settings/serializers/security.py:20 -msgid "Must contain lowercase" -msgstr "小文字を含める必要があります。" - -#: settings/serializers/security.py:23 -msgid "Must contain numeric" -msgstr "数値を含める必要があります" - -#: settings/serializers/security.py:26 -msgid "Must contain special" -msgstr "特別な" - -#: settings/serializers/security.py:31 -msgid "" -"If the user has failed to log in for a limited number of times, no login is " -"allowed during this time interval." -msgstr "" -"ユーザーが限られた回数だけログインできなかった場合、この時間間隔ではログイン" -"はできません。" - -#: settings/serializers/security.py:40 -msgid "All users" -msgstr "すべてのユーザー" - -#: settings/serializers/security.py:41 -msgid "Only admin users" -msgstr "管理者のみ" - -#: settings/serializers/security.py:43 -msgid "Global MFA auth" -msgstr "グローバル有効化MFA認証" - -#: settings/serializers/security.py:47 -msgid "Third-party login users perform MFA authentication" -msgstr "サードパーティのログインユーザーがMFA認証を実行" - -#: settings/serializers/security.py:48 -msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "サードパーティのログインモードには、OIDC、CAS、SAML2" - -#: settings/serializers/security.py:52 -msgid "Limit the number of user login failures" -msgstr "ユーザーログインの失敗数を制限する" - -#: settings/serializers/security.py:56 -msgid "Block user login interval (minute)" -msgstr "ユーザーのログイン間隔をブロックする(分)" - -#: settings/serializers/security.py:61 -msgid "Limit the number of IP login failures" -msgstr "IPログイン失敗の数を制限する" - -#: settings/serializers/security.py:65 -msgid "Block IP login interval (minute)" -msgstr "IPログイン間隔をブロックする(分)" - -#: settings/serializers/security.py:69 -msgid "Login IP White List" -msgstr "ログインIPホワイトリスト" - -#: settings/serializers/security.py:74 -msgid "Login IP Black List" -msgstr "ログインIPブラックリスト" - -#: settings/serializers/security.py:80 +#: settings/serializers/security.py:16 msgid "User password expiration (day)" msgstr "ユーザーパスワードの有効期限(天)" -#: settings/serializers/security.py:82 +#: settings/serializers/security.py:18 msgid "" "If the user does not update the password during the time, the user password " "will expire failure;The password expiration reminder mail will be automatic " @@ -5103,11 +5172,11 @@ msgstr "" "ドの有効期限が切れるリマインダーメールがシステムからユーザーに自動的に送信さ" "れます。" -#: settings/serializers/security.py:89 +#: settings/serializers/security.py:25 msgid "Number of repeated historical passwords" msgstr "繰り返された履歴パスワードの数" -#: settings/serializers/security.py:91 +#: settings/serializers/security.py:27 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" @@ -5115,11 +5184,67 @@ msgstr "" "ヒント: ユーザーがパスワードをリセットすると、ユーザーの前のnの履歴パスワード" "にすることはできません" -#: settings/serializers/security.py:96 +#: settings/serializers/security.py:33 +msgid "Password minimum length" +msgstr "パスワードの最小長" + +#: settings/serializers/security.py:37 +msgid "Admin user password minimum length" +msgstr "管理者ユーザーパスワードの最小長" + +#: settings/serializers/security.py:40 +msgid "Must contain capital" +msgstr "資本を含める必要があります" + +#: settings/serializers/security.py:43 +msgid "Must contain lowercase" +msgstr "小文字を含める必要があります。" + +#: settings/serializers/security.py:46 +msgid "Must contain numeric" +msgstr "数値を含める必要があります" + +#: settings/serializers/security.py:49 +msgid "Must contain special" +msgstr "特別な" + +#: settings/serializers/security.py:54 +msgid "" +"If the user has failed to log in for a limited number of times, no login is " +"allowed during this time interval." +msgstr "" +"ユーザーが限られた回数だけログインできなかった場合、この時間間隔ではログイン" +"はできません。" + +#: settings/serializers/security.py:62 +msgid "Limit the number of user login failures" +msgstr "ユーザーログインの失敗数を制限する" + +#: settings/serializers/security.py:66 +msgid "Block user login interval (minute)" +msgstr "ユーザーのログイン間隔をブロックする(分)" + +#: settings/serializers/security.py:72 +msgid "Limit the number of IP login failures" +msgstr "IPログイン失敗の数を制限する" + +#: settings/serializers/security.py:76 +msgid "Block IP login interval (minute)" +msgstr "IPログイン間隔をブロックする(分)" + +#: settings/serializers/security.py:80 +msgid "Login IP White List" +msgstr "ログインIPホワイトリスト" + +#: settings/serializers/security.py:85 +msgid "Login IP Black List" +msgstr "ログインIPブラックリスト" + +#: settings/serializers/security.py:90 msgid "Only single device login" msgstr "単一デバイスログインのみ" -#: settings/serializers/security.py:97 +#: settings/serializers/security.py:91 msgid "" "After the user logs in on the new device, other logged-in devices will " "automatically log out" @@ -5127,11 +5252,11 @@ msgstr "" "ユーザーが新しいデバイスにログインすると、ログインしている他のデバイスは自動" "的にログアウトします。" -#: settings/serializers/security.py:100 +#: settings/serializers/security.py:94 msgid "Only exist user login" msgstr "ユーザーログインのみ存在" -#: settings/serializers/security.py:101 +#: settings/serializers/security.py:96 msgid "" "If enabled, non-existent users will not be allowed to log in; if disabled, " "users of other authentication methods except local authentication methods " @@ -5142,11 +5267,11 @@ msgstr "" "ローカル認証方法を除く他の認証方法のユーザーはログインでき、ユーザーが自動的" "に作成されます (ユーザーが存在しない場合)。" -#: settings/serializers/security.py:104 +#: settings/serializers/security.py:102 msgid "Only from source login" msgstr "ソースログインからのみ" -#: settings/serializers/security.py:105 +#: settings/serializers/security.py:104 msgid "" "If it is enabled, the user will only authenticate to the source when logging " "in; if it is disabled, the user will authenticate all the enabled " @@ -5157,28 +5282,70 @@ msgstr "" "な場合、ユーザーはログイン時に、いずれかの認証方法が成功する限り、有効なすべ" "ての認証方法を特定の順序で認証します。 、直接ログインできます" -#: settings/serializers/security.py:109 -msgid "MFA verify TTL (secend)" -msgstr "MFAはTTLを確認します(秒)" - -#: settings/serializers/security.py:111 -msgid "" -"The verification MFA takes effect only when you view the account password" -msgstr "検証MFAはアカウントのパスワードを表示したときにのみ有効になります。" +#: settings/serializers/security.py:115 +msgid "Not enabled" +msgstr "有効化されていません" #: settings/serializers/security.py:116 -msgid "Verify code TTL" -msgstr "認証コード有効時間" +msgid "All users" +msgstr "すべてのユーザー" #: settings/serializers/security.py:117 -msgid "Unit: second, reset password and send SMS code expiration time" +msgid "Only admin users" +msgstr "管理者のみ" + +#: settings/serializers/security.py:119 +msgid "Global MFA auth" +msgstr "グローバル有効化MFA認証" + +#: settings/serializers/security.py:123 +msgid "Third-party login users perform MFA authentication" +msgstr "サードパーティのログインユーザーがMFA認証を実行" + +#: settings/serializers/security.py:124 +msgid "The third-party login modes include OIDC, CAS, and SAML2" +msgstr "サードパーティのログインモードには、OIDC、CAS、SAML2" + +#: settings/serializers/security.py:127 +msgid "OTP issuer name" +msgstr "OTP発行者名" + +#: settings/serializers/security.py:131 +msgid "OTP valid window" +msgstr "OTP有効なウィンドウ" + +#: settings/serializers/security.py:135 +msgid "MFA verify TTL" +msgstr "MFAはTTLを確認します(秒)" + +#: settings/serializers/security.py:137 +msgid "" +"Unit: second, The verification MFA takes effect only when you view the " +"account password" +msgstr "検証MFAはアカウントのパスワードを表示したときにのみ有効になります。" + +#: settings/serializers/security.py:142 +msgid "MFA in login page" +msgstr "ログインページのMFA" + +#: settings/serializers/security.py:143 +msgid "Eu security regulations(GDPR) require MFA to be on the login page" +msgstr "" +"Euセキュリティ規制 (GDPR) では、MFAがログインページにある必要があります" + +#: settings/serializers/security.py:147 +msgid "Verify code TTL (second)" +msgstr "認証コード有効時間" + +#: settings/serializers/security.py:148 +msgid "Reset password and send SMS code expiration time" msgstr "パスワードをリセットしてSMSコードの有効期限を送信します" -#: settings/serializers/security.py:121 +#: settings/serializers/security.py:152 msgid "Enable Login dynamic code" msgstr "ログイン動的コードの有効化" -#: settings/serializers/security.py:122 +#: settings/serializers/security.py:153 msgid "" "The password and additional code are sent to a third party authentication " "system for verification" @@ -5186,97 +5353,19 @@ msgstr "" "パスワードと追加コードは、検証のためにサードパーティの認証システムに送信され" "ます" -#: settings/serializers/security.py:127 -msgid "MFA in login page" -msgstr "ログインページのMFA" - -#: settings/serializers/security.py:128 -msgid "Eu security regulations(GDPR) require MFA to be on the login page" -msgstr "" -"Euセキュリティ規制 (GDPR) では、MFAがログインページにある必要があります" - -#: settings/serializers/security.py:131 +#: settings/serializers/security.py:157 msgid "Enable Login captcha" msgstr "ログインcaptchaの有効化" -#: settings/serializers/security.py:132 +#: settings/serializers/security.py:158 msgid "Enable captcha to prevent robot authentication" msgstr "Captchaを有効にしてロボット認証を防止する" -#: settings/serializers/security.py:154 -msgid "Enable terminal register" -msgstr "ターミナルレジスタの有効化" - -#: settings/serializers/security.py:156 -msgid "" -"Allow terminal register, after all terminal setup, you should disable this " -"for security" -msgstr "" -"ターミナルレジスタを許可し、すべてのターミナルセットアップの後、セキュリティ" -"のためにこれを無効にする必要があります" - -#: settings/serializers/security.py:160 -msgid "Enable watermark" -msgstr "透かしの有効化" - #: settings/serializers/security.py:161 -msgid "Enabled, the web session and replay contains watermark information" -msgstr "Webセッションとリプレイには透かし情報が含まれています。" - -#: settings/serializers/security.py:165 -msgid "Connection max idle time (minute)" -msgstr "接続最大アイドル時間(分)" - -#: settings/serializers/security.py:166 -msgid "If idle time more than it, disconnect connection." -msgstr "この設定以上の操作がない場合、接続は切断されます" - -#: settings/serializers/security.py:169 -msgid "Remember manual auth" -msgstr "手動入力パスワードの保存" - -#: settings/serializers/security.py:172 -msgid "Insecure command alert" -msgstr "安全でないコマンドアラート" - -#: settings/serializers/security.py:175 -msgid "Email recipient" -msgstr "メール受信者" - -#: settings/serializers/security.py:176 -msgid "Multiple user using , split" -msgstr "複数のユーザーを使用して、分割" - -#: settings/serializers/security.py:179 -msgid "Operation center" -msgstr "職業センター" - -#: settings/serializers/security.py:180 -msgid "Allow user run batch command or not using ansible" -msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" - -#: settings/serializers/security.py:184 -msgid "Operation center command blacklist" -msgstr "オペレーション センター コマンド ブラックリスト" - -#: settings/serializers/security.py:185 -msgid "Commands that are not allowed execute." -msgstr "実行が許可されていないコマンド" - -#: settings/serializers/security.py:188 -msgid "Session share" -msgstr "セッション共有" - -#: settings/serializers/security.py:189 -msgid "Enabled, Allows user active session to be shared with other users" -msgstr "" -"ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" - -#: settings/serializers/security.py:192 msgid "Remote Login Protection" msgstr "リモートログイン保護" -#: settings/serializers/security.py:194 +#: settings/serializers/security.py:163 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " @@ -5286,7 +5375,69 @@ msgstr "" "します。アカウントが共通のログイン都市からログインしている場合、システムはリ" "モートログインリマインダーを送信します" -#: settings/serializers/settings.py:71 +#: settings/serializers/security.py:169 +msgid "Unused user timeout (day)" +msgstr "未使用のユーザータイムアウト(日)" + +#: settings/serializers/security.py:170 +msgid "" +"Detect infrequent users daily and disable them if they exceed the " +"predetermined time limit." +msgstr "" +"毎日、頻度の低いユーザーを検出し、予め決められた時間制限を超えた場合は無効に" +"します。" + +#: settings/serializers/security.py:190 +msgid "Enable watermark" +msgstr "透かしの有効化" + +#: settings/serializers/security.py:191 +msgid "Enabled, the web session and replay contains watermark information" +msgstr "Webセッションとリプレイには透かし情報が含まれています。" + +#: settings/serializers/security.py:195 +msgid "Connection max idle time (minute)" +msgstr "接続最大アイドル時間(分)" + +#: settings/serializers/security.py:196 +msgid "If idle time more than it, disconnect connection." +msgstr "この設定以上の操作がない場合、接続は切断されます" + +#: settings/serializers/security.py:200 +msgid "Session max connection time (hour)" +msgstr "セッション最大接続時間(時間)" + +#: settings/serializers/security.py:201 +msgid "If session connection time more than it, disconnect connection." +msgstr "セッション接続時間がこれを超えると、接続が切断されます" + +#: settings/serializers/security.py:204 +msgid "Remember manual auth" +msgstr "手動入力パスワードの保存" + +#: settings/serializers/security.py:207 +#: terminal/templates/terminal/_msg_session_sharing.html:10 +msgid "Session share" +msgstr "セッション共有" + +#: settings/serializers/security.py:208 +msgid "Enabled, Allows user active session to be shared with other users" +msgstr "" +"ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" + +#: settings/serializers/security.py:214 +msgid "Insecure command alert" +msgstr "安全でないコマンドアラート" + +#: settings/serializers/security.py:217 +msgid "Email recipient" +msgstr "メール受信者" + +#: settings/serializers/security.py:218 +msgid "Multiple user using , split" +msgstr "複数のユーザーを使用して、分割" + +#: settings/serializers/settings.py:69 #, python-format msgid "[%s] %s" msgstr "[%s] %s" @@ -5299,15 +5450,27 @@ msgstr "ホスト名" msgid "Auto" msgstr "自動" -#: settings/serializers/terminal.py:21 +#: settings/serializers/terminal.py:22 +msgid "Enable terminal register" +msgstr "ターミナルレジスタの有効化" + +#: settings/serializers/terminal.py:24 +msgid "" +"Allow terminal register, after all terminal setup, you should disable this " +"for security" +msgstr "" +"ターミナルレジスタを許可し、すべてのターミナルセットアップの後、セキュリティ" +"のためにこれを無効にする必要があります" + +#: settings/serializers/terminal.py:27 msgid "Password auth" msgstr "パスワード認証" -#: settings/serializers/terminal.py:23 +#: settings/serializers/terminal.py:29 msgid "Public key auth" msgstr "鍵認証" -#: settings/serializers/terminal.py:24 +#: settings/serializers/terminal.py:30 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -5315,54 +5478,42 @@ msgstr "" "ヒント: AD/LDAPなどの他の認証方法を使用する場合は、サードパーティ製システムの" "削除後にこの項目を無効にする必要があります, ログインも可能" -#: settings/serializers/terminal.py:28 +#: settings/serializers/terminal.py:34 msgid "List sort by" msgstr "リストの並べ替え" -#: settings/serializers/terminal.py:31 +#: settings/serializers/terminal.py:37 msgid "List page size" msgstr "ページサイズを一覧表示" -#: settings/serializers/terminal.py:34 -msgid "Telnet login regex" -msgstr "Telnetログインregex" - -#: settings/serializers/terminal.py:35 -msgid "" -"Tips: The login success message varies with devices. if you cannot log in to " -"the device through Telnet, set this parameter" -msgstr "" -"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバ" -"イスにログインできない場合は、このパラメーターを設定します。" - -#: settings/serializers/terminal.py:38 +#: settings/serializers/terminal.py:39 msgid "Enable database proxy" msgstr "属性マップの有効化" -#: settings/serializers/terminal.py:39 +#: settings/serializers/terminal.py:40 msgid "Enable Razor" msgstr "Razor の有効化" -#: settings/serializers/terminal.py:40 +#: settings/serializers/terminal.py:41 msgid "Enable SSH Client" msgstr "SSH Clientの有効化" -#: settings/serializers/terminal.py:51 +#: settings/serializers/terminal.py:52 msgid "Default graphics resolution" msgstr "デフォルトのグラフィック解像度" -#: settings/serializers/terminal.py:52 +#: settings/serializers/terminal.py:53 msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" msgstr "" "ヒント: Luna ページでグラフィック アセットを接続するときに使用するデフォルト" "の解像度" -#: settings/tasks/ldap.py:25 +#: settings/tasks/ldap.py:24 msgid "Periodic import ldap user" msgstr "LDAP ユーザーを定期的にインポートする" -#: settings/tasks/ldap.py:46 +#: settings/tasks/ldap.py:45 msgid "Registration periodic import ldap user task" msgstr "登録サイクルLDAPユーザータスクのインポート" @@ -5623,7 +5774,7 @@ msgstr "確認コードが送信されました" msgid "Home page" msgstr "ホームページ" -#: templates/resource_download.html:18 templates/resource_download.html:32 +#: templates/resource_download.html:18 templates/resource_download.html:33 msgid "Client" msgstr "クライアント" @@ -5636,15 +5787,15 @@ msgstr "" "るために使用されており、現在はRDP SSHクライアントのみをサポートしています。" "「Telnetは将来的にサポートする" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "Microsoft" msgstr "マイクロソフト" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "Official" msgstr "公式" -#: templates/resource_download.html:34 +#: templates/resource_download.html:35 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" @@ -5652,11 +5803,11 @@ msgstr "" "MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" "する必要があります" -#: templates/resource_download.html:43 +#: templates/resource_download.html:44 msgid "Windows Remote application publisher tools" msgstr "Windowsリモートアプリケーション発行者ツール" -#: templates/resource_download.html:44 +#: templates/resource_download.html:45 msgid "" "OpenSSH is a program used to connect remote applications in the Windows " "Remote Application Publisher" @@ -5664,7 +5815,7 @@ msgstr "" "OpenSSHはリモートアプリケーションをWindowsリモートアプリケーションで接続する" "プログラムです" -#: templates/resource_download.html:52 +#: templates/resource_download.html:53 msgid "Offline video player" msgstr "オフラインビデオプレーヤー" @@ -5672,6 +5823,10 @@ msgstr "オフラインビデオプレーヤー" msgid "Invalid zip file" msgstr "zip ファイルが無効です" +#: terminal/api/applet/applet.py:65 +msgid "This is enterprise edition applet" +msgstr "これはエンタープライズ版アプレットです" + #: terminal/api/component/endpoint.py:32 msgid "Not found protocol query params" msgstr "プロトコルクエリパラメータが見つかりません" @@ -5692,7 +5847,7 @@ msgstr "コマンドストア" msgid "Invalid" msgstr "無効" -#: terminal/api/component/storage.py:119 terminal/tasks.py:140 +#: terminal/api/component/storage.py:119 terminal/tasks.py:141 msgid "Test failure: {}" msgstr "テスト失敗: {}" @@ -5701,7 +5856,7 @@ msgid "Test successful" msgstr "テスト成功" #: terminal/api/component/storage.py:124 terminal/notifications.py:240 -#: terminal/tasks.py:144 +#: terminal/tasks.py:145 msgid "Test failure: Account invalid" msgstr "テスト失敗: アカウントが無効" @@ -5729,23 +5884,43 @@ msgstr "安全なセッション共有設定が無効になっています" msgid "Terminals" msgstr "ターミナル管理" -#: terminal/backends/command/models.py:20 +#: terminal/backends/command/models.py:19 msgid "Input" msgstr "入力" -#: terminal/backends/command/models.py:21 terminal/serializers/command.py:74 +#: terminal/backends/command/models.py:20 terminal/serializers/command.py:73 msgid "Output" msgstr "出力" -#: terminal/backends/command/models.py:25 terminal/serializers/command.py:23 +#: terminal/backends/command/models.py:24 terminal/serializers/command.py:22 #: terminal/templates/terminal/_msg_command_warning.html:10 msgid "Risk level" msgstr "リスクレベル" +#: terminal/connect_methods.py:29 +msgid "SSH Client" +msgstr "SSH クライアント" + +#: terminal/connect_methods.py:30 +msgid "SSH Guide" +msgstr "SSH ガイド人" + +#: terminal/connect_methods.py:31 +msgid "SFTP Client" +msgstr "SFTP クライアント" + +#: terminal/connect_methods.py:33 +msgid "DB Guide" +msgstr "" + #: terminal/connect_methods.py:34 msgid "DB Client" msgstr "データベース クライアント" +#: terminal/connect_methods.py:36 +msgid "Remote Desktop" +msgstr "リモートデスクトップ" + #: terminal/const.py:12 msgid "Review & Reject" msgstr "レビューと拒否" @@ -5795,6 +5970,18 @@ msgstr "読み取り専用" msgid "Writable" msgstr "書き込み可能" +#: terminal/const.py:94 +msgid "Kill Session" +msgstr "セッションを終了する" + +#: terminal/const.py:95 +msgid "Lock Session" +msgstr "セッションをロックする" + +#: terminal/const.py:96 +msgid "Unlock Session" +msgstr "セッションのロックを解除する" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "一括作成非サポート" @@ -5815,7 +6002,7 @@ msgstr "エンタープライズ版" msgid "Author" msgstr "著者" -#: terminal/models/applet/applet.py:37 terminal/serializers/applet.py:30 +#: terminal/models/applet/applet.py:37 terminal/serializers/applet.py:31 msgid "Edition" msgstr "バージョン" @@ -5827,7 +6014,7 @@ msgstr "同時実行可能" msgid "Tags" msgstr "ラベル" -#: terminal/models/applet/applet.py:47 terminal/serializers/storage.py:157 +#: terminal/models/applet/applet.py:47 terminal/serializers/storage.py:159 msgid "Hosts" msgstr "ホスト" @@ -5847,28 +6034,36 @@ msgstr "カスタムプラットフォームのみをサポート" msgid "Missing type in platform.yml" msgstr "platform.ymlにタイプがありません" -#: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:34 -#: terminal/models/applet/host.py:134 +#: terminal/models/applet/applet.py:282 terminal/models/applet/host.py:35 +#: terminal/models/applet/host.py:137 msgid "Hosting" msgstr "ホスト マシン" -#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:57 +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:57 msgid "Deploy options" msgstr "展開パラメーター" +#: terminal/models/applet/host.py:19 +msgid "Auto create accounts" +msgstr "アカウントの自動作成" + #: terminal/models/applet/host.py:20 +msgid "Accounts create amount" +msgstr "作成するアカウント数" + +#: terminal/models/applet/host.py:21 msgid "Inited" msgstr "初期化された" -#: terminal/models/applet/host.py:21 -msgid "Date inited" -msgstr "" - #: terminal/models/applet/host.py:22 +msgid "Date inited" +msgstr "初期化日" + +#: terminal/models/applet/host.py:23 msgid "Date synced" msgstr "同期日" -#: terminal/models/applet/host.py:135 +#: terminal/models/applet/host.py:138 msgid "Initial" msgstr "初期化" @@ -5905,18 +6100,18 @@ msgid "Redis port" msgstr "Redis ポート" #: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:100 -#: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:38 -#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 -#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 +#: terminal/models/component/endpoint.py:102 +#: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:40 +#: terminal/serializers/storage.py:52 terminal/serializers/storage.py:82 +#: terminal/serializers/storage.py:92 terminal/serializers/storage.py:100 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/component/endpoint.py:93 +#: terminal/models/component/endpoint.py:95 msgid "IP group" msgstr "IP グループ" -#: terminal/models/component/endpoint.py:106 +#: terminal/models/component/endpoint.py:108 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -5966,7 +6161,7 @@ msgstr "再生ストレージ" msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:77 +#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:76 msgid "Remote Address" msgstr "リモートアドレス" @@ -5974,7 +6169,7 @@ msgstr "リモートアドレス" msgid "Application User" msgstr "ユーザーの適用" -#: terminal/models/component/terminal.py:165 +#: terminal/models/component/terminal.py:166 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5998,7 +6193,7 @@ msgstr "セッション再生をダウンロードできます" msgid "Account id" msgstr "アカウント ID" -#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:104 +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:114 msgid "Login from" msgstr "ログイン元" @@ -6010,27 +6205,31 @@ msgstr "リプレイ" msgid "Date end" msgstr "終了日" -#: terminal/models/session/session.py:240 +#: terminal/models/session/session.py:47 terminal/serializers/session.py:62 +msgid "Command amount" +msgstr "コマンド量" + +#: terminal/models/session/session.py:281 msgid "Session record" msgstr "セッション記録" -#: terminal/models/session/session.py:242 +#: terminal/models/session/session.py:283 msgid "Can monitor session" msgstr "セッションを監視できます" -#: terminal/models/session/session.py:243 +#: terminal/models/session/session.py:284 msgid "Can share session" msgstr "セッションを共有できます" -#: terminal/models/session/session.py:244 +#: terminal/models/session/session.py:285 msgid "Can terminate session" msgstr "セッションを終了できます" -#: terminal/models/session/session.py:245 +#: terminal/models/session/session.py:286 msgid "Can validate session action perm" msgstr "セッションアクションのパーマを検証できます" -#: terminal/models/session/sharing.py:30 +#: terminal/models/session/sharing.py:31 msgid "Expired time (min)" msgstr "期限切れ時間 (分)" @@ -6039,47 +6238,52 @@ msgstr "期限切れ時間 (分)" msgid "Action permission" msgstr "アクションパーミッション" -#: terminal/models/session/sharing.py:40 terminal/models/session/sharing.py:86 +#: terminal/models/session/sharing.py:37 +msgid "Origin" +msgstr "ソース" + +#: terminal/models/session/sharing.py:41 terminal/models/session/sharing.py:96 +#: terminal/notifications.py:261 msgid "Session sharing" msgstr "セッション共有" -#: terminal/models/session/sharing.py:42 +#: terminal/models/session/sharing.py:43 msgid "Can add super session sharing" msgstr "スーパーセッション共有を追加できます" -#: terminal/models/session/sharing.py:69 +#: terminal/models/session/sharing.py:79 msgid "Link not active" msgstr "リンクがアクティブでない" -#: terminal/models/session/sharing.py:71 +#: terminal/models/session/sharing.py:81 msgid "Link expired" msgstr "リンク期限切れ" -#: terminal/models/session/sharing.py:73 +#: terminal/models/session/sharing.py:83 msgid "User not allowed to join" msgstr "ユーザーはセッションに参加できません" -#: terminal/models/session/sharing.py:90 terminal/serializers/sharing.py:71 +#: terminal/models/session/sharing.py:100 terminal/serializers/sharing.py:71 msgid "Joiner" msgstr "ジョイナー" -#: terminal/models/session/sharing.py:93 +#: terminal/models/session/sharing.py:103 msgid "Date joined" msgstr "参加日" -#: terminal/models/session/sharing.py:96 +#: terminal/models/session/sharing.py:106 msgid "Date left" msgstr "日付が残っています" -#: terminal/models/session/sharing.py:119 +#: terminal/models/session/sharing.py:129 msgid "Session join record" msgstr "セッション参加記録" -#: terminal/models/session/sharing.py:135 +#: terminal/models/session/sharing.py:145 msgid "Invalid verification code" msgstr "検証コードが無効" -#: terminal/models/session/sharing.py:142 +#: terminal/models/session/sharing.py:152 msgid "You have already joined this session" msgstr "すでにこのセッションに参加しています" @@ -6151,7 +6355,7 @@ msgstr "" "URL を入力します。
例: https://172.16.10.110 または https://dev." "jumpserver.com" -#: terminal/serializers/applet_host.py:46 terminal/serializers/storage.py:168 +#: terminal/serializers/applet_host.py:46 terminal/serializers/storage.py:170 msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" @@ -6183,35 +6387,53 @@ msgstr "RDS 远程应用注销时间限制" msgid "Load status" msgstr "ロードステータス" -#: terminal/serializers/command.py:20 +#: terminal/serializers/applet_host.py:72 +msgid "" +"These accounts are used to connect to the published application, the account " +"is now divided into two types, one is dedicated to each account, each user " +"has a private account, the other is public, when the application does not " +"support multiple open and the special has been used, the public account will " +"be used to connect" +msgstr "" +"これらのアカウントは、公開されたアプリケーションに接続するために使用されま" +"す。アカウントは現在、2つのタイプに分類されています。1つは、各アカウントに専" +"用のアカウントで、各ユーザーにはプライベートアカウントがあります。もう1つは公" +"開されています。アプリケーションが複数のオープンをサポートしていない場合、お" +"よび特別なものが使用されている場合、公開アカウントが使用されます。" + +#: terminal/serializers/applet_host.py:77 +msgid "The number of public accounts created automatically" +msgstr "自動的に作成される公開アカウントの数" + +#: terminal/serializers/command.py:19 msgid "Session ID" msgstr "セッションID" -#: terminal/serializers/command.py:42 +#: terminal/serializers/command.py:41 msgid "Command Filter ACL" msgstr "コマンドフィルター" -#: terminal/serializers/command.py:45 +#: terminal/serializers/command.py:44 msgid "Command Group" msgstr "コマンドグループ" -#: terminal/serializers/command.py:56 +#: terminal/serializers/command.py:55 msgid "Invalid command filter ACL id" msgstr "無効なコマンドフィルターID" -#: terminal/serializers/command.py:60 +#: terminal/serializers/command.py:59 msgid "Invalid command group id" msgstr "無効なコマンドグループID" -#: terminal/serializers/command.py:64 +#: terminal/serializers/command.py:63 msgid "Invalid session id" msgstr "無効なセッションID" -#: terminal/serializers/command.py:73 +#: terminal/serializers/command.py:72 msgid "Account " msgstr "アカウント" -#: terminal/serializers/command.py:75 +#: terminal/serializers/command.py:74 msgid "Timestamp" msgstr "タイムスタンプ" @@ -6258,100 +6480,100 @@ msgstr "" msgid "Asset IP" msgstr "資産 IP" -#: terminal/serializers/session.py:22 terminal/serializers/session.py:44 +#: terminal/serializers/session.py:23 terminal/serializers/session.py:47 msgid "Can replay" msgstr "再生できます" -#: terminal/serializers/session.py:23 terminal/serializers/session.py:45 +#: terminal/serializers/session.py:24 terminal/serializers/session.py:48 msgid "Can join" msgstr "参加できます" -#: terminal/serializers/session.py:24 terminal/serializers/session.py:48 +#: terminal/serializers/session.py:25 terminal/serializers/session.py:51 msgid "Can terminate" msgstr "終了できます" -#: terminal/serializers/session.py:40 +#: terminal/serializers/session.py:43 msgid "User ID" msgstr "ユーザーID" -#: terminal/serializers/session.py:41 +#: terminal/serializers/session.py:44 msgid "Asset ID" msgstr "資産ID" -#: terminal/serializers/session.py:42 +#: terminal/serializers/session.py:45 msgid "Login from display" msgstr "表示からのログイン" -#: terminal/serializers/session.py:49 +#: terminal/serializers/session.py:52 msgid "Terminal display" msgstr "ターミナルディスプレイ" -#: terminal/serializers/session.py:54 -msgid "Command amount" -msgstr "コマンド量" - -#: terminal/serializers/storage.py:20 +#: terminal/serializers/storage.py:22 msgid "Endpoint invalid: remove path `{}`" msgstr "エンドポイントが無効: パス '{}' を削除" -#: terminal/serializers/storage.py:26 +#: terminal/serializers/storage.py:28 msgid "Bucket" msgstr "バケット" -#: terminal/serializers/storage.py:30 +#: terminal/serializers/storage.py:32 #: xpack/plugins/cloud/serializers/account_attrs.py:17 msgid "Access key id" msgstr "アクセスキー" -#: terminal/serializers/storage.py:34 +#: terminal/serializers/storage.py:36 #: xpack/plugins/cloud/serializers/account_attrs.py:20 msgid "Access key secret" msgstr "アクセスキーシークレット" -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:209 +#: terminal/serializers/storage.py:67 xpack/plugins/cloud/models.py:250 msgid "Region" msgstr "リージョン" -#: terminal/serializers/storage.py:109 +#: terminal/serializers/storage.py:111 msgid "Container name" msgstr "コンテナー名" -#: terminal/serializers/storage.py:112 +#: terminal/serializers/storage.py:114 msgid "Account key" msgstr "アカウントキー" -#: terminal/serializers/storage.py:115 +#: terminal/serializers/storage.py:117 msgid "Endpoint suffix" msgstr "エンドポイントサフィックス" -#: terminal/serializers/storage.py:135 +#: terminal/serializers/storage.py:137 msgid "The address format is incorrect" msgstr "アドレス形式が正しくありません" -#: terminal/serializers/storage.py:142 +#: terminal/serializers/storage.py:144 msgid "Host invalid" msgstr "ホスト無効" -#: terminal/serializers/storage.py:145 +#: terminal/serializers/storage.py:147 msgid "Port invalid" msgstr "ポートが無効" -#: terminal/serializers/storage.py:160 +#: terminal/serializers/storage.py:162 msgid "Index by date" msgstr "日付による索引付け" -#: terminal/serializers/storage.py:161 +#: terminal/serializers/storage.py:163 msgid "Whether to create an index by date" msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" -#: terminal/serializers/storage.py:164 +#: terminal/serializers/storage.py:166 msgid "Index" msgstr "インデックス" -#: terminal/serializers/storage.py:166 +#: terminal/serializers/storage.py:168 msgid "Doc type" msgstr "Docタイプ" +#: terminal/serializers/task.py:9 +msgid "Session id" +msgstr "セッション" + #: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91 msgid "Not found" msgstr "見つかりません" @@ -6376,11 +6598,11 @@ msgstr "アプリケーション マシンの展開を実行する" msgid "Install applet" msgstr "アプリをインストールする" -#: terminal/tasks.py:110 +#: terminal/tasks.py:111 msgid "Generate applet host accounts" msgstr "リモートアプリケーション上のアカウントを収集する" -#: terminal/tasks.py:122 +#: terminal/tasks.py:123 msgid "Check command replay storage connectivity" msgstr "チェックコマンドと録画ストレージの接続性" @@ -6527,7 +6749,7 @@ msgstr "チケットフロー承認ルール" msgid "Ticket flow" msgstr "チケットの流れ" -#: tickets/models/relation.py:10 +#: tickets/models/relation.py:12 msgid "Ticket session relation" msgstr "チケットセッションの関係" @@ -6601,11 +6823,11 @@ msgstr "承認ステップ" msgid "Relation snapshot" msgstr "製造オーダスナップショット" -#: tickets/models/ticket/general.py:398 +#: tickets/models/ticket/general.py:401 msgid "Please try again" msgstr "もう一度お試しください" -#: tickets/models/ticket/general.py:467 +#: tickets/models/ticket/general.py:470 msgid "Super ticket" msgstr "スーパーチケット" @@ -6711,7 +6933,7 @@ msgid "Ticket information" msgstr "作業指示情報" #: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:38 +#: tickets/views/approve.py:39 msgid "Ticket approval" msgstr "作業指示の承認" @@ -6723,30 +6945,34 @@ msgstr "承認" msgid "Go Login" msgstr "ログイン" -#: tickets/views/approve.py:39 +#: tickets/views/approve.py:40 msgid "" "This ticket does not exist, the process has ended, or this link has expired" msgstr "" "このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に" "なっています" -#: tickets/views/approve.py:68 +#: tickets/views/approve.py:69 msgid "Click the button below to approve or reject" msgstr "下のボタンをクリックして同意または拒否。" -#: tickets/views/approve.py:70 +#: tickets/views/approve.py:71 msgid "After successful authentication, this ticket can be approved directly" msgstr "認証に成功した後、作業指示書は直接承認することができる。" -#: tickets/views/approve.py:92 +#: tickets/views/approve.py:93 msgid "Illegal approval action" msgstr "無効な承認アクション" -#: tickets/views/approve.py:105 +#: tickets/views/approve.py:106 msgid "This user is not authorized to approve this ticket" msgstr "このユーザーはこの作業指示を承認する権限がありません" -#: users/api/user.py:191 +#: users/api/user.py:141 +msgid "Can not invite self" +msgstr "自分自身を招待することはできません" + +#: users/api/user.py:194 msgid "Could not reset self otp, use profile reset instead" msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" @@ -6857,78 +7083,78 @@ msgstr "公開鍵は古いものと同じであってはなりません。" msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" -#: users/forms/profile.py:173 users/models/user.py:786 -#: xpack/plugins/cloud/serializers/account_attrs.py:206 +#: users/forms/profile.py:173 users/models/user.py:820 +#: xpack/plugins/cloud/serializers/account_attrs.py:203 msgid "Public key" msgstr "公開キー" -#: users/models/user.py:603 users/serializers/profile.py:118 +#: users/models/user.py:637 users/serializers/profile.py:118 msgid "Force enable" msgstr "強制有効" -#: users/models/user.py:765 users/serializers/user.py:171 +#: users/models/user.py:799 users/serializers/user.py:171 msgid "Is service account" msgstr "サービスアカウントです" -#: users/models/user.py:767 +#: users/models/user.py:801 msgid "Avatar" msgstr "アバター" -#: users/models/user.py:770 +#: users/models/user.py:804 msgid "Wechat" msgstr "微信" -#: users/models/user.py:773 users/serializers/user.py:108 +#: users/models/user.py:807 users/serializers/user.py:108 msgid "Phone" msgstr "電話" -#: users/models/user.py:779 +#: users/models/user.py:813 msgid "OTP secret key" msgstr "OTP 秘密" -#: users/models/user.py:783 -#: xpack/plugins/cloud/serializers/account_attrs.py:209 +#: users/models/user.py:817 +#: xpack/plugins/cloud/serializers/account_attrs.py:206 msgid "Private key" msgstr "ssh秘密鍵" -#: users/models/user.py:789 +#: users/models/user.py:823 msgid "Secret key" msgstr "秘密キー" -#: users/models/user.py:794 users/serializers/profile.py:149 +#: users/models/user.py:828 users/serializers/profile.py:149 #: users/serializers/user.py:168 msgid "Is first login" msgstr "最初のログインです" -#: users/models/user.py:808 +#: users/models/user.py:842 msgid "Date password last updated" msgstr "最終更新日パスワード" -#: users/models/user.py:811 +#: users/models/user.py:845 msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:949 +#: users/models/user.py:988 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:950 +#: users/models/user.py:989 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:951 +#: users/models/user.py:990 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:960 +#: users/models/user.py:999 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:963 +#: users/models/user.py:1002 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:988 +#: users/models/user.py:1027 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -7003,7 +7229,7 @@ msgstr "MFAフォース有効化" msgid "Login blocked" msgstr "ログインがロックされました" -#: users/serializers/user.py:98 users/serializers/user.py:176 +#: users/serializers/user.py:98 users/serializers/user.py:177 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" @@ -7011,23 +7237,27 @@ msgstr "仮想MFAがバインドされているか" msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:173 +#: users/serializers/user.py:172 +msgid "Is org admin" +msgstr "組織管理者です" + +#: users/serializers/user.py:174 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:177 +#: users/serializers/user.py:178 msgid "MFA level" msgstr "MFA レベル" -#: users/serializers/user.py:283 +#: users/serializers/user.py:284 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:284 +#: users/serializers/user.py:285 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:317 +#: users/serializers/user.py:318 msgid "name not unique" msgstr "名前が一意ではない" @@ -7040,22 +7270,26 @@ msgstr "" "管理者は「既存のユーザーのみログインを許可」をオンにしており、現在のユーザー" "はユーザーリストにありません。管理者に連絡してください。" -#: users/tasks.py:21 +#: users/tasks.py:24 msgid "Check password expired" msgstr "パスワードの有効期限が切れていることを確認する" -#: users/tasks.py:35 +#: users/tasks.py:38 msgid "Periodic check password expired" msgstr "定期認証パスワードの有効期限" -#: users/tasks.py:49 +#: users/tasks.py:52 msgid "Check user expired" msgstr "ユーザーの有効期限が切れていることを確認する" -#: users/tasks.py:66 +#: users/tasks.py:69 msgid "Periodic check user expired" msgstr "ユーザーの有効期限の定期的な検出" +#: users/tasks.py:83 +msgid "Check unused users" +msgstr "未使用のユーザーを確認する" + #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" msgstr "アカウントの有効期限は" @@ -7253,36 +7487,36 @@ msgstr "" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "MFA Authenticatorを開き、6ビットの動的コードを入力します" -#: users/views/profile/otp.py:87 +#: users/views/profile/otp.py:85 msgid "Already bound" msgstr "すでにバインド済み" -#: users/views/profile/otp.py:88 +#: users/views/profile/otp.py:86 msgid "MFA already bound, disable first, then bound" msgstr "" "MFAはすでにバインドされており、最初に無効にしてからバインドされています。" -#: users/views/profile/otp.py:115 +#: users/views/profile/otp.py:113 msgid "OTP enable success" msgstr "OTP有効化成功" -#: users/views/profile/otp.py:116 +#: users/views/profile/otp.py:114 msgid "OTP enable success, return login page" msgstr "OTP有効化成功、ログインページを返す" -#: users/views/profile/otp.py:158 +#: users/views/profile/otp.py:156 msgid "Disable OTP" msgstr "OTPの無効化" -#: users/views/profile/otp.py:164 +#: users/views/profile/otp.py:162 msgid "OTP disable success" msgstr "OTP無効化成功" -#: users/views/profile/otp.py:165 +#: users/views/profile/otp.py:163 msgid "OTP disable success, return login page" msgstr "OTP無効化成功、ログインページを返す" -#: users/views/profile/password.py:36 users/views/profile/password.py:41 +#: users/views/profile/password.py:33 users/views/profile/password.py:38 msgid "Password invalid" msgstr "パスワード無効" @@ -7318,11 +7552,11 @@ msgstr "パスワードの成功をリセットし、ログインページに戻 msgid "XPACK" msgstr "XPack" -#: xpack/plugins/cloud/api.py:40 +#: xpack/plugins/cloud/api.py:56 msgid "Test connection successful" msgstr "テスト接続成功" -#: xpack/plugins/cloud/api.py:42 +#: xpack/plugins/cloud/api.py:58 msgid "Test connection failed: {}" msgstr "テスト接続に失敗しました: {}" @@ -7378,7 +7612,7 @@ msgstr "ucloud" msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:23 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:23 xpack/plugins/cloud/providers/nutanix.py:15 msgid "Nutanix" msgstr "Nutanix" @@ -7410,7 +7644,7 @@ msgstr "プライベートIP" msgid "Public IP" msgstr "パブリックIP" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:38 xpack/plugins/cloud/models.py:295 msgid "Instance name" msgstr "インスタンス名" @@ -7438,178 +7672,258 @@ msgstr "同期済み" msgid "Released" msgstr "リリース済み" +#: xpack/plugins/cloud/manager.py:53 +msgid "Account unavailable" +msgstr "利用できないアカウント" + #: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:34 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:38 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:43 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:45 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:147 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:95 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:39 +#: xpack/plugins/cloud/models.py:100 +#: xpack/plugins/cloud/serializers/task.py:150 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:105 xpack/plugins/cloud/serializers/task.py:44 +#: xpack/plugins/cloud/models.py:103 +#: xpack/plugins/cloud/serializers/task.py:155 msgid "Sync IP type" msgstr "同期IPタイプ" -#: xpack/plugins/cloud/models.py:108 xpack/plugins/cloud/serializers/task.py:61 +#: xpack/plugins/cloud/models.py:106 +#: xpack/plugins/cloud/serializers/task.py:173 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:114 +#: xpack/plugins/cloud/models.py:112 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:119 xpack/plugins/cloud/models.py:160 +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/models.py:313 +#: xpack/plugins/cloud/models.py:337 +msgid "Strategy" +msgstr "戦略" + +#: xpack/plugins/cloud/models.py:120 xpack/plugins/cloud/models.py:197 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:219 +#: xpack/plugins/cloud/models.py:208 xpack/plugins/cloud/models.py:260 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:212 +msgid "Sync instance snapshot" +msgstr "インスタンススナップショットの同期" + +#: xpack/plugins/cloud/models.py:216 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:199 +#: xpack/plugins/cloud/models.py:240 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:203 +#: xpack/plugins/cloud/models.py:244 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:206 +#: xpack/plugins/cloud/models.py:247 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:264 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" -#: xpack/plugins/cloud/providers/aws_international.py:17 +#: xpack/plugins/cloud/models.py:281 +msgid "Task strategy" +msgstr "ミッション戦略です" + +#: xpack/plugins/cloud/models.py:285 +msgid "Exact" +msgstr "" + +#: xpack/plugins/cloud/models.py:286 +msgid "Not" +msgstr "否" + +#: xpack/plugins/cloud/models.py:287 +msgid "In" +msgstr "イン" + +#: xpack/plugins/cloud/models.py:288 +msgid "Contains" +msgstr "含む" + +#: xpack/plugins/cloud/models.py:289 +msgid "Startswith" +msgstr "始まる" + +#: xpack/plugins/cloud/models.py:290 +msgid "Endswith" +msgstr "終わる" + +#: xpack/plugins/cloud/models.py:296 +msgid "Instance platform" +msgstr "インスタンス名" + +#: xpack/plugins/cloud/models.py:297 +msgid "Instance address" +msgstr "インスタンスアドレス" + +#: xpack/plugins/cloud/models.py:304 +msgid "Rule attr" +msgstr "ルール属性" + +#: xpack/plugins/cloud/models.py:308 +msgid "Rule match" +msgstr "ルール一致" + +#: xpack/plugins/cloud/models.py:310 +msgid "Rule value" +msgstr "ルール値" + +#: xpack/plugins/cloud/models.py:317 +msgid "Strategy rule" +msgstr "戦略ルール" + +#: xpack/plugins/cloud/models.py:332 +msgid "Action attr" +msgstr "アクション属性" + +#: xpack/plugins/cloud/models.py:334 +msgid "Action value" +msgstr "アクション値" + +#: xpack/plugins/cloud/models.py:341 +msgid "Strategy action" +msgstr "戦略アクション" + +#: xpack/plugins/cloud/providers/aws_international.py:18 msgid "China (Beijing)" msgstr "中国 (北京)" -#: xpack/plugins/cloud/providers/aws_international.py:18 +#: xpack/plugins/cloud/providers/aws_international.py:19 msgid "China (Ningxia)" msgstr "中国 (寧夏)" -#: xpack/plugins/cloud/providers/aws_international.py:21 +#: xpack/plugins/cloud/providers/aws_international.py:22 msgid "US East (Ohio)" msgstr "米国東部 (オハイオ州)" -#: xpack/plugins/cloud/providers/aws_international.py:22 +#: xpack/plugins/cloud/providers/aws_international.py:23 msgid "US East (N. Virginia)" msgstr "米国東部 (N. バージニア州)" -#: xpack/plugins/cloud/providers/aws_international.py:23 +#: xpack/plugins/cloud/providers/aws_international.py:24 msgid "US West (N. California)" msgstr "米国西部 (N. カリフォルニア州)" -#: xpack/plugins/cloud/providers/aws_international.py:24 +#: xpack/plugins/cloud/providers/aws_international.py:25 msgid "US West (Oregon)" msgstr "米国西部 (オレゴン州)" -#: xpack/plugins/cloud/providers/aws_international.py:25 +#: xpack/plugins/cloud/providers/aws_international.py:26 msgid "Africa (Cape Town)" msgstr "アフリカ (ケープタウン)" -#: xpack/plugins/cloud/providers/aws_international.py:26 +#: xpack/plugins/cloud/providers/aws_international.py:27 msgid "Asia Pacific (Hong Kong)" msgstr "アジアパシフィック (香港)" -#: xpack/plugins/cloud/providers/aws_international.py:27 +#: xpack/plugins/cloud/providers/aws_international.py:28 msgid "Asia Pacific (Mumbai)" msgstr "アジア太平洋 (ムンバイ)" -#: xpack/plugins/cloud/providers/aws_international.py:28 +#: xpack/plugins/cloud/providers/aws_international.py:29 msgid "Asia Pacific (Osaka-Local)" msgstr "アジアパシフィック (大阪-ローカル)" -#: xpack/plugins/cloud/providers/aws_international.py:29 +#: xpack/plugins/cloud/providers/aws_international.py:30 msgid "Asia Pacific (Seoul)" msgstr "アジア太平洋地域 (ソウル)" -#: xpack/plugins/cloud/providers/aws_international.py:30 +#: xpack/plugins/cloud/providers/aws_international.py:31 msgid "Asia Pacific (Singapore)" msgstr "アジア太平洋 (シンガポール)" -#: xpack/plugins/cloud/providers/aws_international.py:31 +#: xpack/plugins/cloud/providers/aws_international.py:32 msgid "Asia Pacific (Sydney)" msgstr "アジア太平洋 (シドニー)" -#: xpack/plugins/cloud/providers/aws_international.py:32 +#: xpack/plugins/cloud/providers/aws_international.py:33 msgid "Asia Pacific (Tokyo)" msgstr "アジアパシフィック (東京)" -#: xpack/plugins/cloud/providers/aws_international.py:33 +#: xpack/plugins/cloud/providers/aws_international.py:34 msgid "Canada (Central)" msgstr "カナダ (中央)" -#: xpack/plugins/cloud/providers/aws_international.py:34 +#: xpack/plugins/cloud/providers/aws_international.py:35 msgid "Europe (Frankfurt)" msgstr "ヨーロッパ (フランクフルト)" -#: xpack/plugins/cloud/providers/aws_international.py:35 +#: xpack/plugins/cloud/providers/aws_international.py:36 msgid "Europe (Ireland)" msgstr "ヨーロッパ (アイルランド)" -#: xpack/plugins/cloud/providers/aws_international.py:36 +#: xpack/plugins/cloud/providers/aws_international.py:37 msgid "Europe (London)" msgstr "ヨーロッパ (ロンドン)" -#: xpack/plugins/cloud/providers/aws_international.py:37 +#: xpack/plugins/cloud/providers/aws_international.py:38 msgid "Europe (Milan)" msgstr "ヨーロッパ (ミラノ)" -#: xpack/plugins/cloud/providers/aws_international.py:38 +#: xpack/plugins/cloud/providers/aws_international.py:39 msgid "Europe (Paris)" msgstr "ヨーロッパ (パリ)" -#: xpack/plugins/cloud/providers/aws_international.py:39 +#: xpack/plugins/cloud/providers/aws_international.py:40 msgid "Europe (Stockholm)" msgstr "ヨーロッパ (ストックホルム)" -#: xpack/plugins/cloud/providers/aws_international.py:40 +#: xpack/plugins/cloud/providers/aws_international.py:41 msgid "Middle East (Bahrain)" msgstr "中东 (バーレーン)" -#: xpack/plugins/cloud/providers/aws_international.py:41 +#: xpack/plugins/cloud/providers/aws_international.py:42 msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 +#: xpack/plugins/cloud/providers/jdcloud.py:125 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 -#: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 +#: xpack/plugins/cloud/providers/huaweicloud.py:42 +#: xpack/plugins/cloud/providers/jdcloud.py:128 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -7618,7 +7932,7 @@ msgid "CN East-Suzhou" msgstr "華東-蘇州" #: xpack/plugins/cloud/providers/baiducloud.py:57 -#: xpack/plugins/cloud/providers/huaweicloud.py:48 +#: xpack/plugins/cloud/providers/huaweicloud.py:49 msgid "CN-Hong Kong" msgstr "中国-香港" @@ -7631,72 +7945,72 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN East-Shanghai" msgstr "華東-上海" #: xpack/plugins/cloud/providers/baiducloud.py:61 -#: xpack/plugins/cloud/providers/huaweicloud.py:47 +#: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "AP-Singapore" msgstr "アジア太平洋-シンガポール" -#: xpack/plugins/cloud/providers/huaweicloud.py:35 -msgid "AF-Johannesburg" -msgstr "アフリカ-ヨハネスブルク" - -#: xpack/plugins/cloud/providers/huaweicloud.py:36 -msgid "CN North-Beijing4" -msgstr "華北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:37 +#: xpack/plugins/cloud/providers/huaweicloud.py:39 msgid "CN North-Beijing1" msgstr "華北-北京1" -#: xpack/plugins/cloud/providers/huaweicloud.py:38 -msgid "CN East-Shanghai2" -msgstr "華東-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:39 -msgid "CN East-Shanghai1" -msgstr "華東-上海1" +#: xpack/plugins/cloud/providers/huaweicloud.py:40 +msgid "CN North-Beijing4" +msgstr "華北-北京4" #: xpack/plugins/cloud/providers/huaweicloud.py:41 -msgid "LA-Mexico City1" -msgstr "LA-メキシコCity1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:42 -msgid "LA-Santiago" -msgstr "ラテンアメリカ-サンディエゴ" - -#: xpack/plugins/cloud/providers/huaweicloud.py:43 -msgid "LA-Sao Paulo1" -msgstr "ラミー・サンパウロ1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:44 -msgid "EU-Paris" -msgstr "ヨーロッパ-パリ" - -#: xpack/plugins/cloud/providers/huaweicloud.py:45 -msgid "CN Southwest-Guiyang1" -msgstr "南西-貴陽1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:46 -msgid "AP-Bangkok" -msgstr "アジア太平洋-バンコク" - -#: xpack/plugins/cloud/providers/huaweicloud.py:50 -msgid "CN Northeast-Dalian" -msgstr "华北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "CN North-Ulanqab1" msgstr "華北-ウランチャブ一" -#: xpack/plugins/cloud/providers/huaweicloud.py:52 +#: xpack/plugins/cloud/providers/huaweicloud.py:43 +msgid "CN South-Shenzhen" +msgstr "華南-広州" + +#: xpack/plugins/cloud/providers/huaweicloud.py:44 msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 +#: xpack/plugins/cloud/providers/huaweicloud.py:45 +msgid "CN East-Shanghai2" +msgstr "華東-上海2" + +#: xpack/plugins/cloud/providers/huaweicloud.py:46 +msgid "CN East-Shanghai1" +msgstr "華東-上海1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:48 +msgid "CN Southwest-Guiyang1" +msgstr "南西-貴陽1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:50 +msgid "AP-Bangkok" +msgstr "アジア太平洋-バンコク" + +#: xpack/plugins/cloud/providers/huaweicloud.py:53 +msgid "AF-Johannesburg" +msgstr "アフリカ-ヨハネスブルク" + +#: xpack/plugins/cloud/providers/huaweicloud.py:54 +msgid "LA-Mexico City1" +msgstr "LA-メキシコCity1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:55 +msgid "LA-Santiago" +msgstr "ラテンアメリカ-サンディエゴ" + +#: xpack/plugins/cloud/providers/huaweicloud.py:56 +msgid "LA-Sao Paulo1" +msgstr "ラミー・サンパウロ1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:58 +msgid "TR-Istanbul" +msgstr "" + +#: xpack/plugins/cloud/providers/jdcloud.py:126 msgid "CN East-Suqian" msgstr "華東-宿遷" @@ -7724,7 +8038,7 @@ msgstr "サブスクリプションID" #: xpack/plugins/cloud/serializers/account_attrs.py:103 #: xpack/plugins/cloud/serializers/account_attrs.py:119 #: xpack/plugins/cloud/serializers/account_attrs.py:149 -#: xpack/plugins/cloud/serializers/account_attrs.py:202 +#: xpack/plugins/cloud/serializers/account_attrs.py:199 msgid "API Endpoint" msgstr "APIエンドポイント" @@ -7790,11 +8104,11 @@ msgstr "テストポート" msgid "Test timeout" msgstr "テストタイムアウト" -#: xpack/plugins/cloud/serializers/account_attrs.py:212 +#: xpack/plugins/cloud/serializers/account_attrs.py:209 msgid "Project" msgstr "project" -#: xpack/plugins/cloud/serializers/task.py:28 +#: xpack/plugins/cloud/serializers/task.py:139 msgid "" "Only instances matching the IP range will be synced.
If the instance " "contains multiple IP addresses, the first IP address that matches will be " @@ -7808,11 +8122,11 @@ msgstr "" "ドレスをランダムに一致させることを意味します。
例: " "192.168.1.0/24,10.1.1.1-10.1.1.20。" -#: xpack/plugins/cloud/serializers/task.py:34 +#: xpack/plugins/cloud/serializers/task.py:145 msgid "History count" msgstr "実行回数" -#: xpack/plugins/cloud/serializers/task.py:35 +#: xpack/plugins/cloud/serializers/task.py:146 msgid "Instance count" msgstr "インスタンス数" @@ -7824,15 +8138,11 @@ msgstr "同期インスタンス タスクを実行する" msgid "Period clean sync instance task execution" msgstr "同期インスタンス タスクの実行記録を定期的にクリアする" -#: xpack/plugins/cloud/utils.py:69 -msgid "Account unavailable" -msgstr "利用できないアカウント" - #: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." msgstr "デフォルトの復元に成功しました。" -#: xpack/plugins/interface/meta.py:10 +#: xpack/plugins/interface/meta.py:9 msgid "Interface settings" msgstr "インターフェイスの設定" @@ -7864,15 +8174,15 @@ msgstr "テーマ" msgid "Interface setting" msgstr "インターフェイスの設定" -#: xpack/plugins/license/api.py:50 +#: xpack/plugins/license/api.py:52 msgid "License import successfully" msgstr "ライセンスのインポートに成功" -#: xpack/plugins/license/api.py:51 +#: xpack/plugins/license/api.py:53 msgid "License is invalid" msgstr "ライセンスが無効です" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 +#: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:138 msgid "License" msgstr "ライセンス" @@ -7891,12 +8201,3 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:86 msgid "Community edition" msgstr "コミュニティ版" - -#~ msgid "Item" -#~ msgstr "アイテム" - -#~ msgid "Url" -#~ msgstr "リンク" - -#~ msgid "Danger command alert" -#~ msgstr "危険コマンドアラート" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 14979766a..16bde8040 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b88bc1c5216d7cfc2b0a72d889198bcab84ddd40dd3f5a13a5662dfcf8170ee -size 121846 +oid sha256:da4f312ed86d27fa8b6bde8da3bc70b0f32fe40811ee855f9fe81d89a68a646f +size 126402 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 8db571768..5f14e8671 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-20 18:40+0800\n" +"POT-Creation-Date: 2023-08-17 15:30+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.3\n" -#: accounts/api/automations/base.py:79 +#: accounts/api/automations/base.py:79 tickets/api/ticket.py:112 msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]" @@ -44,7 +44,7 @@ msgid "Access key" msgstr "Access key" #: accounts/const/account.py:9 assets/models/_user.py:48 -#: authentication/models/sso_token.py:14 +#: authentication/models/sso_token.py:14 settings/serializers/feature.py:50 msgid "Token" msgstr "Token" @@ -52,12 +52,12 @@ msgstr "Token" msgid "API key" msgstr "" -#: accounts/const/account.py:14 common/db/fields.py:244 +#: accounts/const/account.py:14 common/db/fields.py:235 #: settings/serializers/terminal.py:14 msgid "All" msgstr "全部" -#: accounts/const/account.py:15 +#: accounts/const/account.py:15 accounts/models/virtual.py:26 msgid "Manual input" msgstr "手动输入" @@ -69,32 +69,32 @@ msgstr "同名账号" msgid "Anonymous account" msgstr "匿名账号" -#: accounts/const/account.py:21 users/models/user.py:699 +#: accounts/const/account.py:25 users/models/user.py:733 msgid "Local" msgstr "数据库" -#: accounts/const/account.py:22 +#: accounts/const/account.py:26 msgid "Collected" msgstr "收集" -#: accounts/const/account.py:23 accounts/serializers/account/account.py:27 +#: accounts/const/account.py:27 accounts/serializers/account/account.py:27 #: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "模板" -#: accounts/const/account.py:27 ops/const.py:45 +#: accounts/const/account.py:31 ops/const.py:45 msgid "Skip" msgstr "跳过" -#: accounts/const/account.py:28 audits/const.py:24 rbac/tree.py:230 +#: accounts/const/account.py:32 audits/const.py:24 rbac/tree.py:233 #: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" -#: accounts/const/account.py:29 +#: accounts/const/account.py:33 #: accounts/serializers/automations/change_secret.py:156 audits/const.py:54 #: audits/signal_handlers/activity_log.py:33 common/const/choices.py:19 -#: ops/const.py:58 terminal/const.py:77 xpack/plugins/cloud/const.py:43 +#: ops/const.py:61 terminal/const.py:77 xpack/plugins/cloud/const.py:43 msgid "Failed" msgstr "失败" @@ -186,125 +186,126 @@ msgstr "创建并推送" msgid "Only create" msgstr "仅创建" -#: accounts/models/account.py:49 +#: accounts/const/vault.py:8 assets/const/category.py:12 +#: assets/models/asset/database.py:9 assets/models/asset/database.py:24 +msgid "Database" +msgstr "数据库" + +#: accounts/const/vault.py:9 settings/serializers/feature.py:41 +msgid "HCP Vault" +msgstr "HashiCorp Vault" + +#: accounts/models/account.py:48 #: accounts/models/automations/gather_account.py:16 -#: accounts/serializers/account/account.py:200 -#: accounts/serializers/account/account.py:237 +#: accounts/serializers/account/account.py:202 +#: accounts/serializers/account/account.py:247 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/serializers/base.py:123 assets/models/asset/common.py:93 -#: assets/models/asset/common.py:331 assets/models/cmd_filter.py:36 +#: assets/models/asset/common.py:334 assets/models/cmd_filter.py:36 #: assets/serializers/domain.py:19 assets/serializers/label.py:27 #: audits/models.py:53 authentication/models/connection_token.py:36 #: perms/models/asset_permission.py:64 perms/serializers/permission.py:34 -#: terminal/backends/command/models.py:18 terminal/models/session/session.py:31 -#: terminal/notifications.py:155 terminal/serializers/command.py:18 +#: terminal/backends/command/models.py:17 terminal/models/session/session.py:31 +#: terminal/notifications.py:155 terminal/serializers/command.py:17 +#: terminal/serializers/session.py:26 #: terminal/templates/terminal/_msg_command_warning.html:4 -#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212 +#: terminal/templates/terminal/_msg_session_sharing.html:4 +#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:253 msgid "Asset" msgstr "资产" -#: accounts/models/account.py:53 accounts/models/account.py:126 -#: accounts/serializers/account/account.py:208 -#: accounts/serializers/account/account.py:247 +#: accounts/models/account.py:52 accounts/models/template.py:15 +#: accounts/serializers/account/account.py:209 +#: accounts/serializers/account/account.py:257 #: accounts/serializers/account/template.py:16 #: authentication/serializers/connect_token_secret.py:49 msgid "Su from" msgstr "切换自" -#: accounts/models/account.py:55 settings/serializers/auth/cas.py:20 +#: accounts/models/account.py:54 settings/serializers/auth/cas.py:20 #: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:34 msgid "Version" msgstr "版本" -#: accounts/models/account.py:57 accounts/serializers/account/account.py:203 -#: users/models/user.py:804 +#: accounts/models/account.py:56 accounts/serializers/account/account.py:204 +#: users/models/user.py:838 msgid "Source" msgstr "来源" -#: accounts/models/account.py:58 +#: accounts/models/account.py:57 msgid "Source ID" msgstr "来源 ID" -#: accounts/models/account.py:61 +#: accounts/models/account.py:60 #: accounts/serializers/automations/change_secret.py:113 #: accounts/serializers/automations/change_secret.py:133 #: acls/serializers/base.py:124 assets/serializers/asset/common.py:125 #: assets/serializers/gateway.py:28 audits/models.py:54 ops/models/base.py:18 #: perms/models/asset_permission.py:70 perms/serializers/permission.py:39 -#: terminal/backends/command/models.py:19 terminal/models/session/session.py:33 +#: terminal/backends/command/models.py:18 terminal/models/session/session.py:33 #: terminal/templates/terminal/_msg_command_warning.html:8 -#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85 +#: terminal/templates/terminal/_msg_session_sharing.html:8 +#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:89 msgid "Account" msgstr "账号" -#: accounts/models/account.py:67 +#: accounts/models/account.py:66 msgid "Can view asset account secret" msgstr "可以查看资产账号密码" -#: accounts/models/account.py:68 +#: accounts/models/account.py:67 msgid "Can view asset history account" msgstr "可以查看资产历史账号" -#: accounts/models/account.py:69 +#: accounts/models/account.py:68 msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" -#: accounts/models/account.py:70 +#: accounts/models/account.py:69 msgid "Can verify account" msgstr "可以验证账号" -#: accounts/models/account.py:71 +#: accounts/models/account.py:70 msgid "Can push account" msgstr "可以推送账号" -#: accounts/models/account.py:130 -msgid "Account template" -msgstr "账号模版" - -#: accounts/models/account.py:135 -msgid "Can view asset account template secret" -msgstr "可以查看资产账号模版密码" - -#: accounts/models/account.py:136 -msgid "Can change asset account template secret" -msgstr "可以更改资产账号模版密码" - #: accounts/models/automations/backup_account.py:27 -#: accounts/models/automations/change_secret.py:64 -#: accounts/serializers/account/backup.py:34 -#: accounts/serializers/automations/change_secret.py:57 -msgid "Recipient" -msgstr "收件人" +msgid "Recipient part one" +msgstr "收件人部分一" -#: accounts/models/automations/backup_account.py:36 -#: accounts/models/automations/backup_account.py:102 +#: accounts/models/automations/backup_account.py:31 +msgid "Recipient part two" +msgstr "收件人部分二" + +#: accounts/models/automations/backup_account.py:40 +#: accounts/models/automations/backup_account.py:110 msgid "Account backup plan" msgstr "账号备份计划" -#: accounts/models/automations/backup_account.py:83 +#: accounts/models/automations/backup_account.py:91 #: assets/models/automations/base.py:115 audits/models.py:60 -#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:194 +#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:228 #: ops/templates/ops/celery_task_log.html:75 -#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:137 +#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:140 #: terminal/models/session/session.py:44 #: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 msgid "Date start" msgstr "开始日期" -#: accounts/models/automations/backup_account.py:86 +#: accounts/models/automations/backup_account.py:94 #: authentication/templates/authentication/_msg_oauth_bind.html:11 #: notifications/notifications.py:186 msgid "Time" msgstr "时间" -#: accounts/models/automations/backup_account.py:90 +#: accounts/models/automations/backup_account.py:98 msgid "Account backup snapshot" msgstr "账号备份快照" -#: accounts/models/automations/backup_account.py:94 +#: accounts/models/automations/backup_account.py:102 #: accounts/serializers/account/backup.py:42 #: accounts/serializers/automations/base.py:55 #: assets/models/automations/base.py:122 @@ -312,19 +313,19 @@ msgstr "账号备份快照" msgid "Trigger mode" msgstr "触发模式" -#: accounts/models/automations/backup_account.py:97 audits/models.py:194 -#: terminal/models/session/sharing.py:111 xpack/plugins/cloud/models.py:168 +#: accounts/models/automations/backup_account.py:105 audits/models.py:194 +#: terminal/models/session/sharing.py:121 xpack/plugins/cloud/models.py:205 msgid "Reason" msgstr "原因" -#: accounts/models/automations/backup_account.py:99 +#: accounts/models/automations/backup_account.py:107 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:134 -#: ops/serializers/job.py:56 terminal/serializers/session.py:43 +#: ops/serializers/job.py:56 terminal/serializers/session.py:46 msgid "Is success" msgstr "是否成功" -#: accounts/models/automations/backup_account.py:107 +#: accounts/models/automations/backup_account.py:115 msgid "Account backup execution" msgstr "账号备份执行" @@ -365,7 +366,7 @@ msgid "Can add push account execution" msgstr "创建推送账号执行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:419 +#: accounts/serializers/account/account.py:429 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 #: authentication/serializers/connect_token_secret.py:41 @@ -374,8 +375,7 @@ msgid "Secret type" msgstr "密文类型" #: accounts/models/automations/change_secret.py:20 -#: accounts/models/automations/change_secret.py:89 accounts/models/base.py:38 -#: accounts/serializers/account/base.py:19 +#: accounts/models/mixins/vault.py:48 accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 @@ -395,13 +395,23 @@ msgstr "密码规则" msgid "SSH key change strategy" msgstr "SSH 密钥推送方式" +#: accounts/models/automations/change_secret.py:64 +#: accounts/serializers/account/backup.py:34 +#: accounts/serializers/automations/change_secret.py:57 +msgid "Recipient" +msgstr "收件人" + #: accounts/models/automations/change_secret.py:71 msgid "Change secret automation" msgstr "自动化改密" #: accounts/models/automations/change_secret.py:88 msgid "Old secret" -msgstr "原密码" +msgstr "原密钥" + +#: accounts/models/automations/change_secret.py:89 +msgid "New secret" +msgstr "新密钥" #: accounts/models/automations/change_secret.py:90 msgid "Date started" @@ -409,15 +419,15 @@ msgstr "开始日期" #: accounts/models/automations/change_secret.py:91 #: assets/models/automations/base.py:116 ops/models/base.py:56 -#: ops/models/celery.py:64 ops/models/job.py:195 -#: terminal/models/applet/host.py:138 +#: ops/models/celery.py:64 ops/models/job.py:229 +#: terminal/models/applet/host.py:141 msgid "Date finished" msgstr "结束日期" #: accounts/models/automations/change_secret.py:93 -#: accounts/serializers/account/account.py:239 assets/const/automation.py:8 -#: authentication/views/base.py:29 authentication/views/base.py:30 -#: authentication/views/base.py:31 common/const/choices.py:20 +#: accounts/serializers/account/account.py:249 assets/const/automation.py:8 +#: authentication/views/base.py:26 authentication/views/base.py:27 +#: authentication/views/base.py:28 common/const/choices.py:20 msgid "Error" msgstr "错误" @@ -435,13 +445,14 @@ msgstr "最后登录日期" #: accounts/models/automations/gather_account.py:17 #: accounts/models/automations/push_account.py:15 accounts/models/base.py:34 -#: acls/serializers/base.py:19 acls/serializers/base.py:50 -#: assets/models/_user.py:23 audits/models.py:179 authentication/forms.py:25 -#: authentication/forms.py:27 authentication/models/temp_token.py:9 +#: accounts/serializers/account/virtual.py:21 acls/serializers/base.py:19 +#: acls/serializers/base.py:50 assets/models/_user.py:23 audits/models.py:179 +#: authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/forms/profile.py:115 -#: users/models/user.py:751 users/templates/users/_msg_user_created.html:12 +#: users/models/user.py:785 users/templates/users/_msg_user_created.html:12 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" msgstr "用户名" @@ -469,7 +480,7 @@ msgstr "触发方式" #: accounts/models/automations/push_account.py:16 acls/models/base.py:41 #: acls/serializers/base.py:57 assets/models/cmd_filter.py:81 -#: audits/models.py:87 audits/serializers.py:82 +#: audits/models.py:87 audits/serializers.py:83 #: authentication/serializers/connect_token_secret.py:116 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" @@ -483,42 +494,88 @@ msgstr "账号推送" msgid "Verify asset account" msgstr "账号验证" -#: accounts/models/base.py:33 acls/models/base.py:35 acls/models/base.py:96 -#: acls/models/command_acl.py:21 acls/serializers/base.py:35 -#: applications/models.py:9 assets/models/_user.py:22 -#: assets/models/asset/common.py:91 assets/models/asset/common.py:149 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:18 -#: assets/models/group.py:17 assets/models/label.py:18 -#: assets/models/platform.py:15 assets/models/platform.py:88 -#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:110 -#: assets/serializers/platform.py:223 +#: accounts/models/base.py:33 accounts/serializers/account/virtual.py:20 +#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21 +#: acls/serializers/base.py:35 applications/models.py:9 +#: assets/models/_user.py:22 assets/models/asset/common.py:91 +#: assets/models/asset/common.py:149 assets/models/cmd_filter.py:21 +#: assets/models/domain.py:18 assets/models/group.py:17 +#: assets/models/label.py:18 assets/models/platform.py:15 +#: assets/models/platform.py:88 assets/serializers/asset/common.py:146 +#: assets/serializers/platform.py:110 assets/serializers/platform.py:223 #: authentication/serializers/connect_token_secret.py:110 ops/mixin.py:21 -#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 -#: ops/models/job.py:94 ops/models/playbook.py:23 ops/serializers/job.py:20 -#: orgs/models.py:80 perms/models/asset_permission.py:56 rbac/models/role.py:29 -#: settings/models.py:33 settings/serializers/sms.py:6 +#: ops/models/adhoc.py:20 ops/models/celery.py:15 ops/models/celery.py:57 +#: ops/models/job.py:126 ops/models/playbook.py:28 ops/serializers/job.py:20 +#: orgs/models.py:82 perms/models/asset_permission.py:56 rbac/models/role.py:29 +#: settings/models.py:32 settings/serializers/msg.py:82 #: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12 -#: terminal/models/component/endpoint.py:92 -#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 +#: terminal/models/component/endpoint.py:94 +#: terminal/models/component/storage.py:26 terminal/models/component/task.py:13 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33 -#: users/models/group.py:13 users/models/user.py:753 -#: xpack/plugins/cloud/models.py:28 +#: users/models/group.py:13 users/models/user.py:787 +#: xpack/plugins/cloud/models.py:32 xpack/plugins/cloud/models.py:273 +#: xpack/plugins/cloud/serializers/task.py:68 msgid "Name" msgstr "名称" -#: accounts/models/base.py:39 +#: accounts/models/base.py:38 msgid "Privileged" msgstr "特权账号" -#: accounts/models/base.py:40 assets/models/asset/common.py:156 +#: accounts/models/base.py:39 assets/models/asset/common.py:156 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:114 #: terminal/models/applet/applet.py:39 -#: terminal/models/component/endpoint.py:103 users/serializers/user.py:169 +#: terminal/models/component/endpoint.py:105 users/serializers/user.py:169 msgid "Is active" msgstr "激活" +#: accounts/models/template.py:19 xpack/plugins/cloud/models.py:325 +msgid "Account template" +msgstr "账号模版" + +# msgid "Account template" +# msgstr "账号模版" +#: accounts/models/template.py:24 +msgid "Can view asset account template secret" +msgstr "可以查看资产账号模版密码" + +#: accounts/models/template.py:25 +msgid "Can change asset account template secret" +msgstr "可以更改资产账号模版密码" + +# msgid "Can view asset account template secret" +# msgstr "可以查看资产账号模版密码" +# msgid "Can change asset account template secret" +# msgstr "可以更改资产账号模版密码" +#: accounts/models/virtual.py:13 +msgid "Alias" +msgstr "别名" + +#: accounts/models/virtual.py:14 +msgid "Secret from login" +msgstr "与用户登录时相同" + +#: accounts/models/virtual.py:27 +msgid "Same with user" +msgstr "用户名与用户相同" + +#: accounts/models/virtual.py:36 +msgid "Non-asset account, Input username/password on connect" +msgstr "登录时手动输入 用户名/密码 来连接的账号" + +#: accounts/models/virtual.py:37 +msgid "The account username name same with user on connect" +msgstr "登录资产时,账号用户名与使用者用户名相同的账号" + +#: accounts/models/virtual.py:38 +msgid "" +"Connect asset without using a username and password, and it only supports " +"web-based and custom-type assets" +msgstr "" +"连接资产时不使用用户名和密码的账号,仅支持 web类型 和 自定义类型 的资产" + #: accounts/notifications.py:8 msgid "Notification of account backup route task results" msgstr "账号备份任务结果通知" @@ -565,84 +622,83 @@ msgstr "立即推送" msgid "Exist policy" msgstr "账号存在策略" -#: accounts/serializers/account/account.py:180 applications/models.py:11 +#: accounts/serializers/account/account.py:182 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:89 #: assets/serializers/asset/common.py:121 assets/serializers/cagegory.py:8 #: assets/serializers/platform.py:128 assets/serializers/platform.py:224 -#: perms/serializers/user_permission.py:26 settings/models.py:35 +#: perms/serializers/user_permission.py:26 settings/models.py:34 #: tickets/models/ticket/apply_application.py:13 msgid "Category" msgstr "类别" -#: accounts/serializers/account/account.py:181 +#: accounts/serializers/account/account.py:183 #: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24 #: acls/serializers/command_acl.py:18 applications/models.py:14 #: assets/models/_user.py:50 assets/models/automations/base.py:20 #: assets/models/cmd_filter.py:74 assets/models/platform.py:90 #: assets/serializers/asset/common.py:122 assets/serializers/platform.py:112 -#: assets/serializers/platform.py:127 audits/serializers.py:48 -#: authentication/serializers/connect_token_secret.py:123 ops/models/job.py:105 +#: assets/serializers/platform.py:127 audits/serializers.py:49 +#: authentication/serializers/connect_token_secret.py:123 ops/models/job.py:137 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:38 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 -#: terminal/serializers/session.py:20 terminal/serializers/storage.py:224 -#: terminal/serializers/storage.py:236 tickets/models/comment.py:26 +#: terminal/serializers/session.py:21 terminal/serializers/storage.py:226 +#: terminal/serializers/storage.py:238 tickets/models/comment.py:26 #: tickets/models/flow.py:56 tickets/models/ticket/apply_application.py:16 #: tickets/models/ticket/general.py:275 tickets/serializers/flow.py:53 #: tickets/serializers/ticket/ticket.py:19 msgid "Type" msgstr "类型" -#: accounts/serializers/account/account.py:196 +#: accounts/serializers/account/account.py:198 msgid "Asset not found" msgstr "资产不存在" -#: accounts/serializers/account/account.py:201 -#: accounts/serializers/account/base.py:64 +#: accounts/serializers/account/account.py:238 msgid "Has secret" msgstr "已托管密码" -#: accounts/serializers/account/account.py:238 ops/models/celery.py:60 +#: accounts/serializers/account/account.py:248 ops/models/celery.py:60 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14 #: tickets/serializers/ticket/ticket.py:21 msgid "State" msgstr "状态" -#: accounts/serializers/account/account.py:240 +#: accounts/serializers/account/account.py:250 msgid "Changed" msgstr "已修改" -#: accounts/serializers/account/account.py:250 +#: accounts/serializers/account/account.py:260 #: accounts/serializers/automations/base.py:22 acls/models/base.py:97 #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 ops/models/base.py:17 -#: ops/models/job.py:107 ops/serializers/job.py:21 +#: ops/models/job.py:139 ops/serializers/job.py:21 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 msgid "Assets" msgstr "资产" -#: accounts/serializers/account/account.py:305 +#: accounts/serializers/account/account.py:315 msgid "Account already exists" msgstr "账号已存在" -#: accounts/serializers/account/account.py:355 +#: accounts/serializers/account/account.py:365 #, python-format msgid "Asset does not support this secret type: %s" msgstr "资产不支持账号类型: %s" -#: accounts/serializers/account/account.py:387 +#: accounts/serializers/account/account.py:397 msgid "Account has exist" msgstr "账号已存在" -#: accounts/serializers/account/account.py:420 +#: accounts/serializers/account/account.py:430 #: authentication/serializers/connect_token_secret.py:156 #: authentication/templates/authentication/_access_key_modal.html:30 #: perms/models/perm_node.py:21 users/serializers/group.py:31 msgid "ID" msgstr "ID" -#: accounts/serializers/account/account.py:427 acls/serializers/base.py:116 +#: accounts/serializers/account/account.py:437 acls/serializers/base.py:116 #: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49 #: audits/models.py:85 audits/models.py:163 #: authentication/models/connection_token.py:32 @@ -650,17 +706,18 @@ msgstr "ID" #: notifications/models/notification.py:12 #: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 #: perms/serializers/permission.py:30 rbac/builtin.py:123 -#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:17 -#: terminal/models/session/session.py:29 terminal/models/session/sharing.py:32 -#: terminal/notifications.py:156 terminal/notifications.py:205 -#: terminal/serializers/command.py:17 +#: rbac/models/rolebinding.py:49 rbac/serializers/rolebinding.py:17 +#: terminal/backends/command/models.py:16 terminal/models/session/session.py:29 +#: terminal/models/session/sharing.py:33 terminal/notifications.py:156 +#: terminal/notifications.py:205 terminal/serializers/command.py:16 #: terminal/templates/terminal/_msg_command_warning.html:6 -#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:947 -#: users/models/user.py:978 users/serializers/group.py:18 +#: terminal/templates/terminal/_msg_session_sharing.html:6 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:981 +#: users/models/user.py:1017 users/serializers/group.py:18 msgid "User" msgstr "用户" -#: accounts/serializers/account/account.py:428 +#: accounts/serializers/account/account.py:438 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:158 terminal/notifications.py:207 msgid "Date" @@ -691,12 +748,12 @@ msgstr "资产类型" msgid "Key password" msgstr "密钥密码" -#: accounts/serializers/account/base.py:80 -#: assets/serializers/asset/common.py:309 +#: accounts/serializers/account/base.py:78 +#: assets/serializers/asset/common.py:311 msgid "Spec info" msgstr "特殊信息" -#: accounts/serializers/account/base.py:82 +#: accounts/serializers/account/base.py:80 msgid "" "Tip: If no username is required for authentication, fill in `null`, If AD " "account, like `username@domain`" @@ -704,6 +761,28 @@ msgstr "" "提示: 如果认证时不需要用户名,可填写为 null, 如果是 AD 账号,格式为 " "username@domain" +#: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 +#: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 +#: ops/models/job.py:145 ops/models/playbook.py:31 rbac/models/role.py:37 +#: settings/models.py:37 terminal/models/applet/applet.py:44 +#: terminal/models/applet/applet.py:284 terminal/models/applet/host.py:142 +#: terminal/models/component/endpoint.py:24 +#: terminal/models/component/endpoint.py:104 +#: terminal/models/session/session.py:46 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:297 users/models/user.py:826 +#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/models.py:109 +msgid "Comment" +msgstr "备注" + +#: accounts/serializers/account/virtual.py:22 +msgid "" +"Current only support login from AD/LDAP. Secret priority: Same account in " +"asset secret > Login secret > Manual input" +msgstr "" +"当前仅支持 AD/LDAP 登录方式用户。 同名账号密码生效顺序: 资产上存在的同名账号" +"密码 > 登录密码 > 手动输入" + #: accounts/serializers/automations/base.py:23 #: assets/models/asset/common.py:155 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21 @@ -740,9 +819,9 @@ msgstr "自动化任务执行历史" #: accounts/serializers/automations/change_secret.py:155 audits/const.py:53 #: audits/models.py:59 audits/signal_handlers/activity_log.py:33 -#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:40 -#: terminal/const.py:76 terminal/models/session/sharing.py:107 -#: tickets/views/approve.py:114 +#: common/const/choices.py:18 ops/const.py:59 ops/serializers/celery.py:40 +#: terminal/const.py:76 terminal/models/session/sharing.py:117 +#: tickets/views/approve.py:115 msgid "Success" msgstr "成功" @@ -762,6 +841,10 @@ msgstr "收集资产上的账号" msgid "Push accounts to assets" msgstr "推送账号到资产" +#: accounts/tasks/vault.py:31 +msgid "Sync secret to vault" +msgstr "同步密文到 vault" + #: accounts/tasks/verify_account.py:49 msgid "Verify asset account availability" msgstr "验证资产账号可用性" @@ -770,19 +853,19 @@ msgstr "验证资产账号可用性" msgid "Verify accounts connectivity" msgstr "测试账号可连接性" -#: accounts/utils.py:43 +#: accounts/utils.py:41 msgid "Password can not contains `{{` " msgstr "密码不能包含 `{{` 字符" -#: accounts/utils.py:46 +#: accounts/utils.py:44 msgid "Password can not contains `'` " msgstr "密码不能包含 `'` 字符" -#: accounts/utils.py:48 +#: accounts/utils.py:46 msgid "Password can not contains `\"` " msgstr "密码不能包含 `\"` 字符" -#: accounts/utils.py:54 +#: accounts/utils.py:52 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" @@ -808,12 +891,14 @@ msgid "Warning" msgstr "告警" #: acls/models/base.py:37 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:95 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:97 +#: xpack/plugins/cloud/models.py:275 msgid "Priority" msgstr "优先级" #: acls/models/base.py:38 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:96 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:98 +#: xpack/plugins/cloud/models.py:276 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -825,7 +910,7 @@ msgstr "审批人" #: acls/models/base.py:43 authentication/models/access_key.py:17 #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:27 +#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" msgstr "激活中" @@ -835,14 +920,14 @@ msgid "Users" msgstr "用户管理" #: acls/models/base.py:98 assets/models/automations/base.py:17 -#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:308 +#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:310 #: rbac/tree.py:35 msgid "Accounts" msgstr "账号管理" #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60 #: ops/serializers/job.py:55 terminal/const.py:84 -#: terminal/models/session/session.py:42 terminal/serializers/command.py:19 +#: terminal/models/session/session.py:42 terminal/serializers/command.py:18 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 #: terminal/templates/terminal/_msg_command_warning.html:23 @@ -850,11 +935,12 @@ msgid "Command" msgstr "命令" #: acls/models/command_acl.py:17 assets/models/cmd_filter.py:59 +#: xpack/plugins/cloud/models.py:291 msgid "Regex" msgstr "正则表达式" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 +#: settings/serializers/feature.py:17 xpack/plugins/license/models.py:30 msgid "Content" msgstr "内容" @@ -926,7 +1012,7 @@ msgid "" "support)" msgstr "" "* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:" -"db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" +"db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" #: acls/serializers/base.py:41 assets/serializers/asset/host.py:19 msgid "IP/Host" @@ -945,7 +1031,7 @@ msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" #: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:22 +#: xpack/plugins/cloud/serializers/task.py:133 msgid "IP address invalid: `{}`" msgstr "IP 地址无效: `{}`" @@ -973,7 +1059,7 @@ msgstr "时段" msgid "Applications" msgstr "应用管理" -#: applications/models.py:16 xpack/plugins/cloud/models.py:33 +#: applications/models.py:16 xpack/plugins/cloud/models.py:37 #: xpack/plugins/cloud/serializers/account.py:63 msgid "Attrs" msgstr "属性" @@ -990,7 +1076,7 @@ msgstr "匹配应用" msgid "Cannot create asset directly, you should create a host or other" msgstr "不能直接创建资产, 你应该创建主机或其他资产" -#: assets/api/domain.py:62 +#: assets/api/domain.py:63 msgid "Number required" msgstr "需要为数字" @@ -1033,7 +1119,7 @@ msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {port} 上的端口 {address}" #: assets/automations/ping_gateway/manager.py:58 -#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:48 +#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:47 msgid "Authentication failed" msgstr "认证失败" @@ -1069,18 +1155,19 @@ msgstr "收集资产信息" msgid "Disabled" msgstr "禁用" -#: assets/const/base.py:34 settings/serializers/basic.py:27 +#: assets/const/base.py:34 settings/serializers/basic.py:6 msgid "Basic" msgstr "基本" -#: assets/const/base.py:35 assets/const/protocol.py:193 +#: assets/const/base.py:35 assets/const/protocol.py:230 #: assets/models/asset/web.py:13 msgid "Script" msgstr "脚本" #: assets/const/category.py:10 assets/models/asset/host.py:8 #: settings/serializers/auth/radius.py:16 settings/serializers/auth/sms.py:67 -#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17 +#: settings/serializers/feature.py:47 terminal/models/component/endpoint.py:13 +#: terminal/serializers/applet.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -1089,11 +1176,6 @@ msgstr "主机" msgid "Device" msgstr "网络设备" -#: assets/const/category.py:12 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 -msgid "Database" -msgstr "数据库" - #: assets/const/category.py:13 msgid "Cloud service" msgstr "云服务" @@ -1145,10 +1227,6 @@ msgstr "ChatGPT" msgid "Other" msgstr "其它" -#: assets/const/protocol.py:43 -msgid "SFTP enabled" -msgstr "SFTP 已启用" - #: assets/const/protocol.py:48 msgid "SFTP home" msgstr "SFTP 根路径" @@ -1165,7 +1243,7 @@ msgstr "连接到控制台会话" msgid "Any" msgstr "任意" -#: assets/const/protocol.py:66 settings/serializers/security.py:151 +#: assets/const/protocol.py:66 settings/serializers/security.py:227 msgid "Security" msgstr "安全" @@ -1177,33 +1255,76 @@ msgstr "连接 RDP 使用的安全层" msgid "AD domain" msgstr "AD 网域" -#: assets/const/protocol.py:92 assets/models/asset/database.py:10 -#: settings/serializers/email.py:37 +#: assets/const/protocol.py:88 +msgid "Username prompt" +msgstr "用户名提示" + +#: assets/const/protocol.py:89 +msgid "We will send username when we see this prompt" +msgstr "当我们看到这个提示时,我们将发送用户名" + +#: assets/const/protocol.py:94 +msgid "Password prompt" +msgstr "密码提示" + +#: assets/const/protocol.py:95 +msgid "We will send password when we see this prompt" +msgstr "当我们看到这个提示时,我们将发送密码" + +#: assets/const/protocol.py:100 +msgid "Success prompt" +msgstr "成功提示" + +#: assets/const/protocol.py:101 +msgid "We will consider login success when we see this prompt" +msgstr "当我们看到这个提示时,我们将认为登录成功" + +#: assets/const/protocol.py:112 assets/models/asset/database.py:10 +#: settings/serializers/msg.py:40 msgid "Use SSL" msgstr "使用 SSL" -#: assets/const/protocol.py:149 +#: assets/const/protocol.py:147 +msgid "SYSDBA" +msgstr "SYSDBA" + +#: assets/const/protocol.py:148 +msgid "Connect as SYSDBA" +msgstr "以 SYSDBA 角色连接" + +#: assets/const/protocol.py:177 msgid "Auth username" msgstr "使用用户名认证" -#: assets/const/protocol.py:170 assets/models/asset/web.py:9 +#: assets/const/protocol.py:200 +msgid "Safe mode" +msgstr "安全模式" + +#: assets/const/protocol.py:202 +msgid "" +"When safe mode is enabled, some operations will be disabled, such as: New " +"tab, right click, visit other website, etc." +msgstr "" +"当安全模式启用时,一些操作将被禁用,例如:新建标签页、右键、访问其它网站 等" + +#: assets/const/protocol.py:207 assets/models/asset/web.py:9 #: assets/serializers/asset/info/spec.py:16 msgid "Autofill" msgstr "自动代填" -#: assets/const/protocol.py:178 assets/models/asset/web.py:10 +#: assets/const/protocol.py:215 assets/models/asset/web.py:10 msgid "Username selector" msgstr "用户名选择器" -#: assets/const/protocol.py:183 assets/models/asset/web.py:11 +#: assets/const/protocol.py:220 assets/models/asset/web.py:11 msgid "Password selector" msgstr "密码选择器" -#: assets/const/protocol.py:188 assets/models/asset/web.py:12 +#: assets/const/protocol.py:225 assets/models/asset/web.py:12 msgid "Submit selector" msgstr "确认按钮选择器" -#: assets/const/protocol.py:211 +#: assets/const/protocol.py:248 msgid "API mode" msgstr "API 模式" @@ -1227,34 +1348,23 @@ msgstr "SSH密钥" msgid "SSH public key" msgstr "SSH公钥" -#: assets/models/_user.py:27 assets/models/cmd_filter.py:40 -#: assets/models/cmd_filter.py:88 assets/models/group.py:20 -#: common/db/models.py:36 ops/models/adhoc.py:27 ops/models/job.py:113 -#: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:248 -#: terminal/models/applet/host.py:139 terminal/models/component/endpoint.py:24 -#: terminal/models/component/endpoint.py:102 -#: terminal/models/session/session.py:46 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:297 users/models/user.py:792 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:111 -msgid "Comment" -msgstr "备注" - +# msgid "Comment" +# msgstr "备注" #: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/cmd_filter.py:41 assets/models/group.py:19 -#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:193 -#: users/models/user.py:979 +#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:227 +#: users/models/user.py:1018 msgid "Date created" msgstr "创建日期" #: assets/models/_user.py:29 assets/models/cmd_filter.py:42 -#: common/db/models.py:35 users/models/user.py:813 +#: common/db/models.py:35 users/models/user.py:847 msgid "Date updated" msgstr "更新日期" #: assets/models/_user.py:30 assets/models/cmd_filter.py:44 #: assets/models/cmd_filter.py:91 assets/models/group.py:18 -#: common/db/models.py:32 users/models/user.py:799 +#: common/db/models.py:32 users/models/user.py:833 #: users/serializers/group.py:29 msgid "Created by" msgstr "创建者" @@ -1281,8 +1391,8 @@ msgstr "用户名与用户相同" #: assets/models/_user.py:52 authentication/models/connection_token.py:41 #: authentication/serializers/connect_token_secret.py:111 -#: terminal/models/applet/applet.py:41 terminal/serializers/session.py:18 -#: terminal/serializers/session.py:39 terminal/serializers/storage.py:68 +#: terminal/models/applet/applet.py:41 terminal/serializers/session.py:19 +#: terminal/serializers/session.py:42 terminal/serializers/storage.py:70 msgid "Protocol" msgstr "协议" @@ -1346,14 +1456,13 @@ msgstr "地址" #: assets/models/asset/common.py:151 assets/models/platform.py:119 #: authentication/serializers/connect_token_secret.py:115 -#: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:196 +#: perms/serializers/user_permission.py:24 xpack/plugins/cloud/models.py:321 msgid "Platform" msgstr "系统平台" #: assets/models/asset/common.py:153 assets/models/domain.py:21 #: authentication/serializers/connect_token_secret.py:133 -#: perms/serializers/user_permission.py:29 +#: perms/serializers/user_permission.py:29 xpack/plugins/cloud/models.py:323 msgid "Domain" msgstr "网域" @@ -1361,7 +1470,7 @@ msgstr "网域" msgid "Labels" msgstr "标签管理" -#: assets/models/asset/common.py:158 assets/serializers/asset/common.py:310 +#: assets/models/asset/common.py:158 assets/serializers/asset/common.py:312 #: assets/serializers/asset/host.py:11 msgid "Gathered info" msgstr "收集资产硬件信息" @@ -1370,19 +1479,19 @@ msgstr "收集资产硬件信息" msgid "Custom info" msgstr "自定义属性" -#: assets/models/asset/common.py:334 +#: assets/models/asset/common.py:337 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset/common.py:335 +#: assets/models/asset/common.py:338 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset/common.py:336 +#: assets/models/asset/common.py:339 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset/common.py:337 +#: assets/models/asset/common.py:340 msgid "Can change asset nodes" msgstr "可以修改资产节点" @@ -1410,7 +1519,7 @@ msgstr "忽略证书校验" msgid "Proxy" msgstr "代理" -#: assets/models/automations/base.py:22 ops/models/job.py:189 +#: assets/models/automations/base.py:22 ops/models/job.py:223 #: settings/serializers/auth/sms.py:99 msgid "Parameters" msgstr "参数" @@ -1424,13 +1533,13 @@ msgid "Asset automation task" msgstr "资产自动化任务" #: assets/models/automations/base.py:113 audits/models.py:199 -#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:186 -#: terminal/models/applet/applet.py:247 terminal/models/applet/host.py:136 +#: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:220 +#: terminal/models/applet/applet.py:283 terminal/models/applet/host.py:139 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 -#: terminal/serializers/applet_host.py:107 tickets/models/ticket/general.py:283 +#: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283 #: tickets/serializers/super_ticket.py:13 -#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164 -#: xpack/plugins/cloud/models.py:216 +#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:201 +#: xpack/plugins/cloud/models.py:257 msgid "Status" msgstr "状态" @@ -1452,7 +1561,7 @@ msgstr "校验日期" #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61 #: perms/serializers/permission.py:32 users/models/group.py:25 -#: users/models/user.py:759 +#: users/models/user.py:793 msgid "User group" msgstr "用户组" @@ -1502,15 +1611,15 @@ msgstr "默认" msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:964 +#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1003 msgid "System" msgstr "系统" -#: assets/models/label.py:19 assets/models/node.py:545 +#: assets/models/label.py:19 assets/models/node.py:544 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:29 #: authentication/serializers/connect_token_secret.py:122 -#: common/serializers/common.py:86 settings/models.py:34 +#: common/serializers/common.py:86 settings/models.py:33 msgid "Value" msgstr "值" @@ -1519,36 +1628,36 @@ msgstr "值" #: assets/serializers/platform.py:111 #: authentication/serializers/connect_token_secret.py:121 #: common/serializers/common.py:85 perms/serializers/user_permission.py:28 -#: settings/serializers/sms.py:7 +#: settings/serializers/msg.py:83 msgid "Label" msgstr "标签" -#: assets/models/node.py:166 +#: assets/models/node.py:165 msgid "New node" msgstr "新节点" -#: assets/models/node.py:473 audits/backends/db.py:55 audits/backends/db.py:56 +#: assets/models/node.py:472 audits/backends/db.py:55 audits/backends/db.py:56 msgid "empty" msgstr "空" -#: assets/models/node.py:544 perms/models/perm_node.py:28 +#: assets/models/node.py:543 perms/models/perm_node.py:28 msgid "Key" msgstr "键" -#: assets/models/node.py:546 assets/serializers/node.py:20 +#: assets/models/node.py:545 assets/serializers/node.py:20 msgid "Full value" msgstr "全称" -#: assets/models/node.py:550 perms/models/perm_node.py:30 +#: assets/models/node.py:549 perms/models/perm_node.py:30 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 perms/serializers/permission.py:35 -#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96 +#: assets/models/node.py:558 perms/serializers/permission.py:35 +#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:322 msgid "Node" msgstr "节点" -#: assets/models/node.py:562 +#: assets/models/node.py:561 msgid "Can match node" msgstr "可以匹配节点" @@ -1565,12 +1674,12 @@ msgid "Public" msgstr "开放的" #: assets/models/platform.py:21 assets/serializers/platform.py:48 -#: settings/serializers/settings.py:67 +#: settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" -#: assets/models/platform.py:38 audits/const.py:48 settings/models.py:37 +#: assets/models/platform.py:38 audits/const.py:48 settings/models.py:36 #: terminal/serializers/applet_host.py:33 msgid "Enabled" msgstr "启用" @@ -1683,7 +1792,8 @@ msgstr "资产中批量更新平台,不符合平台类型跳过的资产" #: assets/serializers/asset/common.py:124 assets/serializers/platform.py:129 #: authentication/serializers/connect_token_secret.py:29 #: authentication/serializers/connect_token_secret.py:72 -#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 +#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:324 +#: xpack/plugins/cloud/serializers/task.py:31 msgid "Protocols" msgstr "协议组" @@ -1693,19 +1803,19 @@ msgid "Node path" msgstr "节点路径" #: assets/serializers/asset/common.py:145 -#: assets/serializers/asset/common.py:311 +#: assets/serializers/asset/common.py:313 msgid "Auto info" msgstr "自动化信息" -#: assets/serializers/asset/common.py:234 +#: assets/serializers/asset/common.py:236 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:270 +#: assets/serializers/asset/common.py:272 msgid "port out of range (0-65535)" msgstr "端口超出范围 (0-65535)" -#: assets/serializers/asset/common.py:277 +#: assets/serializers/asset/common.py:279 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" @@ -1713,8 +1823,8 @@ msgstr "协议是必填的: {}" msgid "Default database" msgstr "默认数据库" -#: assets/serializers/asset/database.py:28 common/db/fields.py:579 -#: common/db/fields.py:584 common/serializers/fields.py:104 +#: assets/serializers/asset/database.py:28 common/db/fields.py:570 +#: common/db/fields.py:575 common/serializers/fields.py:104 #: tickets/serializers/ticket/common.py:58 #: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:79 @@ -1796,7 +1906,7 @@ msgstr "约束" msgid "Types" msgstr "类型" -#: assets/serializers/gateway.py:23 common/validators.py:35 +#: assets/serializers/gateway.py:23 common/validators.py:34 msgid "This field must be unique." msgstr "字段必须唯一" @@ -1918,19 +2028,19 @@ msgstr "测试节点下资产是否可连接" msgid "Test gateways connectivity" msgstr "测试网关可连接性" -#: assets/tasks/utils.py:17 +#: assets/tasks/utils.py:16 msgid "Asset has been disabled, skipped: {}" msgstr "资产已经被禁用, 跳过: {}" -#: assets/tasks/utils.py:21 +#: assets/tasks/utils.py:20 msgid "Asset may not be support ansible, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks/utils.py:39 +#: assets/tasks/utils.py:38 msgid "For security, do not push user {}" msgstr "为了安全,禁止推送用户 {}" -#: assets/tasks/utils.py:55 +#: assets/tasks/utils.py:54 msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" @@ -1956,7 +2066,7 @@ msgstr "删除目录" #: audits/const.py:14 audits/const.py:25 #: authentication/templates/authentication/_access_key_modal.html:65 -#: perms/const.py:17 rbac/tree.py:231 +#: perms/const.py:17 rbac/tree.py:234 msgid "Delete" msgstr "删除" @@ -1980,14 +2090,15 @@ msgstr "下载" msgid "Rename dir" msgstr "映射目录" -#: audits/const.py:23 rbac/tree.py:229 +#: audits/const.py:23 rbac/tree.py:232 #: terminal/templates/terminal/_msg_command_warning.html:18 +#: terminal/templates/terminal/_msg_session_sharing.html:10 msgid "View" msgstr "查看" #: audits/const.py:26 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:228 +#: rbac/tree.py:231 msgid "Create" msgstr "创建" @@ -2006,8 +2117,8 @@ msgid "Change password" msgstr "改密" #: audits/const.py:35 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:163 -#: terminal/serializers/session.py:46 terminal/serializers/session.py:55 +#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:164 +#: terminal/serializers/session.py:49 terminal/serializers/session.py:63 msgid "Terminal" msgstr "终端" @@ -2023,8 +2134,8 @@ msgstr "会话日志" msgid "Login log" msgstr "登录日志" -#: audits/const.py:43 terminal/models/applet/host.py:140 -#: terminal/models/component/task.py:24 +#: audits/const.py:43 terminal/models/applet/host.py:143 +#: terminal/models/component/task.py:22 msgid "Task" msgstr "任务" @@ -2045,11 +2156,11 @@ msgid "Job audit log" msgstr "作业审计日志" #: audits/models.py:51 audits/models.py:95 audits/models.py:166 -#: terminal/models/session/session.py:38 terminal/models/session/sharing.py:99 +#: terminal/models/session/session.py:38 terminal/models/session/sharing.py:109 msgid "Remote addr" msgstr "远端地址" -#: audits/models.py:56 audits/serializers.py:33 +#: audits/models.py:56 audits/serializers.py:34 msgid "Operate" msgstr "操作" @@ -2061,9 +2172,9 @@ msgstr "文件名" msgid "File" msgstr "文件" -#: audits/models.py:62 terminal/backends/command/models.py:22 -#: terminal/models/session/replay.py:9 terminal/models/session/sharing.py:18 -#: terminal/models/session/sharing.py:81 +#: audits/models.py:62 terminal/backends/command/models.py:21 +#: terminal/models/session/replay.py:9 terminal/models/session/sharing.py:19 +#: terminal/models/session/sharing.py:91 #: terminal/templates/terminal/_msg_command_alert.html:10 #: terminal/templates/terminal/_msg_command_warning.html:17 #: tickets/models/ticket/command_confirm.py:15 @@ -2074,17 +2185,17 @@ msgstr "会话" msgid "File transfer log" msgstr "文件管理" -#: audits/models.py:89 audits/serializers.py:84 +#: audits/models.py:89 audits/serializers.py:85 msgid "Resource Type" msgstr "资源类型" #: audits/models.py:90 audits/models.py:93 audits/models.py:139 -#: audits/serializers.py:83 +#: audits/serializers.py:84 msgid "Resource" msgstr "资源" #: audits/models.py:96 audits/models.py:142 audits/models.py:168 -#: terminal/serializers/command.py:76 +#: terminal/serializers/command.py:75 msgid "Datetime" msgstr "日期" @@ -2126,13 +2237,13 @@ msgstr "登录 IP" msgid "Login city" msgstr "登录城市" -#: audits/models.py:188 audits/serializers.py:63 +#: audits/models.py:188 audits/serializers.py:64 msgid "User agent" msgstr "用户代理" -#: audits/models.py:191 audits/serializers.py:47 +#: audits/models.py:191 audits/serializers.py:48 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:65 users/models/user.py:776 +#: users/forms/profile.py:65 users/models/user.py:810 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" @@ -2141,7 +2252,7 @@ msgstr "MFA" msgid "Date login" msgstr "登录日期" -#: audits/models.py:203 audits/serializers.py:65 +#: audits/models.py:203 audits/serializers.py:66 msgid "Authentication backend" msgstr "认证方式" @@ -2149,11 +2260,11 @@ msgstr "认证方式" msgid "User login log" msgstr "用户登录日志" -#: audits/serializers.py:64 +#: audits/serializers.py:65 msgid "Reason display" msgstr "原因描述" -#: audits/serializers.py:132 +#: audits/serializers.py:133 #, python-format msgid "User %s %s this resource" msgstr "用户 %s %s 了当前资源" @@ -2177,7 +2288,7 @@ msgstr "用户 %s 在当前资源, 执行了任务 (%s)" msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signal_handlers/login_log.py:29 settings/serializers/auth/sso.py:10 +#: audits/signal_handlers/login_log.py:29 settings/serializers/auth/sso.py:13 msgid "SSO" msgstr "SSO" @@ -2188,22 +2299,22 @@ msgstr "认证令牌" #: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73 #: authentication/views/login.py:75 authentication/views/wecom.py:159 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 -#: users/models/user.py:706 users/models/user.py:814 +#: users/models/user.py:740 users/models/user.py:848 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:123 +#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:122 #: authentication/views/login.py:87 notifications/backends/__init__.py:14 #: settings/serializers/auth/feishu.py:10 -#: settings/serializers/auth/feishu.py:13 users/models/user.py:708 -#: users/models/user.py:816 +#: settings/serializers/auth/feishu.py:13 users/models/user.py:742 +#: users/models/user.py:850 msgid "FeiShu" msgstr "飞书" -#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:160 +#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:159 #: authentication/views/login.py:81 notifications/backends/__init__.py:12 -#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:707 -#: users/models/user.py:815 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:741 +#: users/models/user.py:849 msgid "DingTalk" msgstr "钉钉" @@ -2224,31 +2335,31 @@ msgstr "上传 FTP 文件到外部存储" msgid "This action require verify your MFA" msgstr "该操作需要验证您的 MFA, 请先开启并配置" -#: authentication/api/connection_token.py:221 +#: authentication/api/connection_token.py:258 msgid "Reusable connection token is not allowed, global setting not enabled" msgstr "不允许使用可重复使用的连接令牌,未启用全局设置" -#: authentication/api/connection_token.py:301 +#: authentication/api/connection_token.py:338 msgid "Anonymous account is not supported for this asset" msgstr "匿名账号不支持当前资产" -#: authentication/api/connection_token.py:323 +#: authentication/api/connection_token.py:357 msgid "Account not found" msgstr "账号未找到" -#: authentication/api/connection_token.py:326 +#: authentication/api/connection_token.py:360 msgid "Permission expired" msgstr "授权已过期" -#: authentication/api/connection_token.py:340 +#: authentication/api/connection_token.py:374 msgid "ACL action is reject: {}({})" msgstr "ACL 动作是拒绝: {}({})" -#: authentication/api/connection_token.py:344 +#: authentication/api/connection_token.py:378 msgid "ACL action is review" msgstr "ACL 动作是复核" -#: authentication/api/mfa.py:59 +#: authentication/api/mfa.py:57 msgid "Current user not support mfa type: {}" msgstr "当前用户不支持 MFA 类型: {}" @@ -2267,7 +2378,7 @@ msgid "" "password" msgstr "用户来自 {} 请去相应系统修改密码" -#: authentication/api/password.py:60 +#: authentication/api/password.py:64 #: authentication/templates/authentication/login.html:305 #: users/templates/users/forgot_password.html:27 #: users/templates/users/forgot_password.html:28 @@ -2285,54 +2396,54 @@ msgstr "认证" msgid "User invalid, disabled or expired" msgstr "用户无效,已禁用或已过期" -#: authentication/backends/drf.py:56 +#: authentication/backends/drf.py:54 msgid "Invalid signature header. No credentials provided." msgstr "不合法的签名头" -#: authentication/backends/drf.py:59 +#: authentication/backends/drf.py:57 msgid "Invalid signature header. Signature string should not contain spaces." msgstr "不合法的签名头" -#: authentication/backends/drf.py:66 +#: authentication/backends/drf.py:64 msgid "Invalid signature header. Format like AccessKeyId:Signature" msgstr "不合法的签名头" -#: authentication/backends/drf.py:70 +#: authentication/backends/drf.py:68 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "不合法的签名头" -#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 +#: authentication/backends/drf.py:88 authentication/backends/drf.py:104 msgid "Invalid signature." msgstr "签名无效" -#: authentication/backends/drf.py:97 +#: authentication/backends/drf.py:95 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgstr "HTTP header not valid" -#: authentication/backends/drf.py:102 +#: authentication/backends/drf.py:100 msgid "Expired, more than 15 minutes" msgstr "已过期,超过15分钟" -#: authentication/backends/drf.py:109 +#: authentication/backends/drf.py:107 msgid "User disabled." msgstr "用户已禁用" -#: authentication/backends/drf.py:127 +#: authentication/backends/drf.py:125 msgid "Invalid token header. No credentials provided." msgstr "无效的令牌头。没有提供任何凭据。" -#: authentication/backends/drf.py:130 +#: authentication/backends/drf.py:128 msgid "Invalid token header. Sign string should not contain spaces." msgstr "无效的令牌头。符号字符串不应包含空格。" -#: authentication/backends/drf.py:137 +#: authentication/backends/drf.py:135 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "无效的令牌头。符号字符串不应包含无效字符。" -#: authentication/backends/drf.py:148 +#: authentication/backends/drf.py:146 msgid "Invalid token or cache refreshed." msgstr "刷新的令牌或缓存无效。" @@ -2403,20 +2514,20 @@ msgid "" "You can also try {times_try} times (The account will be temporarily locked " "for {block_time} minutes)" msgstr "" -"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将" -"被临时 锁定 {block_time} 分钟)" +"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次 (账号将" +"被临时 锁定 {block_time} 分钟)" #: authentication/errors/const.py:47 authentication/errors/const.py:55 msgid "" "The account has been locked (please contact admin to unlock it or try again " "after {} minutes)" -msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" +msgstr "账号已被锁定 (请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:51 msgid "" "The address has been locked (please contact admin to unlock it or try again " "after {} minutes)" -msgstr "IP 已被锁定(请联系管理员解锁或 {} 分钟后重试)" +msgstr "IP 已被锁定 (请联系管理员解锁或 {} 分钟后重试)" #: authentication/errors/const.py:59 #, python-brace-format @@ -2424,7 +2535,7 @@ msgid "" "{error}, You can also try {times_try} times (The account will be temporarily " "locked for {block_time} minutes)" msgstr "" -"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)" +"{error},您还可以尝试 {times_try} 次 (账号将被临时锁定 {block_time} 分钟)" #: authentication/errors/const.py:63 msgid "MFA required" @@ -2475,12 +2586,12 @@ msgstr "企业微信已经绑定" msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:210 -#: authentication/views/dingtalk.py:252 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:209 +#: authentication/views/dingtalk.py:251 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:167 +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:166 msgid "FeiShu is not bound" msgstr "没有绑定飞书" @@ -2595,7 +2706,7 @@ msgstr "清空手机号码禁用" #: authentication/middleware.py:93 settings/utils/ldap.py:661 msgid "Authentication failed (before login check failed): {}" -msgstr "认证失败(登录前检查失败): {}" +msgstr "认证失败 (登录前检查失败): {}" #: authentication/mixins.py:91 msgid "" @@ -2612,7 +2723,7 @@ msgid "Please change your password" msgstr "请修改密码" #: authentication/models/connection_token.py:38 -#: terminal/serializers/storage.py:111 +#: terminal/serializers/storage.py:113 msgid "Account name" msgstr "账号名称" @@ -2634,7 +2745,6 @@ msgid "Connect options" msgstr "连接项" #: authentication/models/connection_token.py:44 -#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" @@ -2649,7 +2759,7 @@ msgstr "可以重复使用" #: authentication/models/connection_token.py:47 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:74 #: tickets/models/ticket/apply_application.py:31 -#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:797 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:831 msgid "Date expired" msgstr "失效日期" @@ -2686,11 +2796,11 @@ msgstr "没有用户或用户失效" msgid "No asset or inactive asset" msgstr "没有资产或资产未激活" -#: authentication/models/connection_token.py:269 +#: authentication/models/connection_token.py:265 msgid "Can view super connection token secret" msgstr "可以查看超级连接令牌密文" -#: authentication/models/connection_token.py:271 +#: authentication/models/connection_token.py:267 msgid "Super connection token" msgstr "超级连接令牌" @@ -2752,15 +2862,15 @@ msgstr "动作" #: authentication/serializers/connection_token.py:42 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57 -#: users/serializers/user.py:96 users/serializers/user.py:172 +#: users/serializers/user.py:96 users/serializers/user.py:173 msgid "Is expired" msgstr "已过期" #: authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 -#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 -#: settings/serializers/email.py:50 users/forms/profile.py:102 -#: users/forms/profile.py:109 users/models/user.py:755 +#: notifications/backends/__init__.py:10 settings/serializers/msg.py:22 +#: settings/serializers/msg.py:57 users/forms/profile.py:102 +#: users/forms/profile.py:109 users/models/user.py:789 #: users/templates/users/forgot_password.html:117 #: users/views/profile/reset.py:73 msgid "Email" @@ -2798,14 +2908,13 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:601 -#: users/serializers/profile.py:116 +#: users/models/user.py:635 users/serializers/profile.py:116 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:602 users/serializers/profile.py:117 +#: users/models/user.py:636 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2850,7 +2959,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:431 +#: jumpserver/conf.py:444 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2904,7 +3013,8 @@ msgid "request new one" msgstr "重新申请" #: authentication/templates/authentication/_msg_reset_password_code.html:12 -#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:83 +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:93 +#: terminal/templates/terminal/_msg_session_sharing.html:12 #: users/forms/profile.py:107 users/templates/users/forgot_password.html:66 msgid "Verify code" msgstr "验证码" @@ -2994,74 +3104,74 @@ msgstr "复制成功" msgid "LAN" msgstr "局域网" -#: authentication/views/base.py:64 +#: authentication/views/base.py:61 #: perms/templates/perms/_msg_permed_items_expire.html:21 msgid "If you have any question, please contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" -#: authentication/views/dingtalk.py:42 +#: authentication/views/dingtalk.py:41 msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:209 +#: authentication/views/dingtalk.py:44 authentication/views/dingtalk.py:208 msgid "DingTalk Error" msgstr "钉钉错误" -#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:51 +#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:50 #: authentication/views/wecom.py:57 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "企业配置错误,请联系系统管理员" -#: authentication/views/dingtalk.py:61 +#: authentication/views/dingtalk.py:60 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:129 +#: authentication/views/dingtalk.py:128 authentication/views/wecom.py:129 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:144 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:153 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:161 +#: authentication/views/dingtalk.py:160 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:211 authentication/views/dingtalk.py:246 +#: authentication/views/dingtalk.py:210 authentication/views/dingtalk.py:245 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:253 +#: authentication/views/dingtalk.py:252 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" -#: authentication/views/feishu.py:39 authentication/views/feishu.py:166 +#: authentication/views/feishu.py:38 authentication/views/feishu.py:165 msgid "FeiShu Error" msgstr "飞书错误" -#: authentication/views/feishu.py:67 +#: authentication/views/feishu.py:66 msgid "FeiShu is already bound" msgstr "飞书已经绑定" -#: authentication/views/feishu.py:108 +#: authentication/views/feishu.py:107 msgid "FeiShu query user failed" msgstr "飞书查询用户失败" -#: authentication/views/feishu.py:117 +#: authentication/views/feishu.py:116 msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" -#: authentication/views/feishu.py:124 +#: authentication/views/feishu.py:123 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" -#: authentication/views/feishu.py:168 +#: authentication/views/feishu.py:167 msgid "Failed to get user from FeiShu" msgstr "从飞书获取用户失败" @@ -3150,7 +3260,7 @@ msgstr "准备" msgid "Pending" msgstr "待定的" -#: common/const/choices.py:17 ops/const.py:55 +#: common/const/choices.py:17 ops/const.py:58 msgid "Running" msgstr "运行中" @@ -3169,8 +3279,8 @@ msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" #: common/db/encoder.py:11 -msgid "ugettext_lazy" -msgstr "ugettext_lazy" +msgid "gettext_lazy" +msgstr "gettext_lazy" #: common/db/fields.py:106 msgid "Marshal dict data to char field" @@ -3196,11 +3306,11 @@ msgstr "编码数据为 char" msgid "Marshal data to text field" msgstr "编码数据为 text" -#: common/db/fields.py:176 +#: common/db/fields.py:167 msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/db/fields.py:567 +#: common/db/fields.py:558 msgid "" "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " @@ -3210,19 +3320,19 @@ msgstr "" "{'type': 'attrs', 'attrs': [{'name': 'ip', 'match': 'exact', 'value': " "'1.1.1.1'}}" -#: common/db/fields.py:574 +#: common/db/fields.py:565 msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgstr "无效类型,应为 all、ids 或 attrs" -#: common/db/fields.py:577 +#: common/db/fields.py:568 msgid "Invalid ids for ids, should be a list" msgstr "无效的ID,应为列表" -#: common/db/fields.py:582 common/db/fields.py:587 +#: common/db/fields.py:573 common/db/fields.py:578 msgid "Invalid attrs, should be a list of dict" msgstr "无效的属性,应为dict列表" -#: common/db/fields.py:589 +#: common/db/fields.py:580 msgid "Invalid attrs, should be has name and value" msgstr "无效属性,应具有名称和值" @@ -3234,7 +3344,7 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" -#: common/db/models.py:33 users/models/user.py:800 +#: common/db/models.py:33 users/models/user.py:834 msgid "Updated by" msgstr "最后更新者" @@ -3297,11 +3407,11 @@ msgstr "此操作需要确认当前用户" msgid "Unexpect error occur" msgstr "发生意外错误" -#: common/plugins/es.py:28 +#: common/plugins/es.py:31 msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" -#: common/plugins/es.py:33 +#: common/plugins/es.py:36 msgid "Not Support Elasticsearch8" msgstr "不支持 Elasticsearch8" @@ -3317,11 +3427,11 @@ msgstr "企业微信错误,请联系系统管理员" msgid "Signature does not match" msgstr "签名不匹配" -#: common/sdk/sms/cmpp2.py:46 +#: common/sdk/sms/cmpp2.py:44 msgid "sp_id is 6 bits" msgstr "SP_id 为6位" -#: common/sdk/sms/cmpp2.py:216 +#: common/sdk/sms/cmpp2.py:214 msgid "Failed to connect to the CMPP gateway server, err: {}" msgstr "连接网关服务器错误,错误:{}" @@ -3404,15 +3514,15 @@ msgstr "无效地址" msgid "Hello %s" msgstr "你好 %s" -#: common/validators.py:17 +#: common/validators.py:16 msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: common/validators.py:43 +#: common/validators.py:42 msgid "Should not contains special characters" msgstr "不能包含特殊字符" -#: common/validators.py:48 +#: common/validators.py:47 msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" @@ -3434,11 +3544,11 @@ msgstr "导出搜素: %s" msgid "User %s view/export secret" msgstr "用户 %s 查看/导出 了密码" -#: jumpserver/conf.py:430 +#: jumpserver/conf.py:443 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:432 +#: jumpserver/conf.py:445 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -3446,7 +3556,7 @@ msgstr "你的账号已创建成功" msgid "JumpServer Open Source Bastion Host" msgstr "JumpServer 开源堡垒机" -#: jumpserver/views/celery_flower.py:23 +#: jumpserver/views/celery_flower.py:22 msgid "

Flower service unavailable, check it

" msgstr "Flower 服务不可用,请检查" @@ -3501,15 +3611,15 @@ msgstr "系统信息" msgid "Publish the station message" msgstr "发布站内消息" -#: ops/ansible/inventory.py:82 +#: ops/ansible/inventory.py:92 ops/models/job.py:60 msgid "No account available" msgstr "无可用账号" -#: ops/ansible/inventory.py:247 +#: ops/ansible/inventory.py:260 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:263 +#: ops/ansible/inventory.py:276 msgid "Skip hosts below:" msgstr "跳过以下主机: " @@ -3569,11 +3679,11 @@ msgstr "空白" msgid "VCS" msgstr "VCS" -#: ops/const.py:38 ops/models/adhoc.py:45 +#: ops/const.py:38 ops/models/adhoc.py:44 msgid "Adhoc" msgstr "命令" -#: ops/const.py:39 ops/models/job.py:103 +#: ops/const.py:39 ops/models/job.py:135 msgid "Playbook" msgstr "Playbook" @@ -3593,7 +3703,19 @@ msgstr "PowerShell" msgid "Python" msgstr "Python" -#: ops/const.py:57 +#: ops/const.py:52 +msgid "MySQL" +msgstr "MySQL" + +#: ops/const.py:53 +msgid "PostgreSQL" +msgstr "PostgreSQL" + +#: ops/const.py:54 +msgid "SQLServer" +msgstr "SQLServer" + +#: ops/const.py:60 msgid "Timeout" msgstr "超时" @@ -3626,22 +3748,22 @@ msgstr "输入在 {} - {} 范围之间" msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/models/adhoc.py:22 +#: ops/models/adhoc.py:21 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:24 ops/models/job.py:98 +#: ops/models/adhoc.py:23 ops/models/job.py:130 msgid "Module" msgstr "模块" -#: ops/models/adhoc.py:25 ops/models/celery.py:58 ops/models/job.py:97 -#: terminal/models/component/task.py:16 +#: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:129 +#: terminal/models/component/task.py:14 msgid "Args" msgstr "参数" -#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:106 ops/models/job.py:192 ops/models/playbook.py:25 -#: terminal/models/session/sharing.py:23 +#: ops/models/adhoc.py:25 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:138 ops/models/job.py:226 ops/models/playbook.py:30 +#: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "创建者" @@ -3657,12 +3779,12 @@ msgstr "最后执行" msgid "Date last run" msgstr "最后运行日期" -#: ops/models/base.py:51 ops/models/job.py:190 -#: xpack/plugins/cloud/models.py:162 +#: ops/models/base.py:51 ops/models/job.py:224 +#: xpack/plugins/cloud/models.py:199 msgid "Result" msgstr "结果" -#: ops/models/base.py:52 ops/models/job.py:191 +#: ops/models/base.py:52 ops/models/job.py:225 msgid "Summary" msgstr "汇总" @@ -3678,11 +3800,11 @@ msgstr "Celery 任务" msgid "Can view task monitor" msgstr "可以查看任务监控" -#: ops/models/celery.py:59 terminal/models/component/task.py:17 +#: ops/models/celery.py:59 terminal/models/component/task.py:15 msgid "Kwargs" msgstr "其它参数" -#: ops/models/celery.py:61 terminal/models/session/sharing.py:114 +#: ops/models/celery.py:61 terminal/models/session/sharing.py:124 #: tickets/const.py:25 msgid "Finished" msgstr "结束" @@ -3695,51 +3817,51 @@ msgstr "发布日期" msgid "Celery Task Execution" msgstr "Celery 任务执行" -#: ops/models/job.py:100 +#: ops/models/job.py:132 msgid "Chdir" msgstr "运行目录" -#: ops/models/job.py:101 +#: ops/models/job.py:133 msgid "Timeout (Seconds)" -msgstr "超时时间(秒)" +msgstr "超时时间 (秒)" -#: ops/models/job.py:108 +#: ops/models/job.py:140 msgid "Use Parameter Define" msgstr "使用参数定义" -#: ops/models/job.py:109 +#: ops/models/job.py:141 msgid "Parameters define" msgstr "参数定义" -#: ops/models/job.py:110 +#: ops/models/job.py:142 msgid "Runas" msgstr "运行用户" -#: ops/models/job.py:112 +#: ops/models/job.py:144 msgid "Runas policy" msgstr "用户策略" -#: ops/models/job.py:174 +#: ops/models/job.py:208 msgid "Job" msgstr "作业" -#: ops/models/job.py:197 +#: ops/models/job.py:231 msgid "Material" msgstr "Material" -#: ops/models/job.py:199 +#: ops/models/job.py:233 msgid "Material Type" msgstr "Material 类型" -#: ops/models/job.py:480 +#: ops/models/job.py:533 msgid "Job Execution" msgstr "作业执行" -#: ops/models/playbook.py:28 +#: ops/models/playbook.py:33 msgid "CreateMethod" msgstr "创建方式" -#: ops/models/playbook.py:29 +#: ops/models/playbook.py:34 msgid "VCS URL" msgstr "VCS URL" @@ -3779,7 +3901,7 @@ msgstr "保存后执行" msgid "Job type" msgstr "任务类型" -#: ops/serializers/job.py:57 terminal/serializers/session.py:47 +#: ops/serializers/job.py:57 terminal/serializers/session.py:50 msgid "Is finished" msgstr "是否完成" @@ -3815,7 +3937,7 @@ msgstr "清理异常作业" msgid "Task log" msgstr "任务列表" -#: ops/templates/ops/celery_task_log.html:71 +#: ops/templates/ops/celery_task_log.html:71 terminal/serializers/task.py:10 msgid "Task name" msgstr "任务名称" @@ -3869,46 +3991,51 @@ msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进 msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" -#: orgs/apps.py:7 rbac/tree.py:119 +#: orgs/apps.py:7 rbac/tree.py:122 msgid "App organizations" msgstr "组织管理" -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:89 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91 #: rbac/const.py:7 rbac/models/rolebinding.py:56 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 +#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/ldap.py:63 #: terminal/templates/terminal/_msg_command_warning.html:21 +#: terminal/templates/terminal/_msg_session_sharing.html:14 #: tickets/models/ticket/general.py:302 tickets/serializers/ticket/ticket.py:60 msgid "Organization" msgstr "组织" -#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:27 msgid "Org name" msgstr "组织名称" -#: orgs/models.py:13 +#: orgs/models.py:14 msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:15 +#: orgs/models.py:16 msgid "DEFAULT" msgstr "默认组织" -#: orgs/models.py:17 +#: orgs/models.py:18 msgid "SYSTEM" msgstr "系统组织" -#: orgs/models.py:81 rbac/models/role.py:36 terminal/models/applet/applet.py:40 +#: orgs/models.py:83 rbac/models/role.py:36 terminal/models/applet/applet.py:40 msgid "Builtin" msgstr "内置的" -#: orgs/models.py:91 +#: orgs/models.py:93 msgid "Can view root org" msgstr "可以查看全局组织" -#: orgs/models.py:92 +#: orgs/models.py:94 msgid "Can view all joined org" msgstr "可以查看所有加入的组织" +#: orgs/models.py:233 +msgid "Can not delete virtual org" +msgstr "" + #: orgs/tasks.py:9 msgid "Refresh organization cache" msgstr "刷新组织缓存" @@ -4021,7 +4148,7 @@ msgstr "角色已绑定用户,不能删除" msgid "Internal role, can't be update" msgstr "内部角色,不能更新" -#: rbac/api/rolebinding.py:52 +#: rbac/api/rolebinding.py:45 msgid "{} at least one system role" msgstr "{} 至少有一个系统角色" @@ -4077,7 +4204,7 @@ msgstr "Web终端" msgid "Can view file manager" msgstr "文件管理" -#: rbac/models/permission.py:26 rbac/models/role.py:34 +#: rbac/models/permission.py:27 rbac/models/role.py:34 msgid "Permissions" msgstr "授权" @@ -4087,7 +4214,7 @@ msgid "Scope" msgstr "范围" #: rbac/models/role.py:46 rbac/models/rolebinding.py:52 -#: users/models/user.py:763 +#: users/models/user.py:797 msgid "Role" msgstr "角色" @@ -4103,25 +4230,25 @@ msgstr "组织角色" msgid "Role binding" msgstr "角色绑定" -#: rbac/models/rolebinding.py:153 +#: rbac/models/rolebinding.py:161 msgid "All organizations" msgstr "所有组织" -#: rbac/models/rolebinding.py:182 +#: rbac/models/rolebinding.py:190 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" -#: rbac/models/rolebinding.py:189 +#: rbac/models/rolebinding.py:197 msgid "Organization role binding" msgstr "组织角色绑定" -#: rbac/models/rolebinding.py:204 +#: rbac/models/rolebinding.py:212 msgid "System role binding" msgstr "系统角色绑定" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 +#: rbac/serializers/permission.py:25 users/serializers/profile.py:132 msgid "Perms" msgstr "权限" @@ -4133,11 +4260,7 @@ msgstr "用户数量" msgid "Display name" msgstr "显示名称" -#: rbac/serializers/rolebinding.py:22 -msgid "Role display" -msgstr "角色显示" - -#: rbac/serializers/rolebinding.py:56 +#: rbac/serializers/rolebinding.py:60 msgid "Has bound this role" msgstr "已经绑定" @@ -4157,7 +4280,7 @@ msgstr "工作台" msgid "Audit view" msgstr "审计台" -#: rbac/tree.py:27 settings/models.py:159 +#: rbac/tree.py:27 settings/models.py:158 msgid "System setting" msgstr "系统设置" @@ -4194,29 +4317,30 @@ msgid "My assets" msgstr "我的资产" #: rbac/tree.py:56 terminal/models/applet/applet.py:51 -#: terminal/models/applet/applet.py:244 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:280 terminal/models/applet/host.py:29 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "远程应用" -#: rbac/tree.py:120 +#: rbac/tree.py:123 msgid "Ticket comment" msgstr "工单评论" -#: rbac/tree.py:121 tickets/models/ticket/general.py:307 +#: rbac/tree.py:124 settings/serializers/feature.py:58 +#: tickets/models/ticket/general.py:307 msgid "Ticket" msgstr "工单管理" -#: rbac/tree.py:122 +#: rbac/tree.py:125 msgid "Common setting" msgstr "一般设置" -#: rbac/tree.py:123 +#: rbac/tree.py:126 msgid "View permission tree" msgstr "查看授权树" #: settings/api/dingtalk.py:31 settings/api/feishu.py:36 -#: settings/api/sms.py:155 settings/api/wecom.py:37 +#: settings/api/sms.py:153 settings/api/vault.py:40 settings/api/wecom.py:37 msgid "Test success" msgstr "测试成功" @@ -4244,11 +4368,11 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully (Organization: {})" msgstr "成功导入 {} 个用户 ( 组织: {} )" -#: settings/api/sms.py:137 +#: settings/api/sms.py:135 msgid "Invalid SMS platform" msgstr "无效的短信平台" -#: settings/api/sms.py:143 +#: settings/api/sms.py:141 msgid "test_phone is required" msgstr "测试手机号 该字段是必填项。" @@ -4256,18 +4380,22 @@ msgstr "测试手机号 该字段是必填项。" msgid "Settings" msgstr "系统设置" -#: settings/models.py:36 +#: settings/models.py:35 msgid "Encrypted" msgstr "加密的" -#: settings/models.py:161 +#: settings/models.py:160 msgid "Can change email setting" msgstr "邮件设置" -#: settings/models.py:162 +#: settings/models.py:161 msgid "Can change auth setting" msgstr "认证设置" +#: settings/models.py:162 +msgid "Can change vault setting" +msgstr "可以更改 vault 设置" + #: settings/models.py:163 msgid "Can change system msg sub setting" msgstr "消息订阅设置" @@ -4301,42 +4429,50 @@ msgid "Can change other setting" msgstr "其它设置" #: settings/serializers/auth/base.py:12 +msgid "LDAP Auth" +msgstr "LDAP 认证" + +#: settings/serializers/auth/base.py:13 msgid "CAS Auth" msgstr "CAS 认证" -#: settings/serializers/auth/base.py:13 +#: settings/serializers/auth/base.py:14 msgid "OPENID Auth" msgstr "OIDC 认证" -#: settings/serializers/auth/base.py:14 -msgid "RADIUS Auth" -msgstr "RADIUS 认证" - #: settings/serializers/auth/base.py:15 -msgid "DingTalk Auth" -msgstr "钉钉 认证" - -#: settings/serializers/auth/base.py:16 -msgid "FeiShu Auth" -msgstr "飞书 认证" - -#: settings/serializers/auth/base.py:17 -msgid "WeCom Auth" -msgstr "企业微信 认证" - -#: settings/serializers/auth/base.py:18 -msgid "SSO Auth" -msgstr "SSO 令牌认证" - -#: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" msgstr "SAML2 认证" -#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 +#: settings/serializers/auth/base.py:16 +msgid "OAuth2 Auth" +msgstr "OAuth2 认证" + +#: settings/serializers/auth/base.py:17 +msgid "RADIUS Auth" +msgstr "RADIUS 认证" + +#: settings/serializers/auth/base.py:18 +msgid "DingTalk Auth" +msgstr "钉钉 认证" + +#: settings/serializers/auth/base.py:19 +msgid "FeiShu Auth" +msgstr "飞书 认证" + +#: settings/serializers/auth/base.py:20 +msgid "WeCom Auth" +msgstr "企业微信 认证" + +#: settings/serializers/auth/base.py:21 +msgid "SSO Auth" +msgstr "SSO 令牌认证" + +#: settings/serializers/auth/base.py:24 msgid "Forgot password url" msgstr "忘记密码 URL" -#: settings/serializers/auth/base.py:28 +#: settings/serializers/auth/base.py:30 msgid "Enable login redirect msg" msgstr "启用登录跳转提示" @@ -4357,7 +4493,7 @@ msgid "Proxy server url" msgstr "回调地址" #: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:54 -#: settings/serializers/auth/saml2.py:34 +#: settings/serializers/auth/saml2.py:33 msgid "Logout completely" msgstr "同步注销" @@ -4369,7 +4505,7 @@ msgstr "用户名属性" msgid "Enable attributes map" msgstr "启用属性映射" -#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:32 msgid "Rename attr" msgstr "映射属性" @@ -4489,7 +4625,7 @@ msgid "Provider end session endpoint" msgstr "注销会话端点地址" #: settings/serializers/auth/oauth2.py:59 settings/serializers/auth/oidc.py:98 -#: settings/serializers/auth/saml2.py:35 +#: settings/serializers/auth/saml2.py:34 msgid "Always update user" msgstr "总是更新用户信息" @@ -4585,31 +4721,31 @@ msgstr "启用 Radius 认证" msgid "OTP in Radius" msgstr "使用 Radius OTP" -#: settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/saml2.py:10 msgid "SAML2" msgstr "SAML2" -#: settings/serializers/auth/saml2.py:14 +#: settings/serializers/auth/saml2.py:13 msgid "Enable SAML2 Auth" msgstr "启用 SAML2 认证" -#: settings/serializers/auth/saml2.py:17 +#: settings/serializers/auth/saml2.py:16 msgid "IDP metadata URL" msgstr "IDP metadata 地址" -#: settings/serializers/auth/saml2.py:20 +#: settings/serializers/auth/saml2.py:19 msgid "IDP metadata XML" msgstr "IDP metadata XML" -#: settings/serializers/auth/saml2.py:23 +#: settings/serializers/auth/saml2.py:22 msgid "SP advanced settings" msgstr "高级设置" -#: settings/serializers/auth/saml2.py:27 +#: settings/serializers/auth/saml2.py:26 msgid "SP private key" msgstr "SP 密钥" -#: settings/serializers/auth/saml2.py:31 +#: settings/serializers/auth/saml2.py:30 msgid "SP cert" msgstr "SP 证书" @@ -4623,7 +4759,7 @@ msgstr "短信服务商 / 协议" #: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:45 #: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 -#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:69 +#: settings/serializers/auth/sms.py:73 settings/serializers/msg.py:76 msgid "Signature" msgstr "签名" @@ -4692,19 +4828,19 @@ msgstr "请求方式" msgid "The value in the parameter must contain %s" msgstr "参数中的值必须包含 %s" -#: settings/serializers/auth/sso.py:13 +#: settings/serializers/auth/sso.py:16 msgid "Enable SSO auth" msgstr "启用 SSO 令牌认证" -#: settings/serializers/auth/sso.py:14 +#: settings/serializers/auth/sso.py:17 msgid "Other service can using SSO token login to JumpServer without password" msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过程" -#: settings/serializers/auth/sso.py:17 +#: settings/serializers/auth/sso.py:20 msgid "SSO auth key TTL" msgstr "令牌有效期" -#: settings/serializers/auth/sso.py:17 +#: settings/serializers/auth/sso.py:20 #: xpack/plugins/cloud/serializers/account_attrs.py:193 msgid "Unit: second" msgstr "单位: 秒" @@ -4714,56 +4850,46 @@ msgid "Enable WeCom Auth" msgstr "启用企业微信认证" #: settings/serializers/basic.py:9 -msgid "Subject" -msgstr "主题" +msgid "Site url" +msgstr "当前站点 URL" + +#: settings/serializers/basic.py:10 +msgid "" +"Email links or other system callbacks are used to access it, eg: http://dev." +"jumpserver.org:8080" +msgstr "" #: settings/serializers/basic.py:13 -msgid "More url" -msgstr "更多信息 URL" - -#: settings/serializers/basic.py:30 -msgid "Site url" -msgstr "当前站点URL" - -#: settings/serializers/basic.py:31 -msgid "eg: http://dev.jumpserver.org:8080" -msgstr "如: http://dev.jumpserver.org:8080" - -#: settings/serializers/basic.py:34 msgid "User guide url" msgstr "用户向导URL" -#: settings/serializers/basic.py:35 +#: settings/serializers/basic.py:14 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" -#: settings/serializers/basic.py:39 -msgid "" -"The forgot password url on login page, If you use ldap or cas external " -"authentication, you can set it" -msgstr "" -"登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" -"置密码访问的地址" - -#: settings/serializers/basic.py:43 +#: settings/serializers/basic.py:17 msgid "Global organization name" msgstr "全局组织名" -#: settings/serializers/basic.py:44 +#: settings/serializers/basic.py:18 msgid "The name of global organization to display" msgstr "全局组织的显示名称,默认为 全局组织" -#: settings/serializers/basic.py:46 -msgid "Enable announcement" -msgstr "启用公告" +#: settings/serializers/basic.py:21 +msgid "Help Docs URL" +msgstr "文档链接" -#: settings/serializers/basic.py:47 -msgid "Announcement" -msgstr "公告" +#: settings/serializers/basic.py:22 +msgid "default: http://docs.jumpserver.org" +msgstr "默认: http://dev.jumpserver.org:8080" -#: settings/serializers/basic.py:48 -msgid "Enable tickets" -msgstr "启用工单" +#: settings/serializers/basic.py:25 +msgid "Help Support URL" +msgstr "支持链接" + +#: settings/serializers/basic.py:26 +msgid "default: http://www.jumpserver.org/support/" +msgstr "默认: http://www.jumpserver.org/support/" #: settings/serializers/cleaning.py:8 msgid "Period clean" @@ -4771,114 +4897,187 @@ msgstr "定時清掃" #: settings/serializers/cleaning.py:12 msgid "Login log keep days (day)" -msgstr "登录日志(天)" +msgstr "登录日志 (天)" #: settings/serializers/cleaning.py:16 msgid "Task log keep days (day)" -msgstr "任务日志(天)" +msgstr "任务日志 (天)" #: settings/serializers/cleaning.py:20 msgid "Operate log keep days (day)" -msgstr "操作日志(天)" +msgstr "操作日志 (天)" #: settings/serializers/cleaning.py:24 msgid "FTP log keep days (day)" -msgstr "上传下载(天)" +msgstr "上传下载 (天)" #: settings/serializers/cleaning.py:28 msgid "Cloud sync record keep days (day)" -msgstr "云同步记录(天)" +msgstr "云同步记录 (天)" #: settings/serializers/cleaning.py:31 msgid "Session keep duration (day)" -msgstr "会话日志(天)" +msgstr "会话日志 (天)" -#: settings/serializers/cleaning.py:32 +#: settings/serializers/cleaning.py:33 msgid "" "Session, record, command will be delete if more than duration, only in " "database, OSS will not be affected." msgstr "" -"会话、录像,命令记录超过该时长将会被洲除(影响数据库存備,OSS 等不受影响)" +"会话、录像,命令记录超过该时长将会被清除 (影响数据库存储,OSS 等不受影响)" -#: settings/serializers/cleaning.py:36 +#: settings/serializers/cleaning.py:37 msgid "Activity log keep days (day)" -msgstr "活动记录(天)" +msgstr "活动记录 (天)" -#: settings/serializers/email.py:21 +#: settings/serializers/feature.py:16 +msgid "Subject" +msgstr "主题" + +#: settings/serializers/feature.py:20 +msgid "More url" +msgstr "更多信息 URL" + +#: settings/serializers/feature.py:34 settings/serializers/feature.py:37 +msgid "Announcement" +msgstr "公告" + +#: settings/serializers/feature.py:36 +msgid "Enable announcement" +msgstr "启用公告" + +#: settings/serializers/feature.py:44 +msgid "Enable Vault" +msgstr "启用 Vault" + +#: settings/serializers/feature.py:53 +msgid "Mount Point" +msgstr "挂在点" + +#: settings/serializers/feature.py:60 +msgid "Enable tickets" +msgstr "启用工单" + +#: settings/serializers/feature.py:63 +msgid "Ticket authorize default time" +msgstr "默认工单授权时间" + +#: settings/serializers/feature.py:66 +msgid "day" +msgstr "天" + +#: settings/serializers/feature.py:66 +msgid "hour" +msgstr "时" + +#: settings/serializers/feature.py:67 +msgid "Ticket authorize default time unit" +msgstr "默认工单授权时间单位" + +#: settings/serializers/feature.py:72 +msgid "Feature" +msgstr "功能" + +#: settings/serializers/feature.py:75 +msgid "Operation center" +msgstr "作业中心" + +#: settings/serializers/feature.py:76 +msgid "Allow user run batch command or not using ansible" +msgstr "是否允许用户使用 ansible 执行批量命令" + +#: settings/serializers/feature.py:80 +msgid "Operation center command blacklist" +msgstr "作业中心命令黑名单" + +#: settings/serializers/feature.py:81 +msgid "Commands that are not allowed execute." +msgstr "不允许执行的命令" + +#: settings/serializers/msg.py:24 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/email.py:22 +#: settings/serializers/msg.py:25 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/email.py:23 +#: settings/serializers/msg.py:26 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/email.py:25 +#: settings/serializers/msg.py:28 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/email.py:26 +#: settings/serializers/msg.py:29 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/email.py:29 +#: settings/serializers/msg.py:32 msgid "Send user" msgstr "发件人" -#: settings/serializers/email.py:30 +#: settings/serializers/msg.py:33 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/email.py:33 +#: settings/serializers/msg.py:36 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/email.py:34 +#: settings/serializers/msg.py:37 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/email.py:38 +#: settings/serializers/msg.py:41 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/email.py:41 +#: settings/serializers/msg.py:44 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/email.py:42 +#: settings/serializers/msg.py:45 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/email.py:45 +#: settings/serializers/msg.py:48 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/email.py:54 +#: settings/serializers/msg.py:51 +msgid "Email suffix" +msgstr "邮件后缀" + +#: settings/serializers/msg.py:52 +msgid "" +"This is used by default if no email is returned during SSO authentication" +msgstr "SSO认证时,如果没有返回邮件地址,将使用该后缀" + +#: settings/serializers/msg.py:61 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/email.py:55 +#: settings/serializers/msg.py:62 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/email.py:59 +#: settings/serializers/msg.py:66 msgid "Create user honorific" msgstr "邮件问候语" -#: settings/serializers/email.py:60 +#: settings/serializers/msg.py:67 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" -#: settings/serializers/email.py:64 +#: settings/serializers/msg.py:71 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/email.py:66 +#: settings/serializers/msg.py:73 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -4886,185 +5085,116 @@ msgid "" msgstr "" "提示: 创建用户时,发送设置密码邮件的内容, 支持 {username} {name} {email} 标签" -#: settings/serializers/email.py:70 +#: settings/serializers/msg.py:77 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" -#: settings/serializers/other.py:6 +#: settings/serializers/other.py:8 msgid "More..." msgstr "更多..." -#: settings/serializers/other.py:9 -msgid "Email suffix" -msgstr "邮件后缀" - -#: settings/serializers/other.py:10 -msgid "" -"This is used by default if no email is returned during SSO authentication" -msgstr "SSO认证时,如果没有返回邮件地址,将使用该后缀" - -#: settings/serializers/other.py:14 -msgid "OTP issuer name" -msgstr "OTP 扫描后的名称" - -#: settings/serializers/other.py:18 -msgid "OTP valid window" -msgstr "OTP 延迟有效次数" - -#: settings/serializers/other.py:22 +#: settings/serializers/other.py:11 msgid "Perm ungroup node" msgstr "显示未分组节点" -#: settings/serializers/other.py:23 +#: settings/serializers/other.py:12 msgid "Perm single to ungroup node" msgstr "" "放置单独授权的资产到未分组节点, 避免能看到资产所在节点,但该节点未被授权的问" "题" -#: settings/serializers/other.py:28 -msgid "Ticket authorize default time" -msgstr "默认工单授权时间" - -#: settings/serializers/other.py:31 -msgid "day" -msgstr "天" - -#: settings/serializers/other.py:31 -msgid "hour" -msgstr "时" - -#: settings/serializers/other.py:32 -msgid "Ticket authorize default time unit" -msgstr "默认工单授权时间单位" - -#: settings/serializers/other.py:35 -msgid "Help Docs URL" -msgstr "文档链接" - -#: settings/serializers/other.py:36 -msgid "default: http://docs.jumpserver.org" -msgstr "默认: http://dev.jumpserver.org:8080" - -#: settings/serializers/other.py:40 -msgid "Help Support URL" -msgstr "支持链接" - -#: settings/serializers/other.py:41 -msgid "default: http://www.jumpserver.org/support/" -msgstr "默认: http://www.jumpserver.org/support/" - -#: settings/serializers/security.py:10 -msgid "Password minimum length" -msgstr "密码最小长度" - -#: settings/serializers/security.py:14 -msgid "Admin user password minimum length" -msgstr "管理员密码最小长度" - -#: settings/serializers/security.py:17 -msgid "Must contain capital" -msgstr "必须包含大写字符" - -#: settings/serializers/security.py:20 -msgid "Must contain lowercase" -msgstr "必须包含小写字符" - -#: settings/serializers/security.py:23 -msgid "Must contain numeric" -msgstr "必须包含数字" - -#: settings/serializers/security.py:26 -msgid "Must contain special" -msgstr "必须包含特殊字符" - -#: settings/serializers/security.py:31 -msgid "" -"If the user has failed to log in for a limited number of times, no login is " -"allowed during this time interval." -msgstr "当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" - -#: settings/serializers/security.py:40 -msgid "All users" -msgstr "所有用户" - -#: settings/serializers/security.py:41 -msgid "Only admin users" -msgstr "仅管理员" - -#: settings/serializers/security.py:43 -msgid "Global MFA auth" -msgstr "全局启用 MFA 认证" - -#: settings/serializers/security.py:47 -msgid "Third-party login users perform MFA authentication" -msgstr "第三方登录用户进行MFA认证" - -#: settings/serializers/security.py:48 -msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "第三方登录方式包括: OIDC、CAS、SAML2" - -#: settings/serializers/security.py:52 -msgid "Limit the number of user login failures" -msgstr "限制用户登录失败次数" - -#: settings/serializers/security.py:56 -msgid "Block user login interval (minute)" -msgstr "禁止用户登录时间间隔(分)" - -#: settings/serializers/security.py:61 -msgid "Limit the number of IP login failures" -msgstr "限制 IP 登录失败次数" - -#: settings/serializers/security.py:65 -msgid "Block IP login interval (minute)" -msgstr "禁止 IP 登录时间间隔(分)" - -#: settings/serializers/security.py:69 -msgid "Login IP White List" -msgstr "IP 登录白名单" - -#: settings/serializers/security.py:74 -msgid "Login IP Black List" -msgstr "IP 登录黑名单" - -#: settings/serializers/security.py:80 +#: settings/serializers/security.py:16 msgid "User password expiration (day)" -msgstr "用户密码过期时间(天)" +msgstr "用户密码过期时间 (天)" -#: settings/serializers/security.py:82 +#: settings/serializers/security.py:18 msgid "" "If the user does not update the password during the time, the user password " "will expire failure;The password expiration reminder mail will be automatic " "sent to the user by system within 5 days (daily) before the password expires" msgstr "" "如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件将在密码过" -"期前5天内由系统(每天)自动发送给用户" +"期前5天内由系统 (每天)自动发送给用户" -#: settings/serializers/security.py:89 +#: settings/serializers/security.py:25 msgid "Number of repeated historical passwords" msgstr "不能设置近几次密码" -#: settings/serializers/security.py:91 +#: settings/serializers/security.py:27 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" -#: settings/serializers/security.py:96 +#: settings/serializers/security.py:33 +msgid "Password minimum length" +msgstr "密码最小长度" + +#: settings/serializers/security.py:37 +msgid "Admin user password minimum length" +msgstr "管理员密码最小长度" + +#: settings/serializers/security.py:40 +msgid "Must contain capital" +msgstr "必须包含大写字符" + +#: settings/serializers/security.py:43 +msgid "Must contain lowercase" +msgstr "必须包含小写字符" + +#: settings/serializers/security.py:46 +msgid "Must contain numeric" +msgstr "必须包含数字" + +#: settings/serializers/security.py:49 +msgid "Must contain special" +msgstr "必须包含特殊字符" + +#: settings/serializers/security.py:54 +msgid "" +"If the user has failed to log in for a limited number of times, no login is " +"allowed during this time interval." +msgstr "当用户登录失败次数达到限制后,那么在此间隔内禁止登录" + +#: settings/serializers/security.py:62 +msgid "Limit the number of user login failures" +msgstr "限制用户登录失败次数" + +#: settings/serializers/security.py:66 +msgid "Block user login interval (minute)" +msgstr "禁止用户登录间隔 (分)" + +#: settings/serializers/security.py:72 +msgid "Limit the number of IP login failures" +msgstr "限制 IP 登录失败次数" + +#: settings/serializers/security.py:76 +msgid "Block IP login interval (minute)" +msgstr "禁止 IP 登录间隔 (分)" + +#: settings/serializers/security.py:80 +msgid "Login IP White List" +msgstr "IP 登录白名单" + +#: settings/serializers/security.py:85 +msgid "Login IP Black List" +msgstr "IP 登录黑名单" + +#: settings/serializers/security.py:90 msgid "Only single device login" msgstr "仅一台设备登录" -#: settings/serializers/security.py:97 +#: settings/serializers/security.py:91 msgid "" "After the user logs in on the new device, other logged-in devices will " "automatically log out" msgstr "用户在新设备登录后,其他已登录的设备会自动退出" -#: settings/serializers/security.py:100 +#: settings/serializers/security.py:94 msgid "Only exist user login" msgstr "仅已存在用户登录" -#: settings/serializers/security.py:101 +#: settings/serializers/security.py:96 msgid "" "If enabled, non-existent users will not be allowed to log in; if disabled, " "users of other authentication methods except local authentication methods " @@ -5072,13 +5202,13 @@ msgid "" "exist)" msgstr "" "如果开启,不存在的用户将不被允许登录;如果关闭,除本地认证方式外,其他认证方" -"式的用户都允许登录并自动创建用户(如果用户不存在)" +"式的用户都允许登录并自动创建用户 (如果用户不存在)" -#: settings/serializers/security.py:104 +#: settings/serializers/security.py:102 msgid "Only from source login" msgstr "仅从用户来源登录" -#: settings/serializers/security.py:105 +#: settings/serializers/security.py:104 msgid "" "If it is enabled, the user will only authenticate to the source when logging " "in; if it is disabled, the user will authenticate all the enabled " @@ -5088,28 +5218,69 @@ msgstr "" "如果开启,用户登录时仅会向来源端进行认证;如果关闭,用户登录时会按照一定的顺" "序对所有已开启的认证方式进行顺序认证,只要有一个认证成功就可以直接登录" -#: settings/serializers/security.py:109 -msgid "MFA verify TTL (secend)" -msgstr "MFA 校验有效期(秒)" - -#: settings/serializers/security.py:111 -msgid "" -"The verification MFA takes effect only when you view the account password" -msgstr "目前仅在查看账号密码校验 MFA 时生效" +#: settings/serializers/security.py:115 +msgid "Not enabled" +msgstr "未启用" #: settings/serializers/security.py:116 -msgid "Verify code TTL" -msgstr "验证码有效时间" +msgid "All users" +msgstr "所有用户" #: settings/serializers/security.py:117 -msgid "Unit: second, reset password and send SMS code expiration time" +msgid "Only admin users" +msgstr "仅管理员" + +#: settings/serializers/security.py:119 +msgid "Global MFA auth" +msgstr "全局启用 MFA 认证" + +#: settings/serializers/security.py:123 +msgid "Third-party login users perform MFA authentication" +msgstr "第三方认证开启 MFA" + +#: settings/serializers/security.py:124 +msgid "The third-party login modes include OIDC, CAS, and SAML2" +msgstr "第三方登录方式包括: OIDC、CAS、SAML2" + +#: settings/serializers/security.py:127 +msgid "OTP issuer name" +msgstr "OTP 扫描后的名称" + +#: settings/serializers/security.py:131 +msgid "OTP valid window" +msgstr "OTP 延迟有效次数" + +#: settings/serializers/security.py:135 +msgid "MFA verify TTL" +msgstr "MFA 校验有效期" + +#: settings/serializers/security.py:137 +msgid "" +"Unit: second, The verification MFA takes effect only when you view the " +"account password" +msgstr "单位:秒,目前仅在查看账号密码校验 MFA 时生效" + +#: settings/serializers/security.py:142 +msgid "MFA in login page" +msgstr "MFA 在登录页面输入" + +#: settings/serializers/security.py:143 +msgid "Eu security regulations(GDPR) require MFA to be on the login page" +msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全" + +#: settings/serializers/security.py:147 +msgid "Verify code TTL (second)" +msgstr "验证码有效时间 (分)" + +#: settings/serializers/security.py:148 +msgid "Reset password and send SMS code expiration time" msgstr "重置密码的验证码及发送短信的验证码过期时间" -#: settings/serializers/security.py:121 +#: settings/serializers/security.py:152 msgid "Enable Login dynamic code" msgstr "启用登录附加码" -#: settings/serializers/security.py:122 +#: settings/serializers/security.py:153 msgid "" "The password and additional code are sent to a third party authentication " "system for verification" @@ -5117,93 +5288,19 @@ msgstr "" "密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密" "码+6位数字 完成认证" -#: settings/serializers/security.py:127 -msgid "MFA in login page" -msgstr "MFA 在登录页面输入" - -#: settings/serializers/security.py:128 -msgid "Eu security regulations(GDPR) require MFA to be on the login page" -msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全" - -#: settings/serializers/security.py:131 +#: settings/serializers/security.py:157 msgid "Enable Login captcha" msgstr "启用登录验证码" -#: settings/serializers/security.py:132 +#: settings/serializers/security.py:158 msgid "Enable captcha to prevent robot authentication" msgstr "开启验证码,防止机器人登录" -#: settings/serializers/security.py:154 -msgid "Enable terminal register" -msgstr "终端注册" - -#: settings/serializers/security.py:156 -msgid "" -"Allow terminal register, after all terminal setup, you should disable this " -"for security" -msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" - -#: settings/serializers/security.py:160 -msgid "Enable watermark" -msgstr "开启水印" - #: settings/serializers/security.py:161 -msgid "Enabled, the web session and replay contains watermark information" -msgstr "启用后,Web 会话和录像将包含水印信息" - -#: settings/serializers/security.py:165 -msgid "Connection max idle time (minute)" -msgstr "连接最大空闲时间(分)" - -#: settings/serializers/security.py:166 -msgid "If idle time more than it, disconnect connection." -msgstr "提示:如果超过该配置没有操作,连接会被断开" - -#: settings/serializers/security.py:169 -msgid "Remember manual auth" -msgstr "保存手动输入密码" - -#: settings/serializers/security.py:172 -msgid "Insecure command alert" -msgstr "危险命令告警" - -#: settings/serializers/security.py:175 -msgid "Email recipient" -msgstr "邮件收件人" - -#: settings/serializers/security.py:176 -msgid "Multiple user using , split" -msgstr "多个用户,使用 , 分割" - -#: settings/serializers/security.py:179 -msgid "Operation center" -msgstr "作业中心" - -#: settings/serializers/security.py:180 -msgid "Allow user run batch command or not using ansible" -msgstr "是否允许用户使用 ansible 执行批量命令" - -#: settings/serializers/security.py:184 -msgid "Operation center command blacklist" -msgstr "作业中心命令黑名单" - -#: settings/serializers/security.py:185 -msgid "Commands that are not allowed execute." -msgstr "不允许执行的命令" - -#: settings/serializers/security.py:188 -msgid "Session share" -msgstr "会话分享" - -#: settings/serializers/security.py:189 -msgid "Enabled, Allows user active session to be shared with other users" -msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" - -#: settings/serializers/security.py:192 msgid "Remote Login Protection" -msgstr "异地登录保护" +msgstr "异地登录通知" -#: settings/serializers/security.py:194 +#: settings/serializers/security.py:163 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " @@ -5212,7 +5309,66 @@ msgstr "" "根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地" "登录提醒" -#: settings/serializers/settings.py:71 +#: settings/serializers/security.py:169 +msgid "Unused user timeout (day)" +msgstr "不活跃用户自动禁用 (天)" + +#: settings/serializers/security.py:170 +msgid "" +"Detect infrequent users daily and disable them if they exceed the " +"predetermined time limit." +msgstr "每天检测一次,超过预设时间的用户自动禁用" + +#: settings/serializers/security.py:190 +msgid "Enable watermark" +msgstr "开启水印" + +#: settings/serializers/security.py:191 +msgid "Enabled, the web session and replay contains watermark information" +msgstr "启用后,Web 会话和录像将包含水印信息" + +#: settings/serializers/security.py:195 +msgid "Connection max idle time (minute)" +msgstr "连接最大空闲时间 (分)" + +#: settings/serializers/security.py:196 +msgid "If idle time more than it, disconnect connection." +msgstr "提示:如果超过该配置没有操作,连接会被断开" + +#: settings/serializers/security.py:200 +msgid "Session max connection time (hour)" +msgstr "会话连接最大时间 (时)" + +#: settings/serializers/security.py:201 +msgid "If session connection time more than it, disconnect connection." +msgstr "提示:如果会话连接超过该配置,连接会被断开" + +#: settings/serializers/security.py:204 +msgid "Remember manual auth" +msgstr "保存手动输入密码" + +#: settings/serializers/security.py:207 +#: terminal/templates/terminal/_msg_session_sharing.html:10 +msgid "Session share" +msgstr "会话分享" + +#: settings/serializers/security.py:208 +msgid "Enabled, Allows user active session to be shared with other users" +msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" + +#: settings/serializers/security.py:214 +msgid "Insecure command alert" +msgstr "危险命令告警" + +#: settings/serializers/security.py:217 +msgid "Email recipient" +msgstr "邮件收件人" + +#: settings/serializers/security.py:218 +msgid "Multiple user using , split" +msgstr "多个用户,使用 , 分割" + +#: settings/serializers/settings.py:69 #, python-format msgid "[%s] %s" msgstr "[%s] %s" @@ -5225,15 +5381,25 @@ msgstr "主机名" msgid "Auto" msgstr "自动" -#: settings/serializers/terminal.py:21 +#: settings/serializers/terminal.py:22 +msgid "Enable terminal register" +msgstr "组件注册" + +#: settings/serializers/terminal.py:24 +msgid "" +"Allow terminal register, after all terminal setup, you should disable this " +"for security" +msgstr "是否允许组件注册,当所有终端启动后,为了安全应该关闭" + +#: settings/serializers/terminal.py:27 msgid "Password auth" msgstr "密码认证" -#: settings/serializers/terminal.py:23 +#: settings/serializers/terminal.py:29 msgid "Public key auth" msgstr "密钥认证" -#: settings/serializers/terminal.py:24 +#: settings/serializers/terminal.py:30 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -5241,51 +5407,40 @@ msgstr "" "提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" "除后,还可以登录" -#: settings/serializers/terminal.py:28 +#: settings/serializers/terminal.py:34 msgid "List sort by" msgstr "资产列表排序" -#: settings/serializers/terminal.py:31 +#: settings/serializers/terminal.py:37 msgid "List page size" msgstr "资产列表每页数量" -#: settings/serializers/terminal.py:34 -msgid "Telnet login regex" -msgstr "Telnet 成功正则表达式" - -#: settings/serializers/terminal.py:35 -msgid "" -"Tips: The login success message varies with devices. if you cannot log in to " -"the device through Telnet, set this parameter" -msgstr "" -"提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" - -#: settings/serializers/terminal.py:38 +#: settings/serializers/terminal.py:39 msgid "Enable database proxy" msgstr "启用数据库组件" -#: settings/serializers/terminal.py:39 +#: settings/serializers/terminal.py:40 msgid "Enable Razor" msgstr "启用 Razor 服务" -#: settings/serializers/terminal.py:40 +#: settings/serializers/terminal.py:41 msgid "Enable SSH Client" msgstr "启用 SSH Client" -#: settings/serializers/terminal.py:51 +#: settings/serializers/terminal.py:52 msgid "Default graphics resolution" msgstr "默认图形化分辨率" -#: settings/serializers/terminal.py:52 +#: settings/serializers/terminal.py:53 msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" msgstr "提示:在Luna 页面中连接图形化资产时默认使用的分辨率" -#: settings/tasks/ldap.py:25 +#: settings/tasks/ldap.py:24 msgid "Periodic import ldap user" msgstr "周期导入 LDAP 用户" -#: settings/tasks/ldap.py:46 +#: settings/tasks/ldap.py:45 msgid "Registration periodic import ldap user task" msgstr "注册周期导入 LDAP 用户 任务" @@ -5344,27 +5499,27 @@ msgstr "LDAP认证没有启用" #: settings/utils/ldap.py:613 msgid "Error (Invalid LDAP server): {}" -msgstr "错误 (不合法的LDAP服务器地址): {}" +msgstr "错误 (不合法的LDAP服务器地址): {}" #: settings/utils/ldap.py:615 msgid "Error (Invalid Bind DN): {}" -msgstr "错误(不合法的绑定DN): {}" +msgstr "错误 (不合法的绑定DN): {}" #: settings/utils/ldap.py:617 msgid "Error (Invalid LDAP User attr map): {}" -msgstr "错误(不合法的LDAP属性映射): {}" +msgstr "错误 (不合法的LDAP属性映射): {}" #: settings/utils/ldap.py:619 msgid "Error (Invalid User OU or User search filter): {}" -msgstr "错误(不合法的用户OU或用户过滤器): {}" +msgstr "错误 (不合法的用户OU或用户过滤器): {}" #: settings/utils/ldap.py:621 msgid "Error (Not enabled LDAP authentication): {}" -msgstr "错误(没有启用LDAP认证): {}" +msgstr "错误 (没有启用LDAP认证): {}" #: settings/utils/ldap.py:623 msgid "Error (Unknown): {}" -msgstr "错误(未知): {}" +msgstr "错误 (未知): {}" #: settings/utils/ldap.py:626 msgid "Succeed: Match {} s user" @@ -5372,7 +5527,7 @@ msgstr "成功匹配 {} 个用户" #: settings/utils/ldap.py:659 msgid "Authentication failed (configuration incorrect): {}" -msgstr "认证失败(配置错误): {}" +msgstr "认证失败 (配置错误): {}" #: settings/utils/ldap.py:663 msgid "Authentication failed (username or password incorrect): {}" @@ -5541,7 +5696,7 @@ msgstr "验证码已发送" msgid "Home page" msgstr "首页" -#: templates/resource_download.html:18 templates/resource_download.html:32 +#: templates/resource_download.html:18 templates/resource_download.html:33 msgid "Client" msgstr "客户端" @@ -5553,31 +5708,31 @@ msgstr "" "JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH " "客户端,Telnet 会在未来支持" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "Microsoft" msgstr "微软" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "Official" msgstr "官方" -#: templates/resource_download.html:34 +#: templates/resource_download.html:35 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" -#: templates/resource_download.html:43 +#: templates/resource_download.html:44 msgid "Windows Remote application publisher tools" msgstr "Windows 远程应用发布服务器工具" -#: templates/resource_download.html:44 +#: templates/resource_download.html:45 msgid "" "OpenSSH is a program used to connect remote applications in the Windows " "Remote Application Publisher" msgstr "OpenSSH 是在 windows 远程应用发布服务器中用来连接远程应用的程序" -#: templates/resource_download.html:52 +#: templates/resource_download.html:53 msgid "Offline video player" msgstr "离线录像播放器" @@ -5585,6 +5740,10 @@ msgstr "离线录像播放器" msgid "Invalid zip file" msgstr "无效的 zip 文件" +#: terminal/api/applet/applet.py:65 +msgid "This is enterprise edition applet" +msgstr "企业版远程应用,在社区版中不能使用" + #: terminal/api/component/endpoint.py:32 msgid "Not found protocol query params" msgstr "未发现 protocol 查询参数" @@ -5605,7 +5764,7 @@ msgstr "命令存储" msgid "Invalid" msgstr "无效" -#: terminal/api/component/storage.py:119 terminal/tasks.py:140 +#: terminal/api/component/storage.py:119 terminal/tasks.py:141 msgid "Test failure: {}" msgstr "测试失败: {}" @@ -5614,7 +5773,7 @@ msgid "Test successful" msgstr "测试成功" #: terminal/api/component/storage.py:124 terminal/notifications.py:240 -#: terminal/tasks.py:144 +#: terminal/tasks.py:145 msgid "Test failure: Account invalid" msgstr "测试失败: 账号无效" @@ -5642,23 +5801,43 @@ msgstr "未开启会话共享" msgid "Terminals" msgstr "终端管理" -#: terminal/backends/command/models.py:20 +#: terminal/backends/command/models.py:19 msgid "Input" msgstr "输入" -#: terminal/backends/command/models.py:21 terminal/serializers/command.py:74 +#: terminal/backends/command/models.py:20 terminal/serializers/command.py:73 msgid "Output" msgstr "输出" -#: terminal/backends/command/models.py:25 terminal/serializers/command.py:23 +#: terminal/backends/command/models.py:24 terminal/serializers/command.py:22 #: terminal/templates/terminal/_msg_command_warning.html:10 msgid "Risk level" msgstr "风险等级" +#: terminal/connect_methods.py:29 +msgid "SSH Client" +msgstr "SSH 客户端" + +#: terminal/connect_methods.py:30 +msgid "SSH Guide" +msgstr "SSH 向导" + +#: terminal/connect_methods.py:31 +msgid "SFTP Client" +msgstr "SFTP 客户端" + +#: terminal/connect_methods.py:33 +msgid "DB Guide" +msgstr "DB 连接向导" + #: terminal/connect_methods.py:34 msgid "DB Client" msgstr "数据库客户端" +#: terminal/connect_methods.py:36 +msgid "Remote Desktop" +msgstr "远程桌面客户端" + #: terminal/const.py:12 msgid "Review & Reject" msgstr "审批 & 拒绝" @@ -5708,6 +5887,18 @@ msgstr "只读" msgid "Writable" msgstr "读写" +#: terminal/const.py:94 +msgid "Kill Session" +msgstr "终断会话" + +#: terminal/const.py:95 +msgid "Lock Session" +msgstr "锁定会话" + +#: terminal/const.py:96 +msgid "Unlock Session" +msgstr "解锁会话" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "不支持批量创建" @@ -5728,7 +5919,7 @@ msgstr "企业版" msgid "Author" msgstr "作者" -#: terminal/models/applet/applet.py:37 terminal/serializers/applet.py:30 +#: terminal/models/applet/applet.py:37 terminal/serializers/applet.py:31 msgid "Edition" msgstr "版本" @@ -5740,7 +5931,7 @@ msgstr "可以并发" msgid "Tags" msgstr "标签" -#: terminal/models/applet/applet.py:47 terminal/serializers/storage.py:157 +#: terminal/models/applet/applet.py:47 terminal/serializers/storage.py:159 msgid "Hosts" msgstr "主机" @@ -5760,28 +5951,36 @@ msgstr "只支持自定义平台" msgid "Missing type in platform.yml" msgstr "在 platform.yml 中缺少类型" -#: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:34 -#: terminal/models/applet/host.py:134 +#: terminal/models/applet/applet.py:282 terminal/models/applet/host.py:35 +#: terminal/models/applet/host.py:137 msgid "Hosting" msgstr "宿主机" -#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:57 +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:57 msgid "Deploy options" msgstr "部署参数" +#: terminal/models/applet/host.py:19 +msgid "Auto create accounts" +msgstr "自动创建账号" + #: terminal/models/applet/host.py:20 +msgid "Accounts create amount" +msgstr "创建账号数量" + +#: terminal/models/applet/host.py:21 msgid "Inited" msgstr "已初始化" -#: terminal/models/applet/host.py:21 +#: terminal/models/applet/host.py:22 msgid "Date inited" msgstr "初始化日期" -#: terminal/models/applet/host.py:22 +#: terminal/models/applet/host.py:23 msgid "Date synced" msgstr "同步日期" -#: terminal/models/applet/host.py:135 +#: terminal/models/applet/host.py:138 msgid "Initial" msgstr "初始化" @@ -5818,18 +6017,18 @@ msgid "Redis port" msgstr "Redis 端口" #: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:100 -#: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:38 -#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 -#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 +#: terminal/models/component/endpoint.py:102 +#: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:40 +#: terminal/serializers/storage.py:52 terminal/serializers/storage.py:82 +#: terminal/serializers/storage.py:92 terminal/serializers/storage.py:100 msgid "Endpoint" msgstr "端点" -#: terminal/models/component/endpoint.py:93 +#: terminal/models/component/endpoint.py:95 msgid "IP group" msgstr "IP 组" -#: terminal/models/component/endpoint.py:106 +#: terminal/models/component/endpoint.py:108 msgid "Endpoint rule" msgstr "端点规则" @@ -5879,7 +6078,7 @@ msgstr "录像存储" msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:77 +#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:76 msgid "Remote Address" msgstr "远端地址" @@ -5887,7 +6086,7 @@ msgstr "远端地址" msgid "Application User" msgstr "应用用户" -#: terminal/models/component/terminal.py:165 +#: terminal/models/component/terminal.py:166 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -5911,7 +6110,7 @@ msgstr "可以下载会话录像" msgid "Account id" msgstr "账号 ID" -#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:104 +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:114 msgid "Login from" msgstr "登录来源" @@ -5923,27 +6122,31 @@ msgstr "回放" msgid "Date end" msgstr "结束日期" -#: terminal/models/session/session.py:240 +#: terminal/models/session/session.py:47 terminal/serializers/session.py:62 +msgid "Command amount" +msgstr "命令数量" + +#: terminal/models/session/session.py:281 msgid "Session record" msgstr "会话记录" -#: terminal/models/session/session.py:242 +#: terminal/models/session/session.py:283 msgid "Can monitor session" msgstr "可以监控会话" -#: terminal/models/session/session.py:243 +#: terminal/models/session/session.py:284 msgid "Can share session" msgstr "可以分享会话" -#: terminal/models/session/session.py:244 +#: terminal/models/session/session.py:285 msgid "Can terminate session" msgstr "可以终断会话" -#: terminal/models/session/session.py:245 +#: terminal/models/session/session.py:286 msgid "Can validate session action perm" msgstr "可以验证会话动作权限" -#: terminal/models/session/sharing.py:30 +#: terminal/models/session/sharing.py:31 msgid "Expired time (min)" msgstr "过期时间 (分)" @@ -5952,47 +6155,52 @@ msgstr "过期时间 (分)" msgid "Action permission" msgstr "操作权限" -#: terminal/models/session/sharing.py:40 terminal/models/session/sharing.py:86 +#: terminal/models/session/sharing.py:37 +msgid "Origin" +msgstr "来源" + +#: terminal/models/session/sharing.py:41 terminal/models/session/sharing.py:96 +#: terminal/notifications.py:261 msgid "Session sharing" msgstr "会话分享" -#: terminal/models/session/sharing.py:42 +#: terminal/models/session/sharing.py:43 msgid "Can add super session sharing" msgstr "可以创建超级会话分享" -#: terminal/models/session/sharing.py:69 +#: terminal/models/session/sharing.py:79 msgid "Link not active" msgstr "链接失效" -#: terminal/models/session/sharing.py:71 +#: terminal/models/session/sharing.py:81 msgid "Link expired" msgstr "链接过期" -#: terminal/models/session/sharing.py:73 +#: terminal/models/session/sharing.py:83 msgid "User not allowed to join" msgstr "该用户无权加入会话" -#: terminal/models/session/sharing.py:90 terminal/serializers/sharing.py:71 +#: terminal/models/session/sharing.py:100 terminal/serializers/sharing.py:71 msgid "Joiner" msgstr "加入者" -#: terminal/models/session/sharing.py:93 +#: terminal/models/session/sharing.py:103 msgid "Date joined" msgstr "加入日期" -#: terminal/models/session/sharing.py:96 +#: terminal/models/session/sharing.py:106 msgid "Date left" msgstr "结束日期" -#: terminal/models/session/sharing.py:119 +#: terminal/models/session/sharing.py:129 msgid "Session join record" msgstr "会话加入记录" -#: terminal/models/session/sharing.py:135 +#: terminal/models/session/sharing.py:145 msgid "Invalid verification code" msgstr "验证码不正确" -#: terminal/models/session/sharing.py:142 +#: terminal/models/session/sharing.py:152 msgid "You have already joined this session" msgstr "您已经加入过此会话" @@ -6062,7 +6270,7 @@ msgstr "" "建议填写内网地址,否则填写当前站点 URL
例如:https://172.16.10.110 or " "https://dev.jumpserver.com" -#: terminal/serializers/applet_host.py:46 terminal/serializers/storage.py:168 +#: terminal/serializers/applet_host.py:46 terminal/serializers/storage.py:170 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -6094,35 +6302,52 @@ msgstr "RDS 远程应用注销时间限制" msgid "Load status" msgstr "负载状态" -#: terminal/serializers/command.py:20 +#: terminal/serializers/applet_host.py:72 +msgid "" +"These accounts are used to connect to the published application, the account " +"is now divided into two types, one is dedicated to each account, each user " +"has a private account, the other is public, when the application does not " +"support multiple open and the special has been used, the public account will " +"be used to connect" +msgstr "" +"这些账号用于连接发布的应用,账号现在分为两种类型:
一种是专用的,每个用" +"户都有一个专用账号。 另一种是公共的,当应用不支持多开且专用的已经被使用时,会" +"使用公共账号连接;
注意: 如果不开启自动创建账号, 当前发布机仅能被指定标" +"签的资产调度到,默认不会放到调度池中" + +#: terminal/serializers/applet_host.py:77 +msgid "The number of public accounts created automatically" +msgstr "公用账号自动创建的数量" + +#: terminal/serializers/command.py:19 msgid "Session ID" msgstr "会话ID" -#: terminal/serializers/command.py:42 +#: terminal/serializers/command.py:41 msgid "Command Filter ACL" msgstr "命令过滤器" -#: terminal/serializers/command.py:45 +#: terminal/serializers/command.py:44 msgid "Command Group" msgstr "命令组" -#: terminal/serializers/command.py:56 +#: terminal/serializers/command.py:55 msgid "Invalid command filter ACL id" msgstr "无效的 命令过滤器 ID" -#: terminal/serializers/command.py:60 +#: terminal/serializers/command.py:59 msgid "Invalid command group id" msgstr "无效的 命令组 ID" -#: terminal/serializers/command.py:64 +#: terminal/serializers/command.py:63 msgid "Invalid session id" msgstr "无效的 Session ID" -#: terminal/serializers/command.py:73 +#: terminal/serializers/command.py:72 msgid "Account " msgstr "账号" -#: terminal/serializers/command.py:75 +#: terminal/serializers/command.py:74 msgid "Timestamp" msgstr "时间戳" @@ -6148,8 +6373,8 @@ msgid "" "access address of the current browser will be used (the default endpoint " "does not allow modification of the host)" msgstr "" -"连接资产时访问的主机地址,如果为空则使用当前浏览器的访问地址(默认端点不允许" -"修改主机)" +"连接资产时访问的主机地址,如果为空则使用当前浏览器的访问地址 (默认端点不允许" +"修改主机)" #: terminal/serializers/endpoint.py:64 msgid "" @@ -6166,100 +6391,100 @@ msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" msgid "Asset IP" msgstr "资产 IP" -#: terminal/serializers/session.py:22 terminal/serializers/session.py:44 +#: terminal/serializers/session.py:23 terminal/serializers/session.py:47 msgid "Can replay" msgstr "是否可重放" -#: terminal/serializers/session.py:23 terminal/serializers/session.py:45 +#: terminal/serializers/session.py:24 terminal/serializers/session.py:48 msgid "Can join" msgstr "是否可加入" -#: terminal/serializers/session.py:24 terminal/serializers/session.py:48 +#: terminal/serializers/session.py:25 terminal/serializers/session.py:51 msgid "Can terminate" msgstr "是否可中断" -#: terminal/serializers/session.py:40 +#: terminal/serializers/session.py:43 msgid "User ID" msgstr "用户 ID" -#: terminal/serializers/session.py:41 +#: terminal/serializers/session.py:44 msgid "Asset ID" msgstr "资产 ID" -#: terminal/serializers/session.py:42 +#: terminal/serializers/session.py:45 msgid "Login from display" msgstr "登录来源名称" -#: terminal/serializers/session.py:49 +#: terminal/serializers/session.py:52 msgid "Terminal display" msgstr "终端显示" -#: terminal/serializers/session.py:54 -msgid "Command amount" -msgstr "命令数量" - -#: terminal/serializers/storage.py:20 +#: terminal/serializers/storage.py:22 msgid "Endpoint invalid: remove path `{}`" msgstr "端点无效: 移除路径 `{}`" -#: terminal/serializers/storage.py:26 +#: terminal/serializers/storage.py:28 msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:30 +#: terminal/serializers/storage.py:32 #: xpack/plugins/cloud/serializers/account_attrs.py:17 msgid "Access key id" msgstr "Access key ID(AK)" -#: terminal/serializers/storage.py:34 +#: terminal/serializers/storage.py:36 #: xpack/plugins/cloud/serializers/account_attrs.py:20 msgid "Access key secret" msgstr "Access key secret(SK)" -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:209 +#: terminal/serializers/storage.py:67 xpack/plugins/cloud/models.py:250 msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:109 +#: terminal/serializers/storage.py:111 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:112 +#: terminal/serializers/storage.py:114 msgid "Account key" msgstr "账号密钥" -#: terminal/serializers/storage.py:115 +#: terminal/serializers/storage.py:117 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:135 +#: terminal/serializers/storage.py:137 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:142 +#: terminal/serializers/storage.py:144 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:145 +#: terminal/serializers/storage.py:147 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:160 +#: terminal/serializers/storage.py:162 msgid "Index by date" msgstr "按日期建索引" -#: terminal/serializers/storage.py:161 +#: terminal/serializers/storage.py:163 msgid "Whether to create an index by date" msgstr "是否根据日期动态建立索引" -#: terminal/serializers/storage.py:164 +#: terminal/serializers/storage.py:166 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:166 +#: terminal/serializers/storage.py:168 msgid "Doc type" msgstr "文档类型" +#: terminal/serializers/task.py:9 +msgid "Session id" +msgstr "会话 ID" + #: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91 msgid "Not found" msgstr "没有发现" @@ -6284,11 +6509,11 @@ msgstr "运行应用机部署" msgid "Install applet" msgstr "安装应用" -#: terminal/tasks.py:110 +#: terminal/tasks.py:111 msgid "Generate applet host accounts" msgstr "收集远程应用上的账号" -#: terminal/tasks.py:122 +#: terminal/tasks.py:123 msgid "Check command replay storage connectivity" msgstr "检查命令及录像存储可连接性 " @@ -6431,7 +6656,7 @@ msgstr "工单批准信息" msgid "Ticket flow" msgstr "工单流程" -#: tickets/models/relation.py:10 +#: tickets/models/relation.py:12 msgid "Ticket session relation" msgstr "工单会话" @@ -6505,11 +6730,11 @@ msgstr "审批步骤" msgid "Relation snapshot" msgstr "工单快照" -#: tickets/models/ticket/general.py:398 +#: tickets/models/ticket/general.py:401 msgid "Please try again" msgstr "请再次尝试" -#: tickets/models/ticket/general.py:467 +#: tickets/models/ticket/general.py:470 msgid "Super ticket" msgstr "超级工单" @@ -6615,7 +6840,7 @@ msgid "Ticket information" msgstr "工单信息" #: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:38 +#: tickets/views/approve.py:39 msgid "Ticket approval" msgstr "工单审批" @@ -6627,28 +6852,32 @@ msgstr "同意" msgid "Go Login" msgstr "去登录" -#: tickets/views/approve.py:39 +#: tickets/views/approve.py:40 msgid "" "This ticket does not exist, the process has ended, or this link has expired" msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" -#: tickets/views/approve.py:68 +#: tickets/views/approve.py:69 msgid "Click the button below to approve or reject" msgstr "点击下方按钮同意或者拒绝" -#: tickets/views/approve.py:70 +#: tickets/views/approve.py:71 msgid "After successful authentication, this ticket can be approved directly" msgstr "认证成功后,工单可直接审批" -#: tickets/views/approve.py:92 +#: tickets/views/approve.py:93 msgid "Illegal approval action" msgstr "无效的审批动作" -#: tickets/views/approve.py:105 +#: tickets/views/approve.py:106 msgid "This user is not authorized to approve this ticket" msgstr "此用户无权审批此工单" -#: users/api/user.py:191 +#: users/api/user.py:141 +msgid "Can not invite self" +msgstr "不能邀请自己" + +#: users/api/user.py:194 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" @@ -6690,8 +6919,8 @@ msgid "" "in. you can also directly bind in \"personal information -> quick " "modification -> change MFA Settings\"!" msgstr "" -"启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速" -"修改->设置 MFA 多因子认证)中直接绑定!" +"启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在 (个人信息->快速" +"修改->设置 MFA 多因子认证)中直接绑定!" #: users/forms/profile.py:61 msgid "* Enable MFA to make the account more secure." @@ -6703,8 +6932,8 @@ msgid "" "and key sensitive information properly. (for example: setting complex " "password, enabling MFA)" msgstr "" -"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:" -"设置复杂密码,并启用 MFA 多因子认证)" +"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息; (如:" +"设置复杂密码,并启用 MFA 多因子认证)" #: users/forms/profile.py:77 msgid "Finish" @@ -6759,78 +6988,78 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:173 users/models/user.py:786 -#: xpack/plugins/cloud/serializers/account_attrs.py:206 +#: users/forms/profile.py:173 users/models/user.py:820 +#: xpack/plugins/cloud/serializers/account_attrs.py:203 msgid "Public key" msgstr "SSH公钥" -#: users/models/user.py:603 users/serializers/profile.py:118 +#: users/models/user.py:637 users/serializers/profile.py:118 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:765 users/serializers/user.py:171 +#: users/models/user.py:799 users/serializers/user.py:171 msgid "Is service account" msgstr "服务账号" -#: users/models/user.py:767 +#: users/models/user.py:801 msgid "Avatar" msgstr "头像" -#: users/models/user.py:770 +#: users/models/user.py:804 msgid "Wechat" msgstr "微信" -#: users/models/user.py:773 users/serializers/user.py:108 +#: users/models/user.py:807 users/serializers/user.py:108 msgid "Phone" msgstr "手机" -#: users/models/user.py:779 +#: users/models/user.py:813 msgid "OTP secret key" msgstr "OTP 密钥" -#: users/models/user.py:783 -#: xpack/plugins/cloud/serializers/account_attrs.py:209 +#: users/models/user.py:817 +#: xpack/plugins/cloud/serializers/account_attrs.py:206 msgid "Private key" msgstr "ssh私钥" -#: users/models/user.py:789 +#: users/models/user.py:823 msgid "Secret key" msgstr "Secret key" -#: users/models/user.py:794 users/serializers/profile.py:149 +#: users/models/user.py:828 users/serializers/profile.py:149 #: users/serializers/user.py:168 msgid "Is first login" msgstr "首次登录" -#: users/models/user.py:808 +#: users/models/user.py:842 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:811 +#: users/models/user.py:845 msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:949 +#: users/models/user.py:988 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:950 +#: users/models/user.py:989 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:951 +#: users/models/user.py:990 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:960 +#: users/models/user.py:999 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:963 +#: users/models/user.py:1002 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:988 +#: users/models/user.py:1027 msgid "User password history" msgstr "用户密码历史" @@ -6905,7 +7134,7 @@ msgstr "强制 MFA" msgid "Login blocked" msgstr "登录被锁定" -#: users/serializers/user.py:98 users/serializers/user.py:176 +#: users/serializers/user.py:98 users/serializers/user.py:177 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" @@ -6913,23 +7142,27 @@ msgstr "是否绑定了虚拟 MFA" msgid "Can public key authentication" msgstr "可以使用公钥认证" -#: users/serializers/user.py:173 +#: users/serializers/user.py:172 +msgid "Is org admin" +msgstr "组织管理员" + +#: users/serializers/user.py:174 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:177 +#: users/serializers/user.py:178 msgid "MFA level" msgstr "MFA 级别" -#: users/serializers/user.py:283 +#: users/serializers/user.py:284 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:284 +#: users/serializers/user.py:285 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:317 +#: users/serializers/user.py:318 msgid "name not unique" msgstr "名称重复" @@ -6941,22 +7174,26 @@ msgid "" msgstr "" "管理员已开启'仅允许已存在用户登录',当前用户不在用户列表中,请联系管理员。" -#: users/tasks.py:21 +#: users/tasks.py:24 msgid "Check password expired" msgstr "校验密码已过期" -#: users/tasks.py:35 +#: users/tasks.py:38 msgid "Periodic check password expired" msgstr "周期校验密码过期" -#: users/tasks.py:49 +#: users/tasks.py:52 msgid "Check user expired" msgstr "校验用户已过期" -#: users/tasks.py:66 +#: users/tasks.py:69 msgid "Periodic check user expired" msgstr "周期检测用户过期" +#: users/tasks.py:83 +msgid "Check unused users" +msgstr "校验用户已过期" + #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" msgstr "您的账号即将过期" @@ -7124,7 +7361,7 @@ msgstr "iPhone手机下载" msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." -msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)" +msgstr "安装完成后点击下一步进入绑定页面 (如已安装,直接进入下一步)" #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 @@ -7145,35 +7382,35 @@ msgstr "账号保护已开启,请根据提示完成以下操作" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "请打开 MFA 验证器,输入 6 位动态码" -#: users/views/profile/otp.py:87 +#: users/views/profile/otp.py:85 msgid "Already bound" msgstr "已经绑定" -#: users/views/profile/otp.py:88 +#: users/views/profile/otp.py:86 msgid "MFA already bound, disable first, then bound" msgstr "MFA(OTP) 已经绑定,请先禁用,再绑定" -#: users/views/profile/otp.py:115 +#: users/views/profile/otp.py:113 msgid "OTP enable success" msgstr "MFA(OTP) 启用成功" -#: users/views/profile/otp.py:116 +#: users/views/profile/otp.py:114 msgid "OTP enable success, return login page" msgstr "MFA(OTP) 启用成功,返回到登录页面" -#: users/views/profile/otp.py:158 +#: users/views/profile/otp.py:156 msgid "Disable OTP" msgstr "禁用虚拟 MFA(OTP)" -#: users/views/profile/otp.py:164 +#: users/views/profile/otp.py:162 msgid "OTP disable success" msgstr "MFA(OTP) 禁用成功" -#: users/views/profile/otp.py:165 +#: users/views/profile/otp.py:163 msgid "OTP disable success, return login page" msgstr "MFA(OTP) 禁用成功,返回登录页面" -#: users/views/profile/password.py:36 users/views/profile/password.py:41 +#: users/views/profile/password.py:33 users/views/profile/password.py:38 msgid "Password invalid" msgstr "用户名或密码无效" @@ -7207,11 +7444,11 @@ msgstr "重置密码成功,返回到登录页面" msgid "XPACK" msgstr "XPack" -#: xpack/plugins/cloud/api.py:40 +#: xpack/plugins/cloud/api.py:56 msgid "Test connection successful" msgstr "测试成功" -#: xpack/plugins/cloud/api.py:42 +#: xpack/plugins/cloud/api.py:58 msgid "Test connection failed: {}" msgstr "测试连接失败:{}" @@ -7267,7 +7504,7 @@ msgstr "ucloud" msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:23 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:23 xpack/plugins/cloud/providers/nutanix.py:15 msgid "Nutanix" msgstr "Nutanix" @@ -7299,7 +7536,7 @@ msgstr "私有IP" msgid "Public IP" msgstr "公网IP" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:38 xpack/plugins/cloud/models.py:295 msgid "Instance name" msgstr "实例名称" @@ -7327,178 +7564,258 @@ msgstr "已同步" msgid "Released" msgstr "已释放" +#: xpack/plugins/cloud/manager.py:53 +msgid "Account unavailable" +msgstr "账号无效" + #: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:34 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:38 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:43 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:45 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:147 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:95 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:39 +#: xpack/plugins/cloud/models.py:100 +#: xpack/plugins/cloud/serializers/task.py:150 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:105 xpack/plugins/cloud/serializers/task.py:44 +#: xpack/plugins/cloud/models.py:103 +#: xpack/plugins/cloud/serializers/task.py:155 msgid "Sync IP type" msgstr "同步IP类型" -#: xpack/plugins/cloud/models.py:108 xpack/plugins/cloud/serializers/task.py:61 +#: xpack/plugins/cloud/models.py:106 +#: xpack/plugins/cloud/serializers/task.py:173 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:114 +#: xpack/plugins/cloud/models.py:112 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:119 xpack/plugins/cloud/models.py:160 +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/models.py:313 +#: xpack/plugins/cloud/models.py:337 +msgid "Strategy" +msgstr "策略" + +#: xpack/plugins/cloud/models.py:120 xpack/plugins/cloud/models.py:197 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:219 +#: xpack/plugins/cloud/models.py:208 xpack/plugins/cloud/models.py:260 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:212 +msgid "Sync instance snapshot" +msgstr "同步实例快照" + +#: xpack/plugins/cloud/models.py:216 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:199 +#: xpack/plugins/cloud/models.py:240 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:203 +#: xpack/plugins/cloud/models.py:244 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:206 +#: xpack/plugins/cloud/models.py:247 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:264 msgid "Sync instance detail" msgstr "同步实例详情" -#: xpack/plugins/cloud/providers/aws_international.py:17 -msgid "China (Beijing)" -msgstr "中国(北京)" +#: xpack/plugins/cloud/models.py:281 +msgid "Task strategy" +msgstr "任务策略" + +#: xpack/plugins/cloud/models.py:285 +msgid "Exact" +msgstr "" + +#: xpack/plugins/cloud/models.py:286 +msgid "Not" +msgstr "否" + +#: xpack/plugins/cloud/models.py:287 +msgid "In" +msgstr "在..里面" + +#: xpack/plugins/cloud/models.py:288 +msgid "Contains" +msgstr "包含" + +#: xpack/plugins/cloud/models.py:289 +msgid "Startswith" +msgstr "以..开头" + +#: xpack/plugins/cloud/models.py:290 +msgid "Endswith" +msgstr "以..结尾" + +#: xpack/plugins/cloud/models.py:296 +msgid "Instance platform" +msgstr "实例平台" + +#: xpack/plugins/cloud/models.py:297 +msgid "Instance address" +msgstr "实例地址" + +#: xpack/plugins/cloud/models.py:304 +msgid "Rule attr" +msgstr "规则属性" + +#: xpack/plugins/cloud/models.py:308 +msgid "Rule match" +msgstr "规则匹配" + +#: xpack/plugins/cloud/models.py:310 +msgid "Rule value" +msgstr "规则值" + +#: xpack/plugins/cloud/models.py:317 +msgid "Strategy rule" +msgstr "策略规则" + +#: xpack/plugins/cloud/models.py:332 +msgid "Action attr" +msgstr "动作属性" + +#: xpack/plugins/cloud/models.py:334 +msgid "Action value" +msgstr "动作值" + +#: xpack/plugins/cloud/models.py:341 +msgid "Strategy action" +msgstr "策略动作" #: xpack/plugins/cloud/providers/aws_international.py:18 -msgid "China (Ningxia)" -msgstr "中国(宁夏)" +msgid "China (Beijing)" +msgstr "中国 (北京)" -#: xpack/plugins/cloud/providers/aws_international.py:21 -msgid "US East (Ohio)" -msgstr "美国东部(俄亥俄州)" +#: xpack/plugins/cloud/providers/aws_international.py:19 +msgid "China (Ningxia)" +msgstr "中国 (宁夏)" #: xpack/plugins/cloud/providers/aws_international.py:22 -msgid "US East (N. Virginia)" -msgstr "美国东部(弗吉尼亚北部)" +msgid "US East (Ohio)" +msgstr "美国东部 (俄亥俄州)" #: xpack/plugins/cloud/providers/aws_international.py:23 -msgid "US West (N. California)" -msgstr "美国西部(加利福尼亚北部)" +msgid "US East (N. Virginia)" +msgstr "美国东部 (弗吉尼亚北部)" #: xpack/plugins/cloud/providers/aws_international.py:24 -msgid "US West (Oregon)" -msgstr "美国西部(俄勒冈)" +msgid "US West (N. California)" +msgstr "美国西部 (加利福尼亚北部)" #: xpack/plugins/cloud/providers/aws_international.py:25 -msgid "Africa (Cape Town)" -msgstr "非洲(开普敦)" +msgid "US West (Oregon)" +msgstr "美国西部 (俄勒冈)" #: xpack/plugins/cloud/providers/aws_international.py:26 -msgid "Asia Pacific (Hong Kong)" -msgstr "亚太地区(香港)" +msgid "Africa (Cape Town)" +msgstr "非洲 (开普敦)" #: xpack/plugins/cloud/providers/aws_international.py:27 -msgid "Asia Pacific (Mumbai)" -msgstr "亚太地区(孟买)" +msgid "Asia Pacific (Hong Kong)" +msgstr "亚太地区 (香港)" #: xpack/plugins/cloud/providers/aws_international.py:28 -msgid "Asia Pacific (Osaka-Local)" -msgstr "亚太区域(大阪当地)" +msgid "Asia Pacific (Mumbai)" +msgstr "亚太地区 (孟买)" #: xpack/plugins/cloud/providers/aws_international.py:29 -msgid "Asia Pacific (Seoul)" -msgstr "亚太区域(首尔)" +msgid "Asia Pacific (Osaka-Local)" +msgstr "亚太区域 (大阪当地)" #: xpack/plugins/cloud/providers/aws_international.py:30 -msgid "Asia Pacific (Singapore)" -msgstr "亚太区域(新加坡)" +msgid "Asia Pacific (Seoul)" +msgstr "亚太区域 (首尔)" #: xpack/plugins/cloud/providers/aws_international.py:31 -msgid "Asia Pacific (Sydney)" -msgstr "亚太区域(悉尼)" +msgid "Asia Pacific (Singapore)" +msgstr "亚太区域 (新加坡)" #: xpack/plugins/cloud/providers/aws_international.py:32 -msgid "Asia Pacific (Tokyo)" -msgstr "亚太区域(东京)" +msgid "Asia Pacific (Sydney)" +msgstr "亚太区域 (悉尼)" #: xpack/plugins/cloud/providers/aws_international.py:33 -msgid "Canada (Central)" -msgstr "加拿大(中部)" +msgid "Asia Pacific (Tokyo)" +msgstr "亚太区域 (东京)" #: xpack/plugins/cloud/providers/aws_international.py:34 -msgid "Europe (Frankfurt)" -msgstr "欧洲(法兰克福)" +msgid "Canada (Central)" +msgstr "加拿大 (中部)" #: xpack/plugins/cloud/providers/aws_international.py:35 -msgid "Europe (Ireland)" -msgstr "欧洲(爱尔兰)" +msgid "Europe (Frankfurt)" +msgstr "欧洲 (法兰克福)" #: xpack/plugins/cloud/providers/aws_international.py:36 -msgid "Europe (London)" -msgstr "欧洲(伦敦)" +msgid "Europe (Ireland)" +msgstr "欧洲 (爱尔兰)" #: xpack/plugins/cloud/providers/aws_international.py:37 -msgid "Europe (Milan)" -msgstr "欧洲(米兰)" +msgid "Europe (London)" +msgstr "欧洲 (伦敦)" #: xpack/plugins/cloud/providers/aws_international.py:38 -msgid "Europe (Paris)" -msgstr "欧洲(巴黎)" +msgid "Europe (Milan)" +msgstr "欧洲 (米兰)" #: xpack/plugins/cloud/providers/aws_international.py:39 -msgid "Europe (Stockholm)" -msgstr "欧洲(斯德哥尔摩)" +msgid "Europe (Paris)" +msgstr "欧洲 (巴黎)" #: xpack/plugins/cloud/providers/aws_international.py:40 -msgid "Middle East (Bahrain)" -msgstr "中东(巴林)" +msgid "Europe (Stockholm)" +msgstr "欧洲 (斯德哥尔摩)" #: xpack/plugins/cloud/providers/aws_international.py:41 +msgid "Middle East (Bahrain)" +msgstr "中东 (巴林)" + +#: xpack/plugins/cloud/providers/aws_international.py:42 msgid "South America (São Paulo)" -msgstr "南美洲(圣保罗)" +msgstr "南美洲 (圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 +#: xpack/plugins/cloud/providers/jdcloud.py:125 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 -#: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 +#: xpack/plugins/cloud/providers/huaweicloud.py:42 +#: xpack/plugins/cloud/providers/jdcloud.py:128 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -7507,7 +7824,7 @@ msgid "CN East-Suzhou" msgstr "华东-苏州" #: xpack/plugins/cloud/providers/baiducloud.py:57 -#: xpack/plugins/cloud/providers/huaweicloud.py:48 +#: xpack/plugins/cloud/providers/huaweicloud.py:49 msgid "CN-Hong Kong" msgstr "中国-香港" @@ -7520,72 +7837,72 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN East-Shanghai" msgstr "华东-上海" #: xpack/plugins/cloud/providers/baiducloud.py:61 -#: xpack/plugins/cloud/providers/huaweicloud.py:47 +#: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "AP-Singapore" msgstr "亚太-新加坡" -#: xpack/plugins/cloud/providers/huaweicloud.py:35 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" - -#: xpack/plugins/cloud/providers/huaweicloud.py:36 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:37 +#: xpack/plugins/cloud/providers/huaweicloud.py:39 msgid "CN North-Beijing1" msgstr "华北-北京1" -#: xpack/plugins/cloud/providers/huaweicloud.py:38 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:39 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" +#: xpack/plugins/cloud/providers/huaweicloud.py:40 +msgid "CN North-Beijing4" +msgstr "华北-北京4" #: xpack/plugins/cloud/providers/huaweicloud.py:41 -msgid "LA-Mexico City1" -msgstr "拉美-墨西哥城一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:42 -msgid "LA-Santiago" -msgstr "拉美-圣地亚哥" - -#: xpack/plugins/cloud/providers/huaweicloud.py:43 -msgid "LA-Sao Paulo1" -msgstr "拉美-圣保罗一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:44 -msgid "EU-Paris" -msgstr "欧洲-巴黎" - -#: xpack/plugins/cloud/providers/huaweicloud.py:45 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:46 -msgid "AP-Bangkok" -msgstr "亚太-曼谷" - -#: xpack/plugins/cloud/providers/huaweicloud.py:50 -msgid "CN Northeast-Dalian" -msgstr "华北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "CN North-Ulanqab1" msgstr "华北-乌兰察布一" -#: xpack/plugins/cloud/providers/huaweicloud.py:52 +#: xpack/plugins/cloud/providers/huaweicloud.py:43 +msgid "CN South-Shenzhen" +msgstr "华南-广州" + +#: xpack/plugins/cloud/providers/huaweicloud.py:44 msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 +#: xpack/plugins/cloud/providers/huaweicloud.py:45 +msgid "CN East-Shanghai2" +msgstr "华东-上海2" + +#: xpack/plugins/cloud/providers/huaweicloud.py:46 +msgid "CN East-Shanghai1" +msgstr "华东-上海1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:48 +msgid "CN Southwest-Guiyang1" +msgstr "西南-贵阳1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:50 +msgid "AP-Bangkok" +msgstr "亚太-曼谷" + +#: xpack/plugins/cloud/providers/huaweicloud.py:53 +msgid "AF-Johannesburg" +msgstr "非洲-约翰内斯堡" + +#: xpack/plugins/cloud/providers/huaweicloud.py:54 +msgid "LA-Mexico City1" +msgstr "拉美-墨西哥城一" + +#: xpack/plugins/cloud/providers/huaweicloud.py:55 +msgid "LA-Santiago" +msgstr "拉美-圣地亚哥" + +#: xpack/plugins/cloud/providers/huaweicloud.py:56 +msgid "LA-Sao Paulo1" +msgstr "拉美-圣保罗一" + +#: xpack/plugins/cloud/providers/huaweicloud.py:58 +msgid "TR-Istanbul" +msgstr "" + +#: xpack/plugins/cloud/providers/jdcloud.py:126 msgid "CN East-Suqian" msgstr "华东-宿迁" @@ -7613,7 +7930,7 @@ msgstr "订阅 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:103 #: xpack/plugins/cloud/serializers/account_attrs.py:119 #: xpack/plugins/cloud/serializers/account_attrs.py:149 -#: xpack/plugins/cloud/serializers/account_attrs.py:202 +#: xpack/plugins/cloud/serializers/account_attrs.py:199 msgid "API Endpoint" msgstr "API 端点" @@ -7678,11 +7995,11 @@ msgstr "测试端口" msgid "Test timeout" msgstr "测试超时时间" -#: xpack/plugins/cloud/serializers/account_attrs.py:212 +#: xpack/plugins/cloud/serializers/account_attrs.py:209 msgid "Project" msgstr "project" -#: xpack/plugins/cloud/serializers/task.py:28 +#: xpack/plugins/cloud/serializers/task.py:139 msgid "" "Only instances matching the IP range will be synced.
If the instance " "contains multiple IP addresses, the first IP address that matches will be " @@ -7694,11 +8011,11 @@ msgstr "" "到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示同步所有实例和随机匹配 " "IP 地址。
例如: 192.168.1.0/24,10.1.1.1-10.1.1.20。" -#: xpack/plugins/cloud/serializers/task.py:34 +#: xpack/plugins/cloud/serializers/task.py:145 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers/task.py:35 +#: xpack/plugins/cloud/serializers/task.py:146 msgid "Instance count" msgstr "实例个数" @@ -7710,15 +8027,11 @@ msgstr "执行同步实例任务" msgid "Period clean sync instance task execution" msgstr "定期清除同步实例任务执行记录" -#: xpack/plugins/cloud/utils.py:69 -msgid "Account unavailable" -msgstr "账号无效" - #: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." msgstr "恢复默认成功!" -#: xpack/plugins/interface/meta.py:10 +#: xpack/plugins/interface/meta.py:9 msgid "Interface settings" msgstr "界面设置" @@ -7750,15 +8063,15 @@ msgstr "主题" msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/license/api.py:50 +#: xpack/plugins/license/api.py:52 msgid "License import successfully" msgstr "许可证导入成功" -#: xpack/plugins/license/api.py:51 +#: xpack/plugins/license/api.py:53 msgid "License is invalid" msgstr "无效的许可证" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 +#: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:138 msgid "License" msgstr "许可证" @@ -7777,12 +8090,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:86 msgid "Community edition" msgstr "社区版" - -#~ msgid "Item" -#~ msgstr "项目" - -#~ msgid "Url" -#~ msgstr "链接" - -#~ msgid "Danger command alert" -#~ msgstr "危险命令告警" diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index 306f2b55b..07ce8ff48 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class NotificationsConfig(AppConfig): @@ -7,6 +7,6 @@ class NotificationsConfig(AppConfig): verbose_name = _('Notifications') def ready(self): - from . import signal_handlers - from . import notifications + from . import signal_handlers # noqa + from . import notifications # noqa super().ready() diff --git a/apps/notifications/models/notification.py b/apps/notifications/models/notification.py index 410d07a28..fe639c7e8 100644 --- a/apps/notifications/models/notification.py +++ b/apps/notifications/models/notification.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel, CASCADE_SIGNAL_SKIP diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 9c0d03849..a650b158e 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -76,6 +76,16 @@ class JMSInventory: var['ansible_ssh_private_key_file'] = account.private_key_path return var + @staticmethod + def make_custom_become_ansible_vars(account, platform): + var = { + 'custom_become': True, 'custom_become_method': platform.su_method, + 'custom_become_user': account.su_from.username, + 'custom_become_password': account.su_from.secret, + 'custom_become_private_key_path': account.su_from.private_key_path + } + return var + def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway): from accounts.const import AutomationTypes if not account: @@ -89,6 +99,7 @@ class JMSInventory: su_from = account.su_from if platform.su_enabled and su_from: host.update(self.make_account_ansible_vars(su_from)) + host.update(self.make_custom_become_ansible_vars(account, platform)) become_method = 'sudo' if platform.su_method != 'su' else 'su' host['ansible_become'] = True host['ansible_become_method'] = 'sudo' @@ -116,7 +127,9 @@ class JMSInventory: } host['jms_asset']['port'] = port else: - host.update(self.make_proxy_command(gateway)) + ansible_ssh_common_args = self.make_proxy_command(gateway) + host['jms_asset'].update(ansible_ssh_common_args) + host.update(ansible_ssh_common_args) @staticmethod def get_primary_protocol(ansible_config, protocols): @@ -270,8 +283,13 @@ class JMSInventory: name = host.pop('name') name = name.replace('[', '_').replace(']', '_') data['all']['hosts'][name] = host - if self.exclude_localhost and data['all']['hosts'].__contains__('localhost'): - data['all']['hosts'].update({'localhost': {'ansible_host': '255.255.255.255'}}) + if not self.exclude_localhost: + data['all']['hosts'].update({ + 'localhost': { + 'ansible_host': '127.0.0.1', + 'ansible_connection': 'local' + } + }) return data def write_to_file(self, path): diff --git a/apps/ops/ansible/modules/custom_command.py b/apps/ops/ansible/modules/custom_command.py index e4f7cf11d..947da2d31 100644 --- a/apps/ops/ansible/modules/custom_command.py +++ b/apps/ops/ansible/modules/custom_command.py @@ -90,9 +90,6 @@ def main(): name=dict(required=True, aliases=['user']), password=dict(aliases=['pass'], no_log=True), commands=dict(type='list', required=False), - first_conn_delay_time=dict( - type='float', required=False, default=0.5 - ), ) module = AnsibleModule(argument_spec=argument_spec) @@ -102,10 +99,10 @@ def main(): module.fail_json( msg='No command found, please go to the platform details to add' ) - err = ssh_client.execute(commands) - if err: + output, err_msg = ssh_client.execute(commands) + if err_msg: module.fail_json( - msg='There was a problem executing the command: %s' % err + msg='There was a problem executing the command: %s' % err_msg ) user = module.params['name'] diff --git a/apps/ops/ansible/modules_utils/custom_common.py b/apps/ops/ansible/modules_utils/custom_common.py index 5da1a725e..0eb454e5a 100644 --- a/apps/ops/ansible/modules_utils/custom_common.py +++ b/apps/ops/ansible/modules_utils/custom_common.py @@ -1,7 +1,8 @@ +import re import time import paramiko -from paramiko.ssh_exception import SSHException, NoValidConnectionsError +from sshtunnel import SSHTunnelForwarder def common_argument_spec(): @@ -12,6 +13,14 @@ def common_argument_spec(): login_password=dict(type='str', required=False, no_log=True), login_secret_type=dict(type='str', required=False, default='password'), login_private_key_path=dict(type='str', required=False, no_log=True), + first_conn_delay_time=dict(type='float', required=False, default=0.5), + gateway_args=dict(type='str', required=False, default=''), + + become=dict(type='bool', default=False, required=False), + become_method=dict(type='str', required=False), + become_user=dict(type='str', required=False), + become_password=dict(type='str', required=False, no_log=True), + become_private_key_path=dict(type='str', required=False, no_log=True), ) return options @@ -19,49 +28,147 @@ def common_argument_spec(): class SSHClient: def __init__(self, module): self.module = module + self.channel = None self.is_connect = False + self.gateway_server = None self.client = paramiko.SSHClient() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.connect_params = self.get_connect_params() def get_connect_params(self): params = { 'allow_agent': False, 'look_for_keys': False, 'hostname': self.module.params['login_host'], 'port': self.module.params['login_port'], - 'username': self.module.params['login_user'], + 'key_filename': self.module.params['login_private_key_path'] or None } - secret_type = self.module.params['login_secret_type'] - if secret_type == 'ssh_key': - params['key_filename'] = self.module.params['login_private_key_path'] + if self.module.params['become']: + params['username'] = self.module.params['become_user'] + params['password'] = self.module.params['become_password'] + params['key_filename'] = self.module.params['become_private_key_path'] or None else: + params['username'] = self.module.params['login_user'] params['password'] = self.module.params['login_password'] + params['key_filename'] = self.module.params['login_private_key_path'] or None return params + def _get_channel(self): + self.channel = self.client.invoke_shell() + # 读取首次登陆终端返回的消息 + self.channel.recv(2048) + # 网络设备一般登录有延迟,等终端有返回后再执行命令 + delay_time = self.module.params['first_conn_delay_time'] + time.sleep(delay_time) + + @staticmethod + def _is_match_user(user, content): + # 正常命令切割后是[命令,用户名,交互前缀] + remote_user = content.split()[1] if len(content.split()) >= 3 else None + return remote_user and remote_user == user + + def switch_user(self): + self._get_channel() + if not self.module.params['become']: + return None + method = self.module.params['become_method'] + username = self.module.params['login_user'] + if method == 'sudo': + switch_method = 'sudo su -' + password = self.module.params['become_password'] + elif method == 'su': + switch_method = 'su -' + password = self.module.params['login_password'] + else: + self.module.fail_json(msg='Become method %s not support' % method) + return + commands = [f'{switch_method} {username}', password] + su_output, err_msg = self.execute(commands) + if err_msg: + return err_msg + i_output, err_msg = self.execute(['whoami']) + if err_msg: + return err_msg + + if self._is_match_user(username, i_output): + err_msg = '' + else: + err_msg = su_output + return err_msg + + def local_gateway_prepare(self): + gateway_args = self.module.params['gateway_args'] or '' + pattern = r"(?:sshpass -p ([\w@]+))?\s*ssh -o Port=(\d+)\s+-o StrictHostKeyChecking=no\s+([\w@]+)@([" \ + r"\d.]+)\s+-W %h:%p -q(?: -i (.+))?'" + match = re.search(pattern, gateway_args) + + if not match: + return + + password, port, username, address, private_key_path = match.groups() + password = password if password else None + private_key_path = private_key_path if private_key_path else None + remote_hostname = self.module.params['login_host'] + remote_port = self.module.params['login_port'] + + server = SSHTunnelForwarder( + (address, int(port)), + ssh_username=username, + ssh_password=password, + ssh_pkey=private_key_path, + remote_bind_address=(remote_hostname, remote_port) + ) + + server.start() + self.connect_params['hostname'] = '127.0.0.1' + self.connect_params['port'] = server.local_bind_port + self.gateway_server = server + + def local_gateway_clean(self): + gateway_server = self.gateway_server + if not gateway_server: + return + try: + gateway_server.stop() + except Exception: + pass + + def before_runner_start(self): + self.local_gateway_prepare() + + def after_runner_end(self): + self.local_gateway_clean() + def connect(self): try: - self.client.connect(**self.get_connect_params()) - except (SSHException, NoValidConnectionsError) as err: - err_msg = str(err) - else: + self.before_runner_start() + self.client.connect(**self.connect_params) self.is_connect = True - err_msg = '' + err_msg = self.switch_user() + self.after_runner_end() + except Exception as err: + err_msg = str(err) return err_msg + def _get_recv(self, size=1024, encoding='utf-8'): + output = self.channel.recv(size).decode(encoding) + return output + def execute(self, commands): if not self.is_connect: self.connect() - - channel = self.client.invoke_shell() - # 读取首次登陆终端返回的消息 - channel.recv(2048) - # 网络设备一般登录有延迟,等终端有返回后再执行命令 - delay_time = self.module.params['first_conn_delay_time'] - time.sleep(delay_time) - err_msg = '' + output, error_msg = '', '' try: for command in commands: - channel.send(command + '\n') + self.channel.send(command + '\n') time.sleep(0.3) - except SSHException as e: - err_msg = str(e) - return err_msg + output = self._get_recv() + except Exception as e: + error_msg = str(e) + return output, error_msg + + def __del__(self): + try: + self.channel.close() + self.client.close() + except: + pass diff --git a/apps/ops/ansible/modules_utils/oracle_common.py b/apps/ops/ansible/modules_utils/oracle_common.py index 8036a8c1e..a89543bc1 100644 --- a/apps/ops/ansible/modules_utils/oracle_common.py +++ b/apps/ops/ansible/modules_utils/oracle_common.py @@ -56,7 +56,7 @@ class OracleClient(object): def cursor(self): if self._cursor is None: try: - oracledb.init_oracle_client(lib_dir='/opt/oracle/instantclient_19_10') + # oracledb.init_oracle_client(lib_dir='/opt/oracle/instantclient_19_10') self._conn = oracledb.connect(**self.connect_params) self._cursor = self._conn.cursor() except DatabaseError as err: diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index d4540269c..67f4d4dda 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -5,7 +5,7 @@ import re from celery.result import AsyncResult from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django_celery_beat.models import PeriodicTask from rest_framework import generics, viewsets, mixins, status from rest_framework.response import Response diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 9d74aa565..72e62cb82 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -4,7 +4,7 @@ import zipfile from django.conf import settings from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status from common.exceptions import JMSException diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 7956a5dc1..a29a03749 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.utils.translation import gettext_lazy as _ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class OpsConfig(AppConfig): @@ -12,8 +12,8 @@ class OpsConfig(AppConfig): from orgs.models import Organization from orgs.utils import set_current_org set_current_org(Organization.root()) - from .celery import signal_handler - from . import signal_handlers - from . import notifications - from . import tasks + from .celery import signal_handler # noqa + from . import signal_handlers # noqa + from . import notifications # noqa + from . import tasks # noqa super().ready() diff --git a/apps/ops/const.py b/apps/ops/const.py index 5f31bb00d..01889a4b2 100644 --- a/apps/ops/const.py +++ b/apps/ops/const.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class StrategyChoice(models.TextChoices): @@ -49,6 +49,9 @@ class Modules(models.TextChoices): shell = 'shell', _('Shell') winshell = 'win_shell', _('Powershell') python = 'python', _('Python') + mysql = 'mysql', _('MySQL') + postgresql = 'postgresql', _('PostgreSQL') + sqlserver = 'sqlserver', _('SQLServer') class JobStatus(models.TextChoices): diff --git a/apps/ops/migrations/0026_auto_20230810_1039.py b/apps/ops/migrations/0026_auto_20230810_1039.py new file mode 100644 index 000000000..f9bd966a8 --- /dev/null +++ b/apps/ops/migrations/0026_auto_20230810_1039.py @@ -0,0 +1,39 @@ +# Generated by Django 4.1.10 on 2023-08-10 02:36 + +import common.db.encoder +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0025_auto_20230413_1531'), + ] + + operations = [ + migrations.AlterField( + model_name='adhoc', + name='module', + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python'), ('mysql', 'MySQL'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer')], default='shell', max_length=128, verbose_name='Module'), + ), + migrations.AlterField( + model_name='historicaljob', + name='module', + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python'), ('mysql', 'MySQL'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer')], default='shell', max_length=128, null=True, verbose_name='Module'), + ), + migrations.AlterField( + model_name='job', + name='module', + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python'), ('mysql', 'MySQL'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer')], default='shell', max_length=128, null=True, verbose_name='Module'), + ), + migrations.AlterField( + model_name='jobexecution', + name='result', + field=models.JSONField(blank=True, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True, verbose_name='Result'), + ), + migrations.AlterField( + model_name='jobexecution', + name='summary', + field=models.JSONField(default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, verbose_name='Summary'), + ), + ] diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index 487f677a0..05d1e297d 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -3,7 +3,7 @@ import abc from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from .celery.utils import ( diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 254453a90..18b1e5cba 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -2,9 +2,8 @@ import uuid from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from common.db.models import JMSBaseModel from common.utils import get_logger __all__ = ["AdHoc"] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 06a858bd6..f27e6b309 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -19,6 +19,7 @@ from simple_history.models import HistoricalRecords from accounts.models import Account from acls.models import CommandFilterACL from assets.models import Asset +from common.db.encoder import ModelJSONFieldEncoder from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner, CommandInBlackListException from ops.mixin import PeriodTaskModelMixin from ops.variables import * @@ -42,12 +43,43 @@ def get_parent_keys(key, include_self=True): class JMSPermedInventory(JMSInventory): - def __init__(self, assets, account_policy='privileged_first', - account_prefer='root,Administrator', host_callback=None, exclude_localhost=False, user=None): - super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost) + def __init__(self, + assets, + account_policy='privileged_first', + account_prefer='root,Administrator', + module=None, + host_callback=None, + user=None): + super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True) self.user = user + self.module = module self.assets_accounts_mapper = self.get_assets_accounts_mapper() + def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway): + if not account: + host['error'] = _("No account available") + return host + + protocol_supported_modules_mapping = { + 'mysql': ['mysql'], + 'postgresql': ['postgresql'], + 'sqlserver': ['sqlserver'], + 'ssh': ['shell', 'python', 'win_shell'], + } + + if self.module not in protocol_supported_modules_mapping.get(protocol.name, []): + host['error'] = "Module {} is not suitable for this asset".format(self.module) + return host + + if protocol.name in ('mysql', 'postgresql', 'sqlserver'): + host['login_host'] = asset.address + host['login_port'] = protocol.port + host['login_user'] = account.username + host['login_password'] = account.secret + host['login_db'] = asset.spec_info.get('db_name', '') + return host + return super().make_account_vars(host, asset, account, automation, protocol, platform, gateway) + def get_asset_sorted_accounts(self, asset): accounts = self.assets_accounts_mapper.get(asset.id, []) return list(accounts) @@ -158,7 +190,9 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): @property def inventory(self): - return JMSPermedInventory(self.assets.all(), self.runas_policy, self.runas, user=self.creator) + return JMSPermedInventory(self.assets.all(), + self.runas_policy, self.runas, + user=self.creator, module=self.module) @property def material(self): @@ -187,8 +221,8 @@ class JobExecution(JMSOrgBaseModel): job = models.ForeignKey(Job, on_delete=models.SET_NULL, related_name='executions', null=True) job_version = models.IntegerField(default=0) parameters = models.JSONField(default=dict, verbose_name=_('Parameters')) - result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) - summary = models.JSONField(default=dict, verbose_name=_('Summary')) + result = models.JSONField(encoder=ModelJSONFieldEncoder, blank=True, null=True, verbose_name=_('Result')) + summary = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_('Summary')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) @@ -256,7 +290,28 @@ class JobExecution(JMSOrgBaseModel): return module = self.current_job.module - # replace win_shell + + db_modules = ('mysql', 'postgresql', 'sqlserver', 'oracle') + db_module_name_map = { + 'mysql': 'community.mysql.mysql_query', + 'postgresql': 'community.postgresql.postgresql_query', + 'sqlserver': 'community.general.mssql_script:', + } + + if module in db_modules: + module = db_module_name_map.get(module, None) + if not module: + print('not support db module: {}'.format(module)) + raise Exception('not support db module: {}'.format(module)) + + login_args = "login_host={{login_host}} " \ + "login_user={{login_user}} " \ + "login_password={{login_password}} " \ + "login_port={{login_port}} " \ + "login_db={{login_db}}" + shell = "{} query=\"{}\" ".format(login_args, self.current_job.args) + return module, shell + if module == 'win_shell': module = 'ansible.windows.win_shell' @@ -284,12 +339,10 @@ class JobExecution(JMSOrgBaseModel): extra_vars = json.loads(self.parameters) else: extra_vars = {} - static_variables = self.gather_static_variables() extra_vars.update(static_variables) if self.current_job.type == 'adhoc': - module, args = self.compile_shell() runner = AdHocRunner( diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 82491480b..2d3c1f409 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -11,10 +11,15 @@ from ops.exception import PlaybookNoValidEntry from orgs.mixins.models import JMSOrgBaseModel dangerous_keywords = ( + 'hosts:localhost', + 'hosts:127.0.0.1', + 'hosts:::1', 'delegate_to:localhost', 'delegate_to:127.0.0.1', + 'delegate_to:::1', 'local_action', 'connection:local', + 'ansible_connection' ) @@ -48,7 +53,14 @@ class Playbook(JMSOrgBaseModel): with open(file, 'r') as f: for line_num, line in enumerate(f): for keyword in dangerous_keywords: - if keyword in line.replace(' ', ''): + clear_line = line.replace(' ', '')\ + .replace('\n', '')\ + .replace('\r', '')\ + .replace('\t', '') \ + .replace('\'', '') \ + .replace('\"', '')\ + .replace('\v', '') + if keyword in clear_line: result.append((line_num, keyword)) return result diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 1fe0fbe7c..045c8f1b9 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -2,7 +2,7 @@ from celery import shared_task from celery.exceptions import SoftTimeLimitExceeded -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django_celery_beat.models import PeriodicTask from common.utils import get_logger, get_object_or_none diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html index 3dfc23886..a2a025391 100644 --- a/apps/ops/templates/ops/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -135,7 +135,7 @@ method: "GET", flash_message: false, success(data) { - const dateStart = new Date(data.date_start).toLocaleString(); + const dateStart = data.date_start ? new Date(data.date_start).toLocaleString() : ''; $('.task-id').html(data.id); $('.task-type').html(data.task_name); $('.date-start').html(dateStart); diff --git a/apps/orgs/api.py b/apps/orgs/api.py index b0d8386e8..b10c3454e 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -2,7 +2,7 @@ # from django.conf import settings -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework.exceptions import PermissionDenied from rest_framework.generics import RetrieveAPIView diff --git a/apps/orgs/apps.py b/apps/orgs/apps.py index 70505d0f0..150f346ec 100644 --- a/apps/orgs/apps.py +++ b/apps/orgs/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class OrgsConfig(AppConfig): @@ -7,5 +7,6 @@ class OrgsConfig(AppConfig): verbose_name = _('App organizations') def ready(self): - from . import tasks - from . import signal_handlers + from . import signal_handlers # noqa + from . import tasks # noqa + super().ready() diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index d9eef1e2f..f41d4a67b 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel from common.utils import get_logger, lazyproperty @@ -119,3 +119,4 @@ class OrgModelMixin(models.Model): class JMSOrgBaseModel(JMSBaseModel, OrgModelMixin): class Meta: abstract = True + diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index e2e5a48a8..558dac282 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 647c37cb9..e667ac4d7 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -1,5 +1,6 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from rest_framework.serializers import ValidationError from common.db.models import JMSBaseModel from common.tree import TreeNode @@ -15,6 +16,7 @@ class OrgRoleMixin: DEFAULT_NAME = _('DEFAULT') SYSTEM_ID = '00000000-0000-0000-0000-000000000004' SYSTEM_NAME = _('SYSTEM') + INTERNAL_IDS = [ROOT_ID, DEFAULT_ID, SYSTEM_ID] members: models.Manager id: str @@ -171,6 +173,10 @@ class Organization(OrgRoleMixin, JMSBaseModel): def is_default(self): return str(self.id) == self.DEFAULT_ID + @property + def internal(self): + return str(self.id) in self.INTERNAL_IDS + def change_to(self): from .utils import set_current_org set_current_org(self) @@ -223,5 +229,7 @@ class Organization(OrgRoleMixin, JMSBaseModel): TicketFlow.objects.filter(org_id=self.id).delete() def delete(self, *args, **kwargs): + if str(self.id) in self.INTERNAL_IDS: + raise ValidationError(_('Can not delete virtual org')) self.delete_related_models() return super().delete(*args, **kwargs) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index c080332dd..efa1fa311 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,8 +1,8 @@ -from rest_framework.serializers import ModelSerializer from rest_framework import serializers +from rest_framework.serializers import ModelSerializer -from .utils import get_current_org from .models import Organization +from .utils import get_current_org class ResourceStatisticsSerializer(serializers.Serializer): @@ -25,7 +25,7 @@ class OrgSerializer(ModelSerializer): fields_mini = ['id', 'name'] fields_small = fields_mini + [ 'resource_statistics', - 'is_default', 'is_root', + 'is_default', 'is_root', 'internal', 'date_created', 'created_by', 'comment', 'created_by', ] diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index 998ee216c..83edeb425 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -103,8 +103,12 @@ def on_user_created_set_default_org(sender, instance, created, **kwargs): return if instance.orgs.count() > 0: return - with tmp_to_org(Organization.default()): - Organization.default().add_member(instance) + default_org = Organization.default() + with tmp_to_org(default_org): + default_org.add_member(instance) + default_group = UserGroup.objects.filter(name='Default').first() + if default_group: + default_group.users.add(instance) def _remove_user_resource(model, users, org, user_field_name='users'): diff --git a/apps/orgs/tasks.py b/apps/orgs/tasks.py index 04992f52a..7e02a64a2 100644 --- a/apps/orgs/tasks.py +++ b/apps/orgs/tasks.py @@ -1,5 +1,5 @@ from celery import shared_task -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 6813e9f0b..ceed23907 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- # import uuid -from inspect import signature -from functools import wraps -from werkzeug.local import LocalProxy from contextlib import contextmanager +from functools import wraps +from inspect import signature + +from werkzeug.local import LocalProxy from common.local import thread_local from .models import Organization @@ -133,6 +134,7 @@ def org_aware_func(org_arg_name): :param org_arg_name: 函数中包含org_id的对象是哪个参数 :return: """ + def decorate(func): @wraps(func) def wrapper(*args, **kwargs): @@ -149,7 +151,9 @@ def org_aware_func(org_arg_name): with tmp_to_org(org_aware_resource.org_id): # print("Current org id: {}".format(org_aware_resource.org_id)) return func(*args, **kwargs) + return wrapper + return decorate @@ -162,4 +166,5 @@ def ensure_in_real_or_default_org(func): if not current_org or current_org.is_root(): raise ValueError('You must in a real or default org!') return func(*args, **kwargs) + return wrapper diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 850d1385b..dcc208e99 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -47,11 +47,9 @@ class UserGroupGrantedAssetsApi(ListAPIView): granted_q |= Q(granted_by_permissions__id__in=asset_perm_ids) - assets = Asset.objects.filter( - granted_q - ).distinct().only( - *self.only_fields - ) + assets = Asset.objects.filter(granted_q) \ + .only(*self.only_fields) \ + .distinct() return assets diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 4f4d48181..24ed7fbc1 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.request import Request -from users.models import User -from rbac.permissions import RBACPermission -from common.utils import is_uuid from common.exceptions import JMSObjectDoesNotExist +from common.utils import is_uuid +from rbac.permissions import RBACPermission +from users.models import User __all__ = ['SelfOrPKUserMixin'] @@ -57,4 +57,3 @@ class SelfOrPKUserMixin: def request_user_is_self(self): return self.kwargs.get('user') in ['my', 'self'] - diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 5469c6ed3..1b4ee5d73 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -177,8 +177,10 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi( return [] pid = f'ROOT_{str(assets[0].category).upper()}_{tp}' return self.serialize_assets(assets, pid=pid) + params = self.request.query_params + get_root = not list(filter(lambda x: params.get(x), ('type', 'n'))) resource_platforms = assets.order_by('id').values_list('platform_id', flat=True) - node_all = AllTypes.get_tree_nodes(resource_platforms) + node_all = AllTypes.get_tree_nodes(resource_platforms, get_root=get_root) pattern = re.compile(r'\(0\)?') nodes = [] for node in node_all: @@ -188,6 +190,8 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi( _type = meta.get('_type') if _type: node['type'] = _type + meta.setdefault('data', {}) + node['meta'] = meta nodes.append(node) if not self.is_sync: @@ -224,9 +228,8 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): util = PermAccountUtil() accounts = util.get_permed_accounts_for_user(self.user, token.asset) account_name = token.account - if account_name in [ - AliasAccount.INPUT, AliasAccount.USER - ]: + + if account_name in [AliasAccount.INPUT, AliasAccount.USER]: return token.input_secret else: accounts = filter(lambda x: x.name == account_name, accounts) diff --git a/apps/perms/apps.py b/apps/perms/apps.py index 7bb606080..ee9e9c0e3 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class PermsConfig(AppConfig): @@ -9,7 +9,7 @@ class PermsConfig(AppConfig): verbose_name = _('App permissions') def ready(self): + from . import signal_handlers # noqa + from . import tasks # noqa + from . import notifications # noqa super().ready() - from . import signal_handlers - from . import notifications - from . import tasks diff --git a/apps/perms/const.py b/apps/perms/const.py index 60e1ae493..373723a4b 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.fields import BitChoices from common.utils.integer import bit diff --git a/apps/perms/filters.py b/apps/perms/filters.py index 202720480..55a72cf68 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -1,12 +1,11 @@ -from django_filters import rest_framework as filters -from django.utils.translation import ugettext_lazy as _ from django.db.models import QuerySet, Q +from django_filters import rest_framework as filters -from common.drf.filters import BaseFilterSet -from common.utils import get_object_or_none, is_uuid -from users.models import User, UserGroup from assets.models import Node, Asset +from common.drf.filters import BaseFilterSet +from common.utils import get_object_or_none from perms.models import AssetPermission, AssetPermissionQuerySet +from users.models import User, UserGroup class PermissionBaseFilter(BaseFilterSet): @@ -64,7 +63,8 @@ class PermissionBaseFilter(BaseFilterSet): groups = list(user.groups.all().values_list('id', flat=True)) user_asset_perm_ids = AssetPermission.objects.filter(users=user).distinct().values_list('id', flat=True) - group_asset_perm_ids = AssetPermission.objects.filter(user_groups__in=groups).distinct().values_list('id', flat=True) + group_asset_perm_ids = AssetPermission.objects.filter(user_groups__in=groups).distinct().values_list('id', + flat=True) asset_perm_ids = {*user_asset_perm_ids, *group_asset_perm_ids} diff --git a/apps/perms/migrations/0015_auto_20200929_1728.py b/apps/perms/migrations/0015_auto_20200929_1728.py index c241a2f71..9b8388c3b 100644 --- a/apps/perms/migrations/0015_auto_20200929_1728.py +++ b/apps/perms/migrations/0015_auto_20200929_1728.py @@ -14,12 +14,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='assetpermission', name='user_groups', - field=models.ManyToManyField(blank=True, related_name='assetpermissions', to='users.UserGroup', verbose_name='User group'), + field=models.ManyToManyField(blank=True, related_name='%(class)ss', to='users.usergroup', verbose_name='User group'), ), migrations.AlterField( model_name='assetpermission', name='users', - field=models.ManyToManyField(blank=True, related_name='assetpermissions', to=settings.AUTH_USER_MODEL, verbose_name='User'), + field=models.ManyToManyField(blank=True, related_name='%(class)ss', to=settings.AUTH_USER_MODEL, verbose_name='User'), ), migrations.AlterField( model_name='databaseapppermission', diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 08484e88e..2d5fa6ea5 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -3,7 +3,7 @@ import logging from django.db import models from django.db.models import Q from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.const import AliasAccount from accounts.models import Account diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index f5dc52be3..b3c209e3d 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -1,6 +1,6 @@ from django.db import models from django.db.models import F, TextChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from accounts.models import Account from assets.models import Asset, Node, FamilyMixin diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 9e074cd03..2a8aa0c2d 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -1,5 +1,5 @@ -from django.utils.translation import ugettext as _ from django.template.loader import render_to_string +from django.utils.translation import gettext as _ from common.utils import reverse as js_reverse from notifications.notifications import UserMessage diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 4d037e4d3..976fc1f31 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import AccountTemplate, Account diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 7d054c0d7..ba69cdeaf 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -2,7 +2,7 @@ # from django.db.models import F -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from accounts.models import Account @@ -69,7 +69,7 @@ class AccountsPermedSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ - 'alias', 'name', 'username', 'has_username', + 'id', 'alias', 'name', 'username', 'has_username', 'has_secret', 'secret_type', 'actions' ] read_only_fields = fields diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index d97343372..303e3ad22 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,7 +1,7 @@ from collections import defaultdict from accounts.const import AliasAccount -from accounts.models import Account +from accounts.models import VirtualAccount from orgs.utils import tmp_to_org from .permission import AssetPermissionUtil @@ -61,17 +61,12 @@ class PermAccountUtil(AssetPermissionUtil): for alias, action_bit in alias_action_bit_mapper.items(): account = None _accounts = [] - if alias == AliasAccount.USER: - if user.username in username_accounts_mapper: - _accounts = username_accounts_mapper[user.username] - else: - account = Account.get_user_account() - elif alias == AliasAccount.INPUT: - account = Account.get_manual_account() - elif alias == AliasAccount.ANON: - account = Account.get_anonymous_account() + if alias == AliasAccount.USER and user.username in username_accounts_mapper: + _accounts = username_accounts_mapper[user.username] elif alias in username_accounts_mapper: _accounts = username_accounts_mapper[alias] + elif alias in ['@INPUT', '@ANON', '@USER']: + account = VirtualAccount.get_special_account(alias, user, asset, from_permed=True) elif alias.startswith('@'): continue diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index d59c42192..387d0bc9c 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -1,5 +1,5 @@ from django.db.models import Q, Count -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied diff --git a/apps/rbac/api/rolebinding.py b/apps/rbac/api/rolebinding.py index f7ad47351..bd33a7168 100644 --- a/apps/rbac/api/rolebinding.py +++ b/apps/rbac/api/rolebinding.py @@ -1,10 +1,10 @@ -from django.utils.translation import ugettext as _ from django.db.models import F, Value from django.db.models.functions import Concat +from django.utils.translation import gettext as _ +from common.exceptions import JMSException from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org -from common.exceptions import JMSException from .. import serializers from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding @@ -26,15 +26,8 @@ class RoleBindingViewSet(OrgBulkModelViewSet): ] def get_queryset(self): - queryset = self._get_queryset()\ - .prefetch_related('user', 'role', 'org') \ - .annotate( - user_display=Concat( - F('user__name'), Value('('), - F('user__username'), Value(')') - ), - role_display=F('role__name') - ) + queryset = self._get_queryset() \ + .prefetch_related('user', 'role', 'org') return queryset def _get_queryset(self): diff --git a/apps/rbac/apps.py b/apps/rbac/apps.py index f22c7cf0a..e233185d5 100644 --- a/apps/rbac/apps.py +++ b/apps/rbac/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class RBACConfig(AppConfig): @@ -7,5 +7,5 @@ class RBACConfig(AppConfig): verbose_name = _('RBAC') def ready(self): - from . import signal_handlers + from . import signal_handlers # noqa super().ready() diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 508883446..a16944d47 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_noop +from django.utils.translation import gettext_noop from .const import Scope, system_exclude_permissions, org_exclude_permissions @@ -111,25 +111,25 @@ class PredefineRole: class BuiltinRole: system_admin = PredefineRole( - '1', ugettext_noop('SystemAdmin'), Scope.system, [] + '1', gettext_noop('SystemAdmin'), Scope.system, [] ) system_auditor = PredefineRole( - '2', ugettext_noop('SystemAuditor'), Scope.system, system_auditor_perms + '2', gettext_noop('SystemAuditor'), Scope.system, system_auditor_perms ) system_component = PredefineRole( - '4', ugettext_noop('SystemComponent'), Scope.system, app_exclude_perms, 'exclude' + '4', gettext_noop('SystemComponent'), Scope.system, app_exclude_perms, 'exclude' ) system_user = PredefineRole( - '3', ugettext_noop('User'), Scope.system, system_user_perms + '3', gettext_noop('User'), Scope.system, system_user_perms ) org_admin = PredefineRole( - '5', ugettext_noop('OrgAdmin'), Scope.org, [] + '5', gettext_noop('OrgAdmin'), Scope.org, [] ) org_auditor = PredefineRole( - '6', ugettext_noop('OrgAuditor'), Scope.org, auditor_perms + '6', gettext_noop('OrgAuditor'), Scope.org, auditor_perms ) org_user = PredefineRole( - '7', ugettext_noop('OrgUser'), Scope.org, user_perms + '7', gettext_noop('OrgUser'), Scope.org, user_perms ) system_role_mapper = None org_role_mapper = None diff --git a/apps/rbac/const.py b/apps/rbac/const.py index cd7fd642a..20f469611 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class Scope(models.TextChoices): @@ -144,7 +144,9 @@ only_system_permissions = ( ('terminal', 'task', '*', '*'), ('terminal', 'endpoint', '*', '*'), ('terminal', 'endpointrule', '*', '*'), - ('authentication', '*', '*', '*'), + ('authentication', 'accesskey', '*', '*'), + ('authentication', 'superconnectiontoken', '*', '*'), + ('authentication', 'temptoken', '*', '*'), ('tickets', '*', '*', '*'), ('orgs', 'organization', 'view', 'rootorg'), ('terminal', 'applet', '*', '*'), diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py index 0cb29f73a..17653f151 100644 --- a/apps/rbac/models/permission.py +++ b/apps/rbac/models/permission.py @@ -1,7 +1,7 @@ -from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import Permission as DjangoPermission from django.contrib.auth.models import ContentType as DjangoContentType +from django.contrib.auth.models import Permission as DjangoPermission +from django.db.models import Q +from django.utils.translation import gettext_lazy as _ from .. import const @@ -21,6 +21,7 @@ class ContentType(DjangoContentType): class Permission(DjangoPermission): """ 权限类 """ + class Meta: proxy = True verbose_name = _('Permissions') diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py index 1eff7c15c..4f18f67f8 100644 --- a/apps/rbac/models/role.py +++ b/apps/rbac/models/role.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _, gettext +from django.utils.translation import gettext_lazy as _, gettext from common.db.models import JMSBaseModel from common.utils import lazyproperty diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index 72b36e5dd..e9d636354 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -116,6 +116,14 @@ class RoleBinding(JMSBaseModel): default_system_orgs = orgs.filter(id__in=default_system_org_ids) return default_system_orgs | orgs.exclude(id__in=default_system_org_ids).order_by('name') + @classmethod + def get_user_joined_orgs(cls, user): + from orgs.models import Organization + org_ids = cls.objects.filter(user=user, scope=Scope.org) \ + .values_list('org', flat=True) \ + .distinct() + return Organization.objects.filter(id__in=org_ids) + @classmethod def get_user_has_the_perm_orgs(cls, perm, user): from orgs.models import Organization diff --git a/apps/rbac/serializers/permission.py b/apps/rbac/serializers/permission.py index 809b86636..cc97b6c8a 100644 --- a/apps/rbac/serializers/permission.py +++ b/apps/rbac/serializers/permission.py @@ -1,10 +1,9 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers from django.contrib.auth.models import ContentType +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers from ..models import Permission - __all__ = ['PermissionSerializer', 'UserPermsSerializer'] diff --git a/apps/rbac/serializers/role.py b/apps/rbac/serializers/role.py index 78724c18a..84c597396 100644 --- a/apps/rbac/serializers/role.py +++ b/apps/rbac/serializers/role.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import LabeledChoiceField diff --git a/apps/rbac/serializers/rolebinding.py b/apps/rbac/serializers/rolebinding.py index cfbf248b8..c5e64946d 100644 --- a/apps/rbac/serializers/rolebinding.py +++ b/apps/rbac/serializers/rolebinding.py @@ -1,25 +1,29 @@ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ +from common.serializers.fields import ObjectRelatedField +from users.models import User from orgs.serializers import CurrentOrgDefault from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding __all__ = [ - 'RoleBindingSerializer', 'OrgRoleBindingSerializer', 'SystemRoleBindingSerializer' + 'RoleBindingSerializer', 'OrgRoleBindingSerializer', 'SystemRoleBindingSerializer' ] class RoleBindingSerializer(serializers.ModelSerializer): + user = ObjectRelatedField( + required=False, queryset=User.objects, + label=_('User'), attrs=('id', 'name', 'username') + ) + class Meta: model = RoleBinding fields = [ - 'id', 'user', 'user_display', 'role', 'role_display', - 'scope', 'org', 'org_name', + 'id', 'user', 'role', 'scope', 'org', 'org_name', ] read_only_fields = ['scope'] extra_kwargs = { - 'user_display': {'label': _('User display')}, - 'role_display': {'label': _('Role display')}, 'org_name': {'label': _("Org name")} } @@ -55,6 +59,3 @@ class OrgRoleBindingSerializer(RoleBindingSerializer): if not self.instance and role_bindings.exists(): raise serializers.ValidationError({'role': _('Has bound this role')}) return attrs - - - diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index a586208b6..5bbedbbf0 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -70,6 +70,9 @@ special_pid_mapper = { 'xpack.syncinstancedetail': 'cloud_import', 'xpack.syncinstancetask': 'cloud_import', 'xpack.syncinstancetaskexecution': 'cloud_import', + 'xpack.strategy': 'cloud_import', + 'xpack.strategyaction': 'cloud_import', + 'xpack.strategyrule': 'cloud_import', 'terminal.applet': 'remote_application', 'terminal.applethost': 'remote_application', 'accounts.accountbackupautomation': "backup_account_node", diff --git a/apps/settings/api/__init__.py b/apps/settings/api/__init__.py index 176a6c2c6..13f4a2b18 100644 --- a/apps/settings/api/__init__.py +++ b/apps/settings/api/__init__.py @@ -1,8 +1,9 @@ -from .settings import * -from .ldap import * -from .wecom import * from .dingtalk import * -from .feishu import * -from .public import * from .email import * +from .feishu import * +from .ldap import * +from .public import * +from .settings import * from .sms import * +from .vault import * +from .wecom import * diff --git a/apps/settings/api/email.py b/apps/settings/api/email.py index 45bec7514..1e8ef2286 100644 --- a/apps/settings/api/email.py +++ b/apps/settings/api/email.py @@ -5,7 +5,7 @@ from smtplib import SMTPSenderRefused from django.conf import settings from django.core.mail import send_mail, get_connection -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.views import Response, APIView from common.utils import get_logger diff --git a/apps/settings/api/ldap.py b/apps/settings/api/ldap.py index 8c98f821f..486da3562 100644 --- a/apps/settings/api/ldap.py +++ b/apps/settings/api/ldap.py @@ -4,7 +4,7 @@ import threading from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import generics from rest_framework.generics import CreateAPIView from rest_framework.views import Response, APIView diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index ab313cd4f..f2628a5d4 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -4,13 +4,14 @@ from rest_framework.permissions import AllowAny from common.permissions import IsValidUserOrConnectionToken from common.utils import get_logger, lazyproperty +from common.utils.timezone import local_now from jumpserver.utils import has_valid_xpack_license, get_xpack_license_info from .. import serializers from ..utils import get_interface_setting_or_default logger = get_logger(__name__) -__all__ = ['PublicSettingApi', 'OpenPublicSettingApi'] +__all__ = ['PublicSettingApi', 'OpenPublicSettingApi', 'ServerInfoApi'] class OpenPublicSettingApi(generics.RetrieveAPIView): @@ -55,3 +56,13 @@ class PublicSettingApi(OpenPublicSettingApi): # 提前把异常爆出来 values[name] = getattr(settings, name) return values + + +class ServerInfoApi(generics.RetrieveAPIView): + permission_classes = (IsValidUserOrConnectionToken,) + serializer_class = serializers.ServerInfoSerializer + + def get_object(self): + return { + "CURRENT_TIME": local_now(), + } diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 4bcef2889..bb55dc453 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -28,6 +28,11 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'basic': serializers.BasicSettingSerializer, 'terminal': serializers.TerminalSettingSerializer, 'security': serializers.SecuritySettingSerializer, + 'security_auth': serializers.SecurityAuthSerializer, + 'security_basic': serializers.SecurityBasicSerializer, + 'security_session': serializers.SecuritySessionSerializer, + 'security_password': serializers.SecurityPasswordRuleSerializer, + 'security_login_limit': serializers.SecurityLoginLimitSerializer, 'ldap': serializers.LDAPSettingSerializer, 'email': serializers.EmailSettingSerializer, 'email_content': serializers.EmailContentSettingSerializer, @@ -39,7 +44,6 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'keycloak': serializers.KeycloakSettingSerializer, 'radius': serializers.RadiusSettingSerializer, 'cas': serializers.CASSettingSerializer, - 'sso': serializers.SSOSettingSerializer, 'saml2': serializers.SAML2SettingSerializer, 'oauth2': serializers.OAuth2SettingSerializer, 'clean': serializers.CleaningSerializer, @@ -50,6 +54,10 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'huawei': serializers.HuaweiSMSSettingSerializer, 'cmpp2': serializers.CMPP2SMSSettingSerializer, 'custom': serializers.CustomSMSSettingSerializer, + 'vault': serializers.VaultSettingSerializer, + 'announcement': serializers.AnnouncementSettingSerializer, + 'ticket': serializers.TicketSettingSerializer, + 'ops': serializers.OpsSettingSerializer, } rbac_category_permissions = { @@ -75,6 +83,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'sms': 'settings.change_sms', 'alibaba': 'settings.change_sms', 'tencent': 'settings.change_sms', + 'vault': 'settings.change_vault', } def get_queryset(self): diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 01fca4436..ec767828d 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -1,18 +1,16 @@ import importlib - from collections import OrderedDict +from django.utils.translation import gettext_lazy as _ +from rest_framework import status +from rest_framework.exceptions import APIException from rest_framework.generics import ListAPIView, GenericAPIView from rest_framework.response import Response -from rest_framework.exceptions import APIException -from rest_framework import status -from django.utils.translation import gettext_lazy as _ -from common.sdk.sms import BACKENDS from common.exceptions import JMSException -from settings.serializers.sms import SMSBackendSerializer +from common.sdk.sms import BACKENDS from settings.models import Setting - +from settings.serializers import SMSBackendSerializer from .. import serializers diff --git a/apps/settings/api/vault.py b/apps/settings/api/vault.py new file mode 100644 index 000000000..a17eeca5c --- /dev/null +++ b/apps/settings/api/vault.py @@ -0,0 +1,61 @@ +from django.conf import settings +from django.utils.translation import gettext_lazy as _ +from rest_framework import status +from rest_framework.generics import GenericAPIView +from rest_framework.views import Response, APIView + +from accounts.backends import get_vault_client +from accounts.tasks.vault import sync_secret_to_vault +from settings.models import Setting +from .. import serializers + + +class VaultTestingAPI(GenericAPIView): + serializer_class = serializers.VaultSettingSerializer + rbac_perms = { + 'POST': 'settings.change_vault' + } + + def get_config(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + data = serializer.validated_data + for k, v in data.items(): + if v: + continue + # 页面没有传递值, 从 settings 中获取 + data[k] = getattr(settings, k, None) + return data + + def post(self, request): + config = self.get_config(request) + config['VAULT_ENABLED'] = settings.VAULT_ENABLED + try: + client = get_vault_client(raise_exception=True, **config) + ok, error = client.is_active() + except Exception as e: + ok, error = False, str(e) + + if ok: + _status, msg = status.HTTP_200_OK, _('Test success') + else: + _status, msg = status.HTTP_400_BAD_REQUEST, error + + return Response(status=_status, data={'msg': msg}) + + +class VaultSyncDataAPI(APIView): + perm_model = Setting + rbac_perms = { + 'POST': 'settings.change_vault' + } + + def post(self, request, *args, **kwargs): + task = self._run_task() + return Response({'task': task.id}, status=status.HTTP_201_CREATED) + + @staticmethod + def _run_task(): + task = sync_secret_to_vault.delay() + return task + diff --git a/apps/settings/apps.py b/apps/settings/apps.py index 2f96498cc..e18608331 100644 --- a/apps/settings/apps.py +++ b/apps/settings/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class SettingsConfig(AppConfig): @@ -7,5 +7,5 @@ class SettingsConfig(AppConfig): verbose_name = _('Settings') def ready(self): - from . import tasks - from . import signal_handlers + from . import signal_handlers # noqa + from . import tasks # noqa diff --git a/apps/settings/migrations/0008_alter_setting_options.py b/apps/settings/migrations/0008_alter_setting_options.py new file mode 100644 index 000000000..9a8691d69 --- /dev/null +++ b/apps/settings/migrations/0008_alter_setting_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.19 on 2023-06-30 10:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('settings', '0007_migrate_ldap_sync_org_ids'), + ] + + operations = [ + migrations.AlterModelOptions( + name='setting', + options={'permissions': [('change_email', 'Can change email setting'), ('change_auth', 'Can change auth setting'), ('change_vault', 'Can change vault setting'), ('change_systemmsgsubscription', 'Can change system msg sub setting'), ('change_sms', 'Can change sms setting'), ('change_security', 'Can change security setting'), ('change_clean', 'Can change clean setting'), ('change_interface', 'Can change interface setting'), ('change_license', 'Can change license setting'), ('change_terminal', 'Can change terminal setting'), ('change_other', 'Can change other setting')], 'verbose_name': 'System setting'}, + ), + ] diff --git a/apps/settings/models.py b/apps/settings/models.py index 170d032a4..6aebf79b1 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -1,13 +1,12 @@ -import os import json +from django.conf import settings +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage +from django.core.files.uploadedfile import InMemoryUploadedFile from django.db import models from django.db.utils import ProgrammingError, OperationalError -from django.utils.translation import ugettext_lazy as _ -from django.conf import settings -from django.core.files.storage import default_storage -from django.core.files.base import ContentFile -from django.core.files.uploadedfile import InMemoryUploadedFile +from django.utils.translation import gettext_lazy as _ from common.utils import signer, get_logger @@ -160,6 +159,7 @@ class Setting(models.Model): permissions = [ ('change_email', _('Can change email setting')), ('change_auth', _('Can change auth setting')), + ('change_vault', _('Can change vault setting')), ('change_systemmsgsubscription', _('Can change system msg sub setting')), ('change_sms', _('Can change sms setting')), ('change_security', _('Can change security setting')), diff --git a/apps/settings/serializers/__init__.py b/apps/settings/serializers/__init__.py index 0a55f645d..fe94eb1da 100644 --- a/apps/settings/serializers/__init__.py +++ b/apps/settings/serializers/__init__.py @@ -1,13 +1,14 @@ # coding: utf-8 # -from .basic import * from .auth import * -from .email import * -from .public import * -from .settings import * -from .security import * -from .terminal import * +from .basic import * from .cleaning import * +from .feature import * +from .msg import * +from .msg import * from .other import * - +from .public import * +from .security import * +from .settings import * +from .terminal import * diff --git a/apps/settings/serializers/auth/base.py b/apps/settings/serializers/auth/base.py index abc1ab8b0..16cf535b4 100644 --- a/apps/settings/serializers/auth/base.py +++ b/apps/settings/serializers/auth/base.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers __all__ = [ @@ -9,14 +9,16 @@ __all__ = [ class AuthSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('Authentication') + AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP Auth')) AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth')) AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth')) + AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth")) + AUTH_OAUTH2 = serializers.BooleanField(default=False, label=_("OAuth2 Auth")) AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth')) AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('DingTalk Auth')) AUTH_FEISHU = serializers.BooleanField(default=False, label=_('FeiShu Auth')) AUTH_WECOM = serializers.BooleanField(default=False, label=_('WeCom Auth')) AUTH_SSO = serializers.BooleanField(default=False, label=_("SSO Auth")) - AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth")) FORGOT_PASSWORD_URL = serializers.CharField( required=False, allow_blank=True, max_length=1024, label=_("Forgot password url") diff --git a/apps/settings/serializers/auth/cas.py b/apps/settings/serializers/auth/cas.py index 4fc964529..c85497b00 100644 --- a/apps/settings/serializers/auth/cas.py +++ b/apps/settings/serializers/auth/cas.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers __all__ = [ diff --git a/apps/settings/serializers/auth/dingtalk.py b/apps/settings/serializers/auth/dingtalk.py index 4ec7814a1..418693bb8 100644 --- a/apps/settings/serializers/auth/dingtalk.py +++ b/apps/settings/serializers/auth/dingtalk.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/feishu.py b/apps/settings/serializers/auth/feishu.py index a06d41b23..d3c1edcad 100644 --- a/apps/settings/serializers/auth/feishu.py +++ b/apps/settings/serializers/auth/feishu.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py index cc1976884..054fe6144 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/oauth2.py b/apps/settings/serializers/auth/oauth2.py index d7f1f4407..56ddd6a66 100644 --- a/apps/settings/serializers/auth/oauth2.py +++ b/apps/settings/serializers/auth/oauth2.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index 2daf02ff5..0738de49a 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/radius.py b/apps/settings/serializers/auth/radius.py index 54098c3d0..b4085c352 100644 --- a/apps/settings/serializers/auth/radius.py +++ b/apps/settings/serializers/auth/radius.py @@ -1,7 +1,7 @@ # coding: utf-8 # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/auth/saml2.py b/apps/settings/serializers/auth/saml2.py index 9e0001218..35a4ef5d5 100644 --- a/apps/settings/serializers/auth/saml2.py +++ b/apps/settings/serializers/auth/saml2.py @@ -1,5 +1,4 @@ - -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers __all__ = [ diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 0b8138137..40881e52a 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -1,10 +1,10 @@ -from django.utils.translation import ugettext_lazy as _ from django.db import models +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from common.sdk.sms import BACKENDS from common.serializers.fields import EncryptedField, PhoneField from common.validators import PhoneValidator -from common.sdk.sms import BACKENDS __all__ = [ 'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer', @@ -26,7 +26,7 @@ class SignTmplPairSerializer(serializers.Serializer): class BaseSMSSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('SMS') - + SMS_TEST_PHONE = PhoneField( validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_('Test phone') ) diff --git a/apps/settings/serializers/auth/sso.py b/apps/settings/serializers/auth/sso.py index 0f7a7cef7..fe43357d6 100644 --- a/apps/settings/serializers/auth/sso.py +++ b/apps/settings/serializers/auth/sso.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers __all__ = [ @@ -7,6 +7,9 @@ __all__ = [ class SSOSettingSerializer(serializers.Serializer): + """ + 不对外开放了,只能通过配置文件修改,比较这个稍微有点危险 + """ PREFIX_TITLE = _('SSO') AUTH_SSO = serializers.BooleanField( diff --git a/apps/settings/serializers/auth/wecom.py b/apps/settings/serializers/auth/wecom.py index 462b17db6..b296eb691 100644 --- a/apps/settings/serializers/auth/wecom.py +++ b/apps/settings/serializers/auth/wecom.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField diff --git a/apps/settings/serializers/basic.py b/apps/settings/serializers/basic.py index d7d7f7ed9..baa5edb68 100644 --- a/apps/settings/serializers/basic.py +++ b/apps/settings/serializers/basic.py @@ -1,55 +1,33 @@ -import uuid - -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -class AnnouncementSerializer(serializers.Serializer): - ID = serializers.CharField(required=False, allow_blank=True, allow_null=True) - SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject")) - CONTENT = serializers.CharField(label=_("Content")) - LINK = serializers.URLField( - required=False, allow_null=True, allow_blank=True, - label=_("More url"), default='', - ) - - def to_representation(self, instance): - defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False} - data = {**defaults, **instance} - return super().to_representation(data) - - def to_internal_value(self, data): - data['ID'] = str(uuid.uuid4()) - return super().to_internal_value(data) - - class BasicSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('Basic') SITE_URL = serializers.URLField( required=True, label=_("Site url"), - help_text=_('eg: http://dev.jumpserver.org:8080') + help_text=_('Email links or other system callbacks are used to access it, eg: http://dev.jumpserver.org:8080') ) USER_GUIDE_URL = serializers.URLField( required=False, allow_blank=True, allow_null=True, label=_("User guide url"), help_text=_('User first login update profile done redirect to it') ) - FORGOT_PASSWORD_URL = serializers.URLField( - required=False, allow_blank=True, allow_null=True, label=_("Forgot password url"), - help_text=_('The forgot password url on login page, If you use ' - 'ldap or cas external authentication, you can set it') - ) GLOBAL_ORG_DISPLAY_NAME = serializers.CharField( required=False, max_length=1024, allow_blank=True, allow_null=True, label=_("Global organization name"), help_text=_('The name of global organization to display') ) - ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) - ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) - TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets")) + HELP_DOCUMENT_URL = serializers.URLField( + required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"), + help_text=_('default: http://docs.jumpserver.org') + ) + HELP_SUPPORT_URL = serializers.URLField( + required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"), + help_text=_('default: http://www.jumpserver.org/support/') + ) @staticmethod def validate_SITE_URL(s): if not s: return 'http://127.0.0.1' return s.strip('/') - diff --git a/apps/settings/serializers/cleaning.py b/apps/settings/serializers/cleaning.py index dfc568c99..eb1841561 100644 --- a/apps/settings/serializers/cleaning.py +++ b/apps/settings/serializers/cleaning.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers __all__ = ['CleaningSerializer'] @@ -29,7 +29,8 @@ class CleaningSerializer(serializers.Serializer): ) TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField( min_value=1, max_value=99999, required=True, label=_('Session keep duration (day)'), - help_text=_('Session, record, command will be delete if more than duration, only in database, OSS will not be affected.') + help_text=_( + 'Session, record, command will be delete if more than duration, only in database, OSS will not be affected.') ) ACTIVITY_LOG_KEEP_DAYS = serializers.IntegerField( min_value=1, max_value=9999, diff --git a/apps/settings/serializers/feature.py b/apps/settings/serializers/feature.py new file mode 100644 index 000000000..f2efe5b03 --- /dev/null +++ b/apps/settings/serializers/feature.py @@ -0,0 +1,82 @@ +import uuid + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.serializers.fields import EncryptedField + +__all__ = [ + 'AnnouncementSettingSerializer', 'OpsSettingSerializer', + 'VaultSettingSerializer', 'TicketSettingSerializer' +] + + +class AnnouncementSerializer(serializers.Serializer): + ID = serializers.CharField(required=False, allow_blank=True, allow_null=True) + SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject")) + CONTENT = serializers.CharField(label=_("Content")) + LINK = serializers.URLField( + required=False, allow_null=True, allow_blank=True, + label=_("More url"), default='', + ) + + def to_representation(self, instance): + defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False} + data = {**defaults, **instance} + return super().to_representation(data) + + def to_internal_value(self, data): + data['ID'] = str(uuid.uuid4()) + return super().to_internal_value(data) + + +class AnnouncementSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Announcement') + + ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) + ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) + + +class VaultSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('HCP Vault') + + VAULT_ENABLED = serializers.BooleanField( + required=False, label=_('Enable Vault'), read_only=True + ) + VAULT_HCP_HOST = serializers.CharField( + max_length=256, allow_blank=True, required=False, label=_('Host') + ) + VAULT_HCP_TOKEN = EncryptedField( + max_length=256, allow_blank=True, required=False, label=_('Token'), default='' + ) + VAULT_HCP_MOUNT_POINT = serializers.CharField( + max_length=256, allow_blank=True, required=False, label=_('Mount Point') + ) + + +class TicketSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Ticket') + + TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets")) + TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField( + min_value=1, max_value=999999, required=False, + label=_("Ticket authorize default time") + ) + TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField( + choices=[('day', _("day")), ('hour', _("hour"))], + label=_("Ticket authorize default time unit"), required=False, + ) + + +class OpsSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Feature') + + SECURITY_COMMAND_EXECUTION = serializers.BooleanField( + required=False, label=_('Operation center'), + help_text=_('Allow user run batch command or not using ansible') + ) + SECURITY_COMMAND_BLACKLIST = serializers.ListField( + child=serializers.CharField(max_length=1024, ), + label=_('Operation center command blacklist'), + help_text=_("Commands that are not allowed execute.") + ) diff --git a/apps/settings/serializers/email.py b/apps/settings/serializers/msg.py similarity index 82% rename from apps/settings/serializers/email.py rename to apps/settings/serializers/msg.py index ab8b10415..07e8e7205 100644 --- a/apps/settings/serializers/email.py +++ b/apps/settings/serializers/msg.py @@ -1,12 +1,15 @@ # coding: utf-8 # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField -__all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingSerializer'] +__all__ = [ + 'MailTestSerializer', 'EmailSettingSerializer', + 'EmailContentSettingSerializer', 'SMSBackendSerializer', +] class MailTestSerializer(serializers.Serializer): @@ -44,6 +47,10 @@ class EmailSettingSerializer(serializers.Serializer): EMAIL_SUBJECT_PREFIX = serializers.CharField( max_length=1024, required=True, label=_('Subject prefix') ) + EMAIL_SUFFIX = serializers.CharField( + required=False, max_length=1024, label=_("Email suffix"), + help_text=_('This is used by default if no email is returned during SSO authentication') + ) class EmailContentSettingSerializer(serializers.Serializer): @@ -69,3 +76,8 @@ class EmailContentSettingSerializer(serializers.Serializer): max_length=512, allow_blank=True, required=False, label=_('Signature'), help_text=_('Tips: Email signature (eg:jumpserver)') ) + + +class SMSBackendSerializer(serializers.Serializer): + name = serializers.CharField(max_length=256, required=True, label=_('Name')) + label = serializers.CharField(max_length=256, required=True, label=_('Label')) diff --git a/apps/settings/serializers/other.py b/apps/settings/serializers/other.py index 42df32496..5b9ee654c 100644 --- a/apps/settings/serializers/other.py +++ b/apps/settings/serializers/other.py @@ -1,46 +1,17 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +__all__ = ['OtherSettingSerializer'] + class OtherSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('More...') - EMAIL_SUFFIX = serializers.CharField( - required=False, max_length=1024, label=_("Email suffix"), - help_text=_('This is used by default if no email is returned during SSO authentication') - ) - - OTP_ISSUER_NAME = serializers.CharField( - required=False, max_length=16, label=_('OTP issuer name'), - ) - OTP_VALID_WINDOW = serializers.IntegerField( - min_value=1, max_value=10, - label=_("OTP valid window") - ) - PERM_SINGLE_ASSET_TO_UNGROUP_NODE = serializers.BooleanField( required=False, label=_("Perm ungroup node"), help_text=_("Perm single to ungroup node") ) - TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField( - min_value=1, max_value=999999, required=False, - label=_("Ticket authorize default time") - ) - TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField( - choices=[('day', _("day")), ('hour', _("hour"))], - label=_("Ticket authorize default time unit"), required=False, - ) - HELP_DOCUMENT_URL = serializers.URLField( - required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"), - help_text=_('default: http://docs.jumpserver.org') - ) - - HELP_SUPPORT_URL = serializers.URLField( - required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"), - help_text=_('default: http://www.jumpserver.org/support/') - ) - # 准备废弃 # PERIOD_TASK_ENABLED = serializers.BooleanField( # required=False, label=_("Enable period task") diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 8429ccd48..02f900d5f 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -3,7 +3,9 @@ from rest_framework import serializers -__all__ = ['PublicSettingSerializer', 'PrivateSettingSerializer'] +__all__ = [ + 'PublicSettingSerializer', 'PrivateSettingSerializer', 'ServerInfoSerializer' +] class PublicSettingSerializer(serializers.Serializer): @@ -50,3 +52,9 @@ class PrivateSettingSerializer(PublicSettingSerializer): TICKETS_ENABLED = serializers.BooleanField() CONNECTION_TOKEN_REUSABLE = serializers.BooleanField() + CACHE_LOGIN_PASSWORD_ENABLED = serializers.BooleanField() + VAULT_ENABLED = serializers.BooleanField() + + +class ServerInfoSerializer(serializers.Serializer): + CURRENT_TIME = serializers.DateTimeField() diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index c9f02d591..a5c27f1a1 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -1,10 +1,33 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from acls.serializers.rules import ip_group_help_text, ip_group_child_validator +__all__ = [ + 'SecurityPasswordRuleSerializer', 'SecuritySessionSerializer', + 'SecurityAuthSerializer', 'SecuritySettingSerializer', + 'SecurityLoginLimitSerializer', 'SecurityBasicSerializer', +] + class SecurityPasswordRuleSerializer(serializers.Serializer): + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( + min_value=1, max_value=99999, required=True, + label=_('User password expiration (day)'), + help_text=_( + 'If the user does not update the password during the time, ' + 'the user password will expire failure;The password expiration reminder mail will be ' + 'automatic sent to the user by system within 5 days (daily) before the password expires' + ) + ) + OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( + min_value=0, max_value=99999, required=True, + label=_('Number of repeated historical passwords'), + help_text=_( + 'Tip: When the user resets the password, it cannot be ' + 'the previous n historical passwords of the user' + ) + ) SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField( min_value=6, max_value=30, required=True, label=_('Password minimum length') @@ -33,20 +56,7 @@ login_ip_limit_time_help_text = _( ) -class SecurityAuthSerializer(serializers.Serializer): - SECURITY_MFA_AUTH = serializers.ChoiceField( - choices=( - [0, _('Disable')], - [1, _('All users')], - [2, _('Only admin users')], - ), - required=False, label=_("Global MFA auth") - ) - SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField( - required=False, default=True, - label=_('Third-party login users perform MFA authentication'), - help_text=_('The third-party login modes include OIDC, CAS, and SAML2'), - ) +class SecurityLoginLimitSerializer(serializers.Serializer): SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField( min_value=3, max_value=99999, label=_('Limit the number of user login failures') @@ -56,6 +66,7 @@ class SecurityAuthSerializer(serializers.Serializer): label=_('Block user login interval (minute)'), help_text=login_ip_limit_time_help_text ) + SECURITY_LOGIN_IP_LIMIT_COUNT = serializers.IntegerField( min_value=3, max_value=99999, label=_('Limit the number of IP login failures') @@ -75,46 +86,66 @@ class SecurityAuthSerializer(serializers.Serializer): child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]), help_text=ip_group_help_text ) - SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( - min_value=1, max_value=99999, required=True, - label=_('User password expiration (day)'), - help_text=_( - 'If the user does not update the password during the time, ' - 'the user password will expire failure;The password expiration reminder mail will be ' - 'automatic sent to the user by system within 5 days (daily) before the password expires' - ) - ) - OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( - min_value=0, max_value=99999, required=True, - label=_('Number of repeated historical passwords'), - help_text=_( - 'Tip: When the user resets the password, it cannot be ' - 'the previous n historical passwords of the user' - ) - ) USER_LOGIN_SINGLE_MACHINE_ENABLED = serializers.BooleanField( required=False, default=False, label=_("Only single device login"), help_text=_("After the user logs in on the new device, other logged-in devices will automatically log out") ) ONLY_ALLOW_EXIST_USER_AUTH = serializers.BooleanField( required=False, default=False, label=_("Only exist user login"), - help_text=_("If enabled, non-existent users will not be allowed to log in; if disabled, users of other authentication methods except local authentication methods are allowed to log in and automatically create users (if the user does not exist)") + help_text=_( + "If enabled, non-existent users will not be allowed to log in; if disabled, " + "users of other authentication methods except local authentication methods are allowed " + "to log in and automatically create users (if the user does not exist)" + ) ) ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField( required=False, default=False, label=_("Only from source login"), - help_text=_("If it is enabled, the user will only authenticate to the source when logging in; if it is disabled, the user will authenticate all the enabled authentication methods in a certain order when logging in, and as long as one of the authentication methods is successful, they can log in directly") + help_text=_( + "If it is enabled, the user will only authenticate to the source when logging in; " + "if it is disabled, the user will authenticate all the enabled authentication methods " + "in a certain order when logging in, and as long as one of the authentication methods is successful, " + "they can log in directly" + ) + ) + + +class SecurityAuthSerializer(serializers.Serializer): + SECURITY_MFA_AUTH = serializers.ChoiceField( + choices=( + [0, _('Not enabled')], + [1, _('All users')], + [2, _('Only admin users')], + ), + required=False, label=_("Global MFA auth") + ) + SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField( + required=False, default=True, + label=_('Third-party login users perform MFA authentication'), + help_text=_('The third-party login modes include OIDC, CAS, and SAML2'), + ) + OTP_ISSUER_NAME = serializers.CharField( + required=False, max_length=16, label=_('OTP issuer name'), + ) + OTP_VALID_WINDOW = serializers.IntegerField( + min_value=1, max_value=10, + label=_("OTP valid window") ) SECURITY_MFA_VERIFY_TTL = serializers.IntegerField( min_value=5, max_value=60 * 60 * 10, - label=_("MFA verify TTL (secend)"), + label=_("MFA verify TTL"), help_text=_( - "The verification MFA takes effect only when you view the account password" + "Unit: second, The verification MFA takes effect only when you view the account password" ) ) + SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField( + required=False, default=False, + label=_("MFA in login page"), + help_text=_("Eu security regulations(GDPR) require MFA to be on the login page") + ) VERIFY_CODE_TTL = serializers.IntegerField( min_value=5, max_value=60 * 60 * 10, - label=_("Verify code TTL"), - help_text=_("Unit: second, reset password and send SMS code expiration time") + label=_("Verify code TTL (second)"), + help_text=_("Reset password and send SMS code expiration time") ) SECURITY_LOGIN_CHALLENGE_ENABLED = serializers.BooleanField( required=False, default=False, @@ -122,15 +153,22 @@ class SecurityAuthSerializer(serializers.Serializer): help_text=_("The password and additional code are sent to a third party " "authentication system for verification") ) - SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField( - required=False, default=False, - label=_("MFA in login page"), - help_text=_("Eu security regulations(GDPR) require MFA to be on the login page") - ) SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField( required=False, default=False, label=_("Enable Login captcha"), help_text=_("Enable captcha to prevent robot authentication") ) + SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField( + required=False, label=_('Remote Login Protection'), + help_text=_( + 'The system determines whether the login IP address belongs to a common login city. ' + 'If the account is logged in from a common login city, the system sends a remote login reminder' + ) + ) + SECURITY_UNCOMMON_USERS_TTL = serializers.IntegerField( + min_value=30, max_value=99999, required=False, + label=_('Unused user timeout (day)'), + help_text=_("Detect infrequent users daily and disable them if they exceed the predetermined time limit.") + ) def validate(self, attrs): if attrs.get('SECURITY_MFA_AUTH') != 1: @@ -147,15 +185,7 @@ class SecurityAuthSerializer(serializers.Serializer): return data -class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer): - PREFIX_TITLE = _('Security') - - SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( - required=True, label=_('Enable terminal register'), - help_text=_( - "Allow terminal register, after all terminal setup, you should disable this for security" - ) - ) +class SecuritySessionSerializer(serializers.Serializer): SECURITY_WATERMARK_ENABLED = serializers.BooleanField( required=True, label=_('Enable watermark'), help_text=_('Enabled, the web session and replay contains watermark information') @@ -165,9 +195,21 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri label=_('Connection max idle time (minute)'), help_text=_('If idle time more than it, disconnect connection.') ) + SECURITY_MAX_SESSION_TIME = serializers.IntegerField( + min_value=1, max_value=99999, required=False, + label=_('Session max connection time (hour)'), + help_text=_('If session connection time more than it, disconnect connection.') + ) SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField( label=_("Remember manual auth") ) + SECURITY_SESSION_SHARE = serializers.BooleanField( + required=True, label=_('Session share'), + help_text=_("Enabled, Allows user active session to be shared with other users") + ) + + +class SecurityBasicSerializer(serializers.Serializer): SECURITY_INSECURE_COMMAND = serializers.BooleanField( required=False, label=_('Insecure command alert') ) @@ -175,23 +217,11 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri max_length=8192, required=False, allow_blank=True, label=_('Email recipient'), help_text=_('Multiple user using , split') ) - SECURITY_COMMAND_EXECUTION = serializers.BooleanField( - required=False, label=_('Operation center'), - help_text=_('Allow user run batch command or not using ansible') - ) - SECURITY_COMMAND_BLACKLIST = serializers.ListField( - child=serializers.CharField(max_length=1024, ), - label=_('Operation center command blacklist'), - help_text=_("Commands that are not allowed execute.") - ) - SECURITY_SESSION_SHARE = serializers.BooleanField( - required=True, label=_('Session share'), - help_text=_("Enabled, Allows user active session to be shared with other users") - ) - SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField( - required=False, label=_('Remote Login Protection'), - help_text=_( - 'The system determines whether the login IP address belongs to a common login city. ' - 'If the account is logged in from a common login city, the system sends a remote login reminder' - ) - ) + + +class SecuritySettingSerializer( + SecurityPasswordRuleSerializer, SecurityAuthSerializer, + SecuritySessionSerializer, SecurityBasicSerializer, + SecurityLoginLimitSerializer, +): + PREFIX_TITLE = _('Security') diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 2fb15b0af..2d38ed74a 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -1,24 +1,23 @@ # coding: utf-8 from django.core.cache import cache from django.utils import translation -from django.utils.translation import gettext_noop, ugettext_lazy as _ +from django.utils.translation import gettext_noop, gettext_lazy as _ from common.utils import i18n_fmt -from .basic import BasicSettingSerializer -from .other import OtherSettingSerializer -from .email import EmailSettingSerializer, EmailContentSettingSerializer from .auth import ( LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer, CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer, WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer, TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer, - SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer, + SAML2SettingSerializer, OAuth2SettingSerializer, CustomSMSSettingSerializer, ) -from .terminal import TerminalSettingSerializer -from .security import SecuritySettingSerializer +from .basic import BasicSettingSerializer from .cleaning import CleaningSerializer - +from .msg import EmailSettingSerializer, EmailContentSettingSerializer +from .other import OtherSettingSerializer +from .security import SecuritySettingSerializer +from .terminal import TerminalSettingSerializer __all__ = [ 'SettingsSerializer', @@ -43,7 +42,6 @@ class SettingsSerializer( KeycloakSettingSerializer, CASSettingSerializer, RadiusSettingSerializer, - SSOSettingSerializer, CleaningSerializer, AlibabaSMSSettingSerializer, TencentSMSSettingSerializer, diff --git a/apps/settings/serializers/sms.py b/apps/settings/serializers/sms.py deleted file mode 100644 index fa274a52a..000000000 --- a/apps/settings/serializers/sms.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - - -class SMSBackendSerializer(serializers.Serializer): - name = serializers.CharField(max_length=256, required=True, label=_('Name')) - label = serializers.CharField(max_length=256, required=True, label=_('Label')) diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index 87ce12fb7..17782a2e0 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -18,6 +18,12 @@ class TerminalSettingSerializer(serializers.Serializer): ('25', '25'), ('50', '50'), ) + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( + required=True, label=_('Enable terminal register'), + help_text=_( + "Allow terminal register, after all terminal setup, you should disable this for security" + ) + ) TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False, label=_('Password auth')) TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField( required=False, label=_('Public key auth'), @@ -30,11 +36,6 @@ class TerminalSettingSerializer(serializers.Serializer): TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField( PAGE_SIZE_CHOICES, required=False, label=_('List page size') ) - TERMINAL_TELNET_REGEX = serializers.CharField( - allow_blank=True, max_length=1024, required=False, label=_('Telnet login regex'), - help_text=_("Tips: The login success message varies with devices. " - "if you cannot log in to the device through Telnet, set this parameter") - ) TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) TERMINAL_RAZOR_ENABLED = serializers.BooleanField(label=_("Enable Razor")) TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField(label=_("Enable SSH Client")) diff --git a/apps/settings/signals.py b/apps/settings/signals.py index 859378a83..ccc9cd2d5 100644 --- a/apps/settings/signals.py +++ b/apps/settings/signals.py @@ -1,3 +1,3 @@ from django.dispatch import Signal -category_setting_updated = Signal(providing_args=('category', 'serializer')) +category_setting_updated = Signal() diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py index b931efb48..0530680e8 100644 --- a/apps/settings/tasks/ldap.py +++ b/apps/settings/tasks/ldap.py @@ -2,8 +2,7 @@ # from celery import shared_task from django.conf import settings -from django.db import transaction -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from ops.celery.decorator import after_app_ready_start diff --git a/apps/settings/tools/__init__.py b/apps/settings/tools/__init__.py new file mode 100644 index 000000000..09154d4b2 --- /dev/null +++ b/apps/settings/tools/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 +# +from .ping import * +from .telnet import * +from .nmap import * +from .tcpdump import * diff --git a/apps/settings/tools/nmap.py b/apps/settings/tools/nmap.py new file mode 100644 index 000000000..d58c46824 --- /dev/null +++ b/apps/settings/tools/nmap.py @@ -0,0 +1,57 @@ +import asyncio +import time +import nmap + +from common.utils.timezone import local_now_display +from settings.utils import generate_ips + + +def get_nmap_result(nm, ip, ports, timeout): + results = [] + nm.scan(ip, ports=ports, timeout=timeout) + tcp_port = nm[ip].get('tcp', {}) + udp_port = nm[ip].get('udp', {}) + results.append(f'PORT\tSTATE\tSERVICE') + for port, info in tcp_port.items(): + results.append(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}") + for port, info in udp_port.items(): + results.append(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}") + return results + + +async def once_nmap(nm, ip, ports, timeout, display): + await display(f'Starting Nmap at {local_now_display()} for {ip}') + try: + is_ok = True + loop = asyncio.get_running_loop() + results = await loop.run_in_executor(None, get_nmap_result, nm, ip, ports, timeout) + for result in results: + await display(result) + + except KeyError: + is_ok = False + await display(f'Host seems down.') + except Exception as err: + is_ok = False + await display(f"Error: %s" % err) + return is_ok + + +async def verbose_nmap(dest_ips, dest_ports=None, timeout=None, display=None): + if not display: + return + + ips = generate_ips(dest_ips) + dest_port = ','.join(list(dest_ports)) if dest_ports else None + + nm = nmap.PortScanner() + success_num, start_time = 0, time.time() + nmap_version = '.'.join(map(lambda x: str(x), nm.nmap_version())) + await display(f'[Summary] Nmap (v{nmap_version}): {len(ips)} addresses were scanned') + for ip in ips: + ok = await once_nmap(nm, str(ip), dest_port, timeout, display) + if ok: + success_num += 1 + await display() + await display(f'[Done] Nmap: {len(ips)} IP addresses ({success_num} hosts up) ' + f'scanned in {round(time.time() - start_time, 2)} seconds') diff --git a/apps/settings/utils/ping.py b/apps/settings/tools/ping.py similarity index 77% rename from apps/settings/utils/ping.py rename to apps/settings/tools/ping.py index 7b4f0a2a4..556bb9e5b 100644 --- a/apps/settings/utils/ping.py +++ b/apps/settings/tools/ping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # - +import asyncio import os import select import socket @@ -23,16 +23,16 @@ def checksum(source_string): for count in range(0, count_to, 2): this = source_string[count + 1] * 256 + source_string[count] sum = sum + this - sum = sum & 0xffffffff # Necessary? + sum &= 0xffffffff # Necessary? if count_to < len(source_string): - sum = sum + ord(source_string[len(source_string) - 1]) - sum = sum & 0xffffffff # Necessary? + sum += ord(source_string[len(source_string) - 1]) + sum &= 0xffffffff # Necessary? sum = (sum >> 16) + (sum & 0xffff) - sum = sum + (sum >> 16) + sum += sum >> 16 answer = ~sum - answer = answer & 0xffff + answer &= 0xffff # Swap bytes. Bugger me if I know why. answer = answer >> 8 | (answer << 8 & 0xff00) @@ -61,7 +61,7 @@ def receive_one_ping(my_socket, id, timeout): time_sent = struct.unpack("d", received_packet[28: 28 + bytes])[0] return time_received - time_sent - time_left = time_left - how_long_in_select + time_left -= how_long_in_select if time_left <= 0: return @@ -118,7 +118,7 @@ def ping(dest_addr, timeout, psize, flag=0): raise # raise the original error process_pre = os.getpid() & 0xFF00 - flag = flag & 0x00FF + flag &= 0x00FF my_id = process_pre | flag send_one_ping(my_socket, dest_addr, my_id, psize) @@ -128,31 +128,42 @@ def ping(dest_addr, timeout, psize, flag=0): return delay -def verbose_ping(dest_addr, timeout=2, count=5, psize=64, display=None): +async def verbose_ping(dest_ip, timeout=2, count=5, psize=64, display=None): """ Send `count' ping with `psize' size to `dest_addr' with the given `timeout' and display the result. """ - ip = lookup_domain(dest_addr) - if not ip: + if not display: return - if display is None: - display = print - display("PING %s (%s): 56 data bytes" % (dest_addr, ip)) + + ip, err = lookup_domain(dest_ip) + if not ip: + await display(err) + return + + await display("PING %s (%s): 56 data bytes" % (dest_ip, ip)) + await asyncio.sleep(0.1) + error_count = 0 for i in range(count): try: - delay = ping(dest_addr, timeout, psize) + delay = ping(dest_ip, timeout, psize) except socket.gaierror as e: - display("failed. (socket error: '%s')" % str(e)) + await display("Failed (socket error: '%s')" % str(e)) + error_count += 1 break if delay is None: - display("Request timeout for icmp_seq %i" % i) + await display("Request timeout for icmp_seq %i" % i) + error_count += 1 else: - delay = delay * 1000 - display("64 bytes from %s: icmp_seq=0 ttl=115 time=%.3f ms" % (ip, delay)) - time.sleep(1) - print() + delay *= 1000 + await display("64 bytes from %s: icmp_seq=0 ttl=115 time=%.3f ms" % (ip, delay)) + await asyncio.sleep(1) + + await display(f'--- {dest_ip} ping statistics ---') + await display(f'{count} packets transmitted, ' + f'{count - error_count} packets received, ' + f'{(error_count / count) * 100}% packet loss') if __name__ == "__main__": diff --git a/apps/settings/tools/tcpdump.py b/apps/settings/tools/tcpdump.py new file mode 100644 index 000000000..2b5412c15 --- /dev/null +++ b/apps/settings/tools/tcpdump.py @@ -0,0 +1,98 @@ +import asyncio +import netifaces +import socket +import struct + +from common.utils.timezone import local_now_display +from settings.utils import generate_ips, generate_ports + + +async def once_tcpdump( + interface, src_ips, src_ports, dest_ips, dest_ports, display, stop_event +): + loop = asyncio.get_event_loop() + s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0003)) + s.bind((interface, 0)) + s.setblocking(False) + while not stop_event.is_set(): + try: + packet = await loop.sock_recv(s, 65535) + except BlockingIOError: + await asyncio.sleep(0.1) + # 解析IP数据包 + ip_header = packet[14:34] + ip_hdr = struct.unpack('!BBHHHBBH4s4s', ip_header) + # 判断是否为TCP数据包 + protocol = ip_hdr[6] + if protocol != 6: + continue + # 解析TCP数据包 + tcp_header = packet[34:54] + tcp_hdr = struct.unpack('!HHLLBBHHH', tcp_header) + # 获取源地址、源端口号、目标地址、目标端口等信息 + src_ip, dest_ip = map(lambda x: socket.inet_ntoa(x), ip_hdr[8:10]) + src_port, dest_port = tcp_hdr[0], tcp_hdr[1] + # 获取数据包类型和长度 + packet_type = socket.htons(ip_hdr[6]) + packet_len = len(packet) + # 获取TCP标志位、序号、确认号、部分数据等信息 + seq, ack, flags = tcp_hdr[2], tcp_hdr[3], tcp_hdr[5] + data = packet[54:] + # 如果过滤的参数[源地址、源端口等]为空,则不过滤 + # 各个过滤参数之间为 `且` 的关系 + green_light = True + if src_ips and src_ip not in src_ips: + green_light = False + if src_ports and src_port not in src_ports: + green_light = False + if dest_ips and dest_ip not in dest_ips: + green_light = False + if dest_ports and dest_port not in dest_ports: + green_light = False + if not green_light: + continue + + results = [ + f'[{interface}][{local_now_display()}] {src_ip}:{src_port} -> ' + f'{dest_ip}:{dest_port} ({packet_type}, {packet_len} bytes)', + f'\tFlags: {flags} Seq: {seq}, Ack: {ack}', f'\tData: {data}' + ] + for r in results: + await display(r) + + +def list_show(items, default='all'): + return ','.join(map(str, items)) or default + + +async def verbose_tcpdump(interfaces, src_ips, src_ports, dest_ips, dest_ports, display=None): + if not display: + return + + stop_event = asyncio.Event() + valid_interface = netifaces.interfaces() + if interfaces: + valid_interface = set(netifaces.interfaces()) & set(interfaces) + + src_ips = generate_ips(src_ips) + src_ports = generate_ports(src_ports) + dest_ips = generate_ips(dest_ips) + dest_ports = generate_ports(dest_ports) + + summary = [ + f"[Summary] Tcpdump filter info: ", + f"Interface: [{list_show(valid_interface)}]", + f"Source address: [{list_show(src_ips)}]", + f"source port: [{list_show(src_ports)}]", + f"Destination address: [{list_show(dest_ips)}]", + f"Destination port: [{list_show(dest_ports)}]", + ] + for s in summary: + await display(s) + + params = [src_ips, src_ports, dest_ips, dest_ports, display, stop_event] + tasks = [ + asyncio.create_task(once_tcpdump(i, *params)) for i in valid_interface + ] + await asyncio.gather(*tasks) + stop_event.set() diff --git a/apps/settings/tools/telnet.py b/apps/settings/tools/telnet.py new file mode 100644 index 000000000..b54a393d8 --- /dev/null +++ b/apps/settings/tools/telnet.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +import asyncio + +from common.utils import lookup_domain + +PROMPT_REGEX = r'[\<|\[](.*)[\>|\]]' + + +async def telnet(dest_addr, port_number=23, timeout=10): + try: + reader, writer = await asyncio.wait_for( + asyncio.open_connection(dest_addr, port_number), timeout + ) + except asyncio.TimeoutError: + return False, 'Timeout' + except (ConnectionRefusedError, OSError) as e: + return False, str(e) + try: + # 发送命令 + writer.write(b"command\r\n") + await writer.drain() + # 读取响应 + response = await reader.readuntil() + except asyncio.TimeoutError: + writer.close() + await writer.wait_closed() + return False, 'Timeout' + writer.close() + await writer.wait_closed() + return True, response.decode('utf-8', 'ignore') + + +async def verbose_telnet(dest_ip, dest_port=23, timeout=10, display=None): + if not display: + return + + ip, err = lookup_domain(dest_ip) + if not ip: + await display(err) + return + + await display(f'Trying {dest_ip} ({ip}:{dest_port})') + try: + is_connective, resp = await telnet(dest_ip, dest_port, timeout) + if is_connective: + msg = f'Connected to {dest_ip} {dest_port} {resp}.\r\n' \ + f'Connection closed by foreign host.' + else: + msg = f'Unable to connect to remote host\r\n' \ + f'Reason: {resp}' + except Exception as e: + msg = 'Error: %s' % e + await display(msg) + + +if __name__ == "__main__": + print(verbose_telnet(dest_addr='1.1.1.1', port_number=2222)) + print(verbose_telnet(dest_addr='baidu.com', port_number=80)) + print(verbose_telnet(dest_addr='baidu.com', port_number=8080)) + print(verbose_telnet(dest_addr='192.168.4.1', port_number=2222)) + print(verbose_telnet(dest_addr='192.168.4.1', port_number=2223)) + print(verbose_telnet(dest_addr='ssssss', port_number=-1)) diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 5cfc3bb36..ba074d4ee 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -18,9 +18,12 @@ urlpatterns = [ path('feishu/testing/', api.FeiShuTestingAPI.as_view(), name='feishu-testing'), path('sms//testing/', api.SMSTestingAPI.as_view(), name='sms-testing'), path('sms/backend/', api.SMSBackendAPI.as_view(), name='sms-backend'), + path('vault/testing/', api.VaultTestingAPI.as_view(), name='vault-testing'), + path('vault/sync/', api.VaultSyncDataAPI.as_view(), name='vault-sync'), path('setting/', api.SettingsApi.as_view(), name='settings-setting'), path('logo/', api.SettingsLogoApi.as_view(), name='settings-logo'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), path('public/open/', api.OpenPublicSettingApi.as_view(), name='open-public-setting'), + path('server-info/', api.ServerInfoApi.as_view(), name='server-info'), ] diff --git a/apps/settings/utils/__init__.py b/apps/settings/utils/__init__.py index 0927bde18..91d05860e 100644 --- a/apps/settings/utils/__init__.py +++ b/apps/settings/utils/__init__.py @@ -1,7 +1,5 @@ # coding: utf-8 -# +# from .ldap import * from .common import * -from .ping import * -from .telnet import * diff --git a/apps/settings/utils/common.py b/apps/settings/utils/common.py index 3d73e8cdd..e549f9b8c 100644 --- a/apps/settings/utils/common.py +++ b/apps/settings/utils/common.py @@ -1,6 +1,7 @@ # coding: utf-8 from jumpserver.context_processor import default_interface from django.conf import settings +from IPy import IP def get_interface_setting_or_default(): @@ -12,3 +13,56 @@ def get_interface_setting_or_default(): def get_login_title(): return get_interface_setting_or_default()['login_title'] + + +def generate_ips(ip_string): + # 支持的格式 + # 192.168.1.1,192.168.1.2 + # 192.168.1.1-12 | 192.168.1.1-192.168.1.12 | 192.168.1.0/30 | 192.168.1.1 + ips = [] + ip_list = ip_string.split(',') + if len(ip_list) > 1: + for ip in ip_list: + try: + ips.append(str(IP(ip))) + except ValueError: + pass + return ips + + ip_list = ip_string.split('-') + try: + if len(ip_list) == 2: + start_ip, end_ip = ip_list + if ip_list[1].find('.') == -1: + end_ip = start_ip[:start_ip.rindex('.') + 1] + end_ip + for ip in range(IP(start_ip).int(), IP(end_ip).int() + 1): + ips.extend((str(ip) for ip in IP(ip))) + else: + ips.extend((str(ip) for ip in IP(ip_list[0]))) + except ValueError: + ips = [] + return ips + + +def is_valid_port(port): + valid = True + try: + port = int(port) + if port > 65535 or port < 1: + valid = False + except (TypeError, ValueError): + valid = False + return valid + +def generate_ports(ports): + port_list = [] + if isinstance(ports, int): + port_list.append(ports) + elif isinstance(ports, str): + port_list.extend( + [int(p) for p in ports.split(',') if p.isdigit()] + ) + elif isinstance(ports, list): + port_list = ports + port_list = list(map(int, filter(is_valid_port, port_list))) + return port_list diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index e8ff0ab79..d130ac41a 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -7,7 +7,7 @@ from copy import deepcopy from django.conf import settings from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from ldap3 import Server, Connection, SIMPLE from ldap3.core.exceptions import ( LDAPSocketOpenError, diff --git a/apps/settings/utils/telnet.py b/apps/settings/utils/telnet.py deleted file mode 100644 index 22a1cf619..000000000 --- a/apps/settings/utils/telnet.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -import socket -import telnetlib - -from common.utils import lookup_domain - -PROMPT_REGEX = r'[\<|\[](.*)[\>|\]]' - - -def telnet(dest_addr, port_number=23, timeout=10): - try: - connection = telnetlib.Telnet(dest_addr, port_number, timeout) - except (ConnectionRefusedError, socket.timeout, socket.gaierror) as e: - return False, str(e) - expected_regexes = [bytes(PROMPT_REGEX, encoding='ascii')] - index, prompt_regex, output = connection.expect(expected_regexes, timeout=3) - return True, output.decode('utf-8', 'ignore') - - -def verbose_telnet(dest_addr, port_number=23, timeout=10, display=None): - if display is None: - display = print - ip = lookup_domain(dest_addr) - if not ip: - return - msg = 'Trying %s (%s:%s)' % (dest_addr, ip, port_number) - display(msg) - try: - is_connective, resp = telnet(dest_addr, port_number, timeout) - if is_connective: - template = 'Connected to {0} {1}.\r\n{2}Connection closed by foreign host.' - else: - template = 'telnet: connect to {0} {1} {2}\r\ntelnet: Unable to connect to remote host' - msg = template.format(dest_addr, port_number, resp) - except Exception as e: - msg = 'Error: %s' % e - display(msg) - - -if __name__ == "__main__": - print(verbose_telnet(dest_addr='1.1.1.1', port_number=2222)) - print(verbose_telnet(dest_addr='baidu.com', port_number=80)) - print(verbose_telnet(dest_addr='baidu.com', port_number=8080)) - print(verbose_telnet(dest_addr='192.168.4.1', port_number=2222)) - print(verbose_telnet(dest_addr='192.168.4.1', port_number=2223)) - print(verbose_telnet(dest_addr='ssssss', port_number=-1)) diff --git a/apps/settings/ws.py b/apps/settings/ws.py index 9e248536f..b15d61ea7 100644 --- a/apps/settings/ws.py +++ b/apps/settings/ws.py @@ -1,52 +1,73 @@ # -*- coding: utf-8 -*- # - import json -from channels.generic.websocket import JsonWebsocketConsumer +from channels.generic.websocket import AsyncJsonWebsocketConsumer from common.db.utils import close_old_connections from common.utils import get_logger -from .utils import verbose_ping, verbose_telnet +from .tools import verbose_ping, verbose_telnet, verbose_nmap, verbose_tcpdump + logger = get_logger(__name__) -class ToolsWebsocket(JsonWebsocketConsumer): +class ToolsWebsocket(AsyncJsonWebsocketConsumer): - def connect(self): + async def connect(self): user = self.scope["user"] if user.is_authenticated: - self.accept() + await self.accept() else: - self.close() + await self.close() - def send_msg(self, msg): - self.send_json({'msg': msg + '\r\n'}) + async def send_msg(self, msg=''): + await self.send_json({'msg': msg + '\r\n'}) - def imitate_ping(self, dest_addr, timeout=3, count=5, psize=64): - """ - Send `count' ping with `psize' size to `dest_addr' with - the given `timeout' and display the result. - """ - logger.info('receive request ping {}'.format(dest_addr)) - verbose_ping(dest_addr, timeout, count, psize, display=self.send_msg) + async def imitate_ping(self, dest_ip, timeout=3, count=5, psize=64): + params = { + 'dest_ip': dest_ip, 'timeout': timeout, + 'count': count, 'psize': psize + } + logger.info(f'Receive request ping: {params}') + await verbose_ping(display=self.send_msg, **params) - def imitate_telnet(self, dest_addr, port_num=23, timeout=10): - logger.info('receive request telnet {}'.format(dest_addr)) - verbose_telnet(dest_addr, port_num, timeout, display=self.send_msg) + async def imitate_telnet(self, dest_ip, dest_port=23, timeout=10): + params = { + 'dest_ip': dest_ip, 'dest_port': dest_port, 'timeout': timeout, + } + logger.info(f'Receive request telnet: {params}') + await verbose_telnet(display=self.send_msg, **params) - def receive(self, text_data=None, bytes_data=None, **kwargs): + async def imitate_nmap(self, dest_ips, dest_ports=None, timeout=None): + params = { + 'dest_ips': dest_ips, 'dest_ports': dest_ports, 'timeout': timeout, + } + logger.info(f'Receive request nmap: {params}') + await verbose_nmap(display=self.send_msg, **params) + + async def imitate_tcpdump( + self, interfaces=None, src_ips='', + src_ports='', dest_ips='', dest_ports='' + ): + params = { + 'interfaces': interfaces, 'src_ips': src_ips, 'src_ports': src_ports, + 'dest_ips': dest_ips, 'dest_ports': dest_ports + } + logger.info(f'Receive request tcpdump: {params}') + await verbose_tcpdump(display=self.send_msg, **params) + + async def receive(self, text_data=None, bytes_data=None, **kwargs): data = json.loads(text_data) - tool_type = data.get('tool_type', 'Ping') - dest_addr = data.get('dest_addr') - if tool_type == 'Ping': - self.imitate_ping(dest_addr) - else: - port_num = data.get('port_num') - self.imitate_telnet(dest_addr, port_num) - self.close() + tool_type = data.pop('tool_type', 'Ping') + try: + tool_func = getattr(self, f'imitate_{tool_type.lower()}') + await tool_func(**data) + except Exception as error: + await self.send_msg('Exception: %s' % error) + await self.send_msg() + await self.close() - def disconnect(self, code): - self.close() + async def disconnect(self, code): + await self.close() close_old_connections() diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index a477df5ba..da801b2f3 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -15,16 +15,17 @@ p {
-

JumpServer {% trans 'Client' %} v1.1.8

+

JumpServer {% trans 'Client' %} v2.0.0

{% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %}

diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index ae984ba2f..90b4f162c 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -60,6 +60,10 @@ class DownloadUploadMixin: name = manifest['name'] update = request.query_params.get('update') + is_enterprise = manifest.get('edition') == Applet.Edition.enterprise + if is_enterprise and not settings.XPACK_ENABLED: + raise ValidationError({'error': _('This is enterprise edition applet')}) + instance = Applet.objects.filter(name=name).first() if instance and not update: return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index cc40936e5..6b06ee44e 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -1,3 +1,5 @@ +from django.db import transaction +from rest_framework import status from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response @@ -7,8 +9,7 @@ from common.permissions import IsServiceAccount from orgs.utils import tmp_to_builtin_org from terminal.models import AppletHost, AppletHostDeployment from terminal.serializers import ( - AppletHostSerializer, AppletHostDeploymentSerializer, - AppletHostStartupSerializer, AppletHostDeployAppletSerializer + AppletHostSerializer, AppletHostDeploymentSerializer, AppletHostStartupSerializer ) from terminal.tasks import run_applet_host_deployment, run_applet_host_deployment_install_applet @@ -58,17 +59,26 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - instance = serializer.save() + with transaction.atomic(): + instance = serializer.save() task = run_applet_host_deployment.delay(instance.id) instance.save_task(task.id) return Response({'task': str(task.id)}, status=201) - @action(methods=['post'], detail=False, serializer_class=AppletHostDeployAppletSerializer) + @action(methods=['post'], detail=False) def applets(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - applet_id = serializer.validated_data.pop('applet_id', '') - instance = serializer.save() - task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id) - instance.save_task(task.id) - return Response({'task': str(task.id)}, status=201) + hosts = request.data.get('hosts', []) + applet_id = request.data.get('applet_id', '') + model = self.get_queryset().model + hosts_qs = AppletHost.objects.filter(id__in=hosts) + if not hosts_qs.exists(): + return Response(status=status.HTTP_404_NOT_FOUND) + + objs = [model(host=host) for host in hosts_qs] + applet_host_deployments = model.objects.bulk_create(objs) + applet_host_deployment_ids = [str(obj.id) for obj in applet_host_deployments] + + task = run_applet_host_deployment_install_applet.delay(applet_host_deployment_ids, applet_id) + task_id = str(task.id) + model.objects.filter(id__in=applet_host_deployment_ids).update(task=task_id) + return Response({'task': task_id}, status=201) diff --git a/apps/terminal/api/applet/relation.py b/apps/terminal/api/applet/relation.py index 8028ff1c8..6435b6c0c 100644 --- a/apps/terminal/api/applet/relation.py +++ b/apps/terminal/api/applet/relation.py @@ -46,9 +46,10 @@ class HostMixin: @property def host(self): if self.kwargs.get('host'): - return self.pk_host() + host = self.pk_host() else: - return self.self_host() + host = self.self_host() + return host class AppletHostAccountsViewSet(HostMixin, JMSModelViewSet): diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index c7518932f..e2eeaf548 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -1,5 +1,5 @@ from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import status from rest_framework.decorators import action from rest_framework.request import Request diff --git a/apps/terminal/api/component/storage.py b/apps/terminal/api/component/storage.py index c912e3131..04e0db9ad 100644 --- a/apps/terminal/api/component/storage.py +++ b/apps/terminal/api/component/storage.py @@ -1,15 +1,15 @@ # coding: utf-8 # -from rest_framework import viewsets, generics, status -from rest_framework.response import Response -from rest_framework.request import Request -from rest_framework.decorators import action -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django_filters import utils +from rest_framework import viewsets, generics, status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response -from terminal import const from common.const.http import GET +from terminal import const from terminal.filters import CommandStorageFilter, CommandFilter, CommandFilterForStorageTree from terminal.models import CommandStorage, ReplayStorage from terminal.serializers import CommandStorageSerializer, ReplayStorageSerializer @@ -78,26 +78,26 @@ class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): invalid = _('Invalid') nodes = [ - { - 'id': storage.id, - 'name': f'{storage.name}({storage.type})({command_count})', - 'title': f'{storage.name}({storage.type})', - 'pId': 'root', - 'isParent': False, - 'open': False, - 'valid': True, - } for storage, command_count in storages_with_count - ] + [ - { - 'id': storage.id, - 'name': f'{storage.name}({storage.type}) *{invalid}', - 'title': f'{storage.name}({storage.type})', - 'pId': 'root', - 'isParent': False, - 'open': False, - 'valid': False, - } for storage in invalid_storages - ] + { + 'id': storage.id, + 'name': f'{storage.name}({storage.type})({command_count})', + 'title': f'{storage.name}({storage.type})', + 'pId': 'root', + 'isParent': False, + 'open': False, + 'valid': True, + } for storage, command_count in storages_with_count + ] + [ + { + 'id': storage.id, + 'name': f'{storage.name}({storage.type}) *{invalid}', + 'title': f'{storage.name}({storage.type})', + 'pId': 'root', + 'isParent': False, + 'open': False, + 'valid': False, + } for storage in invalid_storages + ] nodes.append(root) return Response(data=nodes) diff --git a/apps/terminal/api/session/session.py b/apps/terminal/api/session/session.py index 47432b60b..730fc99eb 100644 --- a/apps/terminal/api/session/session.py +++ b/apps/terminal/api/session/session.py @@ -8,7 +8,7 @@ from django.db.models import F from django.http import FileResponse from django.shortcuts import get_object_or_404, reverse from django.utils.encoding import escape_uri_path -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django_filters import rest_framework as filters from rest_framework import generics from rest_framework import viewsets, views diff --git a/apps/terminal/api/session/sharing.py b/apps/terminal/api/session/sharing.py index 9a60734fa..1675ee294 100644 --- a/apps/terminal/api/session/sharing.py +++ b/apps/terminal/api/session/sharing.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.decorators import action from rest_framework.exceptions import MethodNotAllowed, ValidationError from rest_framework.response import Response diff --git a/apps/terminal/api/session/task.py b/apps/terminal/api/session/task.py index 8daeeb4a7..f9dc8a2f2 100644 --- a/apps/terminal/api/session/task.py +++ b/apps/terminal/api/session/task.py @@ -3,17 +3,20 @@ import logging from rest_framework import status +from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView, Response from common.api import JMSBulkModelViewSet +from common.const.http import POST from common.utils import get_object_or_none from orgs.utils import tmp_to_root_org from terminal import serializers +from terminal.const import TaskNameType from terminal.models import Session, Task from terminal.utils import is_session_approver -__all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI'] +__all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI', ] logger = logging.getLogger(__file__) @@ -21,9 +24,44 @@ class TaskViewSet(JMSBulkModelViewSet): queryset = Task.objects.all() serializer_class = serializers.TaskSerializer filterset_fields = ('is_finished',) + serializer_classes = { + 'create_toggle_task': serializers.LockTaskSessionSerializer, + 'handle_ticket_task': serializers.LockTaskSessionSerializer, + 'default': serializers.TaskSerializer + } + rbac_perms = { + "create_toggle_task": "terminal.terminate_session", + } + + @action(methods=[POST], detail=False, url_path='toggle-lock-session') + def create_toggle_task(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + session_id = serializer.validated_data['session_id'] + task_name = serializer.validated_data['task_name'] + session_ids = [session_id, ] + validated_session = create_sessions_tasks(session_ids, request.user, task_name=task_name) + return Response({"ok": validated_session}) + + @action(methods=[POST], detail=False, permission_classes=[IsAuthenticated, ], + url_path='toggle-lock-session-for-ticket', ) + def handle_ticket_task(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + session_id = serializer.validated_data['session_id'] + task_name = serializer.validated_data['task_name'] + session_ids = [session_id, ] + user_id = request.user.id + for session_id in session_ids: + if not is_session_approver(session_id, user_id): + return Response({}, status=status.HTTP_403_FORBIDDEN) + with tmp_to_root_org(): + validated_session = create_sessions_tasks(session_ids, request.user, task_name=task_name) + + return Response({"ok": validated_session}) -def kill_sessions(session_ids, user): +def create_sessions_tasks(session_ids, user, task_name=TaskNameType.kill_session): validated_session = [] for session_id in session_ids: @@ -31,9 +69,10 @@ def kill_sessions(session_ids, user): if session and not session.is_finished: validated_session.append(session_id) Task.objects.create( - name="kill_session", args=session.id, terminal=session.terminal, + name=task_name, args=session.id, terminal=session.terminal, kwargs={ - 'terminated_by': str(user) + 'terminated_by': str(user), + 'created_by': str(user) } ) return validated_session @@ -47,7 +86,7 @@ class KillSessionAPI(APIView): def post(self, request, *args, **kwargs): session_ids = request.data - validated_session = kill_sessions(session_ids, request.user) + validated_session = create_sessions_tasks(session_ids, request.user) return Response({"ok": validated_session}) @@ -63,6 +102,6 @@ class KillSessionForTicketAPI(APIView): return Response({}, status=status.HTTP_403_FORBIDDEN) with tmp_to_root_org(): - validated_session = kill_sessions(session_ids, request.user) + validated_session = create_sessions_tasks(session_ids, request.user) return Response({"ok": validated_session}) diff --git a/apps/terminal/applets/chrome/ChangeLog b/apps/terminal/applets/chrome/ChangeLog index 7a03e6949..d59b31125 100644 --- a/apps/terminal/applets/chrome/ChangeLog +++ b/apps/terminal/applets/chrome/ChangeLog @@ -1,3 +1,15 @@ +# 2023-08-16 Version 0.8 +## 功能优化 + - 修复代填失败,造成页面卡住的问题 + +# 2023-07-28 Version 0.7 +## 功能优化 + - 增加进度窗口,隐藏代填操作 + +# 2023-07-13 Version 0.6 +## 功能优化 + - 优化 Chrome 插件拦截逻辑 + # 2023-07-06 Version 0.5 ## 功能更新 - 增加匿名用户的支持,如果账号是匿名用户,username 和 secret 则为空 diff --git a/apps/terminal/applets/chrome/app.py b/apps/terminal/applets/chrome/app.py index 47cce8844..92ddd1e7c 100644 --- a/apps/terminal/applets/chrome/app.py +++ b/apps/terminal/applets/chrome/app.py @@ -9,7 +9,7 @@ from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement -from code_dialog import CodeDialog +from code_dialog import CodeDialog, wrapper_progress_bar from common import (Asset, User, Account, Platform, Step) from common import (BaseApplication) from common import (notify_err_message, block_input, unblock_input) @@ -121,7 +121,6 @@ def execute_action(driver: webdriver.Chrome, step: StepAction) -> bool: return step.execute(driver) except Exception as e: print(e) - notify_err_message(str(e)) return False @@ -221,10 +220,6 @@ def default_chrome_driver_options(): options.add_argument('--ignore-certificate-errors-spki-list') options.add_argument('--allow-running-insecure-content') - # 加载 extensions - extension_paths = load_extensions() - for extension_path in extension_paths: - options.add_argument('--load-extension={}'.format(extension_path)) # 禁用开发者工具 options.add_argument("--disable-dev-tools") # 禁用 密码管理器弹窗 @@ -248,7 +243,14 @@ class AppletApplication(BaseApplication): self._chrome_options = default_chrome_driver_options() self._chrome_options.add_argument("--app={}".format(self.asset.address)) self._chrome_options.add_argument("--user-data-dir={}".format(self._tmp_user_dir.name)) + protocol_setting = self.platform.get_protocol_setting(self.protocol) + if protocol_setting and protocol_setting.safe_mode: + # 加载 extensions + extension_paths = load_extensions() + for extension_path in extension_paths: + self._chrome_options.add_argument('--load-extension={}'.format(extension_path)) + @wrapper_progress_bar def run(self): service = Service() # driver 的 console 终端框不显示 @@ -256,11 +258,11 @@ class AppletApplication(BaseApplication): self.driver = webdriver.Chrome(options=self._chrome_options, service=service) self.driver.implicitly_wait(10) if self.app.asset.address != "": + self.driver.minimize_window() ok = self.app.execute(self.driver) if not ok: print("执行失败") self.driver.maximize_window() - def wait(self): disconnected_msg = "Unable to evaluate script: disconnected: not connected to DevTools\n" closed_msg = "Unable to evaluate script: no such window: target window already closed" diff --git a/apps/terminal/applets/chrome/code_dialog.py b/apps/terminal/applets/chrome/code_dialog.py index b3b93730c..862e80986 100644 --- a/apps/terminal/applets/chrome/code_dialog.py +++ b/apps/terminal/applets/chrome/code_dialog.py @@ -1,3 +1,5 @@ +import functools +import threading import tkinter as tk from tkinter import StringVar, messagebox from tkinter import ttk @@ -13,7 +15,7 @@ class CodeDialog(object): mainframe.grid(column=0, row=0, ) self.label = ttk.Label(mainframe, text=label, width=10) self.input = ttk.Entry(mainframe, textvariable=self.code, width=20) - self.button = ttk.Button(mainframe, text="ok", command=self.click_ok, width=5,) + self.button = ttk.Button(mainframe, text="ok", command=self.click_ok, width=5, ) self.label.grid(row=1, column=0) self.input.grid(row=1, column=1) self.button.grid(row=2, column=1, sticky=tk.E) @@ -32,6 +34,63 @@ class CodeDialog(object): self.root.destroy() +class TkProgressBar(object): + def __init__(self, wait_func=None): + self._wait_func = wait_func + self._done = threading.Event() + self._root = None + + def _check(self): + if self._done.isSet(): + self._root.destroy() + return + self._root.after(100, self._check) + + def stop(self): + self._done.set() + + def show(self): + if not self._wait_func: + return + root = tk.Tk() + width, height = root.winfo_screenwidth(), root.winfo_screenheight() + root.geometry('%dx%d' % (width, height)) + root.title('Progress') + root.grid() + pb_length = width - 20 + pb = ttk.Progressbar(root, orient='horizontal', length=pb_length, mode='indeterminate') + pb.pack(expand=True, padx=10, pady=10) + + self._root = root + self._root.after(60, pb.start) + self._root.after(100, self._check) + + def _wait_run_func(): + self._wait_func() + self.stop() + print('wait func done') + + self._root.attributes("-topmost", True) + self._root.attributes("-fullscreen", True) + threading.Thread(target=_wait_run_func).start() + self._root.mainloop() + + +# progress bar 装饰器,用于显示进度动画 +# 此方法会创建一个全屏的进度条窗口 +def wrapper_progress_bar(func): + def inner(*args, **kwargs): + wait_func = functools.partial(func, *args, **kwargs) + tk_process = TkProgressBar(wait_func=wait_func) + tk_process.show() + + return inner + + if __name__ == '__main__': - code = CodeDialog(title="Code Dialog", label="Code: ").wait_string() - print(code) + # code = CodeDialog(title="Code Dialog", label="Code: ").wait_string() + # print(code) + import time + progress = TkProgressBar(wait_func=lambda: time.sleep(15)) + progress.show() + print('end') diff --git a/apps/terminal/applets/chrome/common.py b/apps/terminal/applets/chrome/common.py index 06d23f9fd..8d2ab3df2 100644 --- a/apps/terminal/applets/chrome/common.py +++ b/apps/terminal/applets/chrome/common.py @@ -7,6 +7,7 @@ import subprocess import sys import time from subprocess import CREATE_NO_WINDOW +from threading import Thread _blockInput = None _messageBox = None @@ -36,7 +37,10 @@ def unblock_input(): def notify_err_message(msg): if _messageBox: - _messageBox(msg, 'Error') + # _messageBox 是阻塞当前线程的,所以需要开启一个新线程执行 + t = Thread(target=_messageBox, args=(msg, 'Error'), kwargs={}) + t.daemon = True + t.start() def decode_content(content: bytes) -> str: @@ -160,6 +164,7 @@ class ProtocolSetting(DictObj): password_selector: str submit_selector: str script: list[Step] + safe_mode: bool class PlatformProtocolSetting(DictObj): diff --git a/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/background.js b/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/background.js index 7f2556ab8..0ea3a1353 100644 --- a/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/background.js +++ b/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/background.js @@ -6,24 +6,25 @@ const debug = console.log // 监听标签页的创建事件 chrome.tabs.onCreated.addListener(function (tab) { // 获取当前窗口的所有标签页 - debug('New tab add, tabs : ', tabs) + debug('New tab add, tabs : ', tabs.map(t => t.id)) tabs.push(tab) }); chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { - debug('Tab changed xx: ', tabId, changeInfo, tab) + debug('Tab status changed: ', tabId, ' => ', changeInfo.status) if (changeInfo.status !== 'loading') { return } const tabFind = tabs.findIndex(t => t.id === tabId) if (tabFind === -1) { - debug('Tab not found: ', tabId, tabs) - return + tabs.push(tab) + } else { + Object.assign(tabs[tabFind], tab) } - Object.assign(tabs[tabFind], tab) const blockUrls = ['chrome://newtab/'] if (!tab.url || blockUrls.includes(tab.url) || tab.url.startsWith('chrome://')) { + alert('安全模式,禁止打开新标签页') debug('Blocked url, destroy: ', tab.url) chrome.tabs.remove(tabId); return @@ -52,3 +53,11 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { chrome.tabs.remove(tabId); } }) + +chrome.tabs.onRemoved.addListener(function (tabId, removeInfo) { + debug('Tab removed: ', tabId) + const tabFind = tabs.findIndex(t => t.id === tabId) + if (tabFind !== -1) { + tabs.splice(tabFind, 1) + } +}) diff --git a/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/manifest.json b/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/manifest.json index 58ad2b4a1..6cef03a67 100644 --- a/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/manifest.json +++ b/apps/terminal/applets/chrome/extensions/disable_new_tab_window_menu/manifest.json @@ -7,7 +7,7 @@ "scripts": [ "background.js" ], - "persistent": false + "persistent": true }, "content_scripts": [ { diff --git a/apps/terminal/applets/chrome/manifest.yml b/apps/terminal/applets/chrome/manifest.yml index 066530eef..89b35a7dc 100644 --- a/apps/terminal/applets/chrome/manifest.yml +++ b/apps/terminal/applets/chrome/manifest.yml @@ -1,6 +1,6 @@ name: chrome display_name: "{{ 'Chrome Browser' | trans }}" -version: 0.6 +version: 0.8 comment: "{{ 'Chrome Browser Open URL Page Address' | trans }}" author: JumpServer Team exec_type: python diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py index d383d01cd..b1b9538d3 100644 --- a/apps/terminal/apps.py +++ b/apps/terminal/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.utils.translation import gettext_lazy as _ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class TerminalConfig(AppConfig): @@ -9,7 +9,7 @@ class TerminalConfig(AppConfig): verbose_name = _('Terminals') def ready(self): - from . import signal_handlers - from . import notifications - from . import tasks + from . import signal_handlers # noqa + from . import notifications # noqa + from . import tasks # noqa return super().ready() diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index 164340098..449306a92 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -6,7 +6,7 @@ from django.conf import settings from django.utils import timezone from common.db.utils import safe_db_connection -from common.utils import get_logger +from common.utils import get_logger, random_string from ops.ansible import PlaybookRunner, JMSInventory from terminal.models import Applet, AppletHostDeployment @@ -58,13 +58,15 @@ class DeployAppletHostManager: download_host = download_host.rstrip("/") def handler(plays): + applet_host_name = self.deployment.host.name + hostname = '{}-{}'.format(applet_host_name, random_string(7)) for play in plays: play["vars"].update(options) play["vars"]["APPLET_DOWNLOAD_HOST"] = download_host play["vars"]["CORE_HOST"] = core_host play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token play["vars"]["HOST_ID"] = host_id - play["vars"]["HOST_NAME"] = self.deployment.host.name + play["vars"]["HOST_NAME"] = hostname return plays return self._generate_playbook("playbook.yml", handler) diff --git a/apps/terminal/backends/command/models.py b/apps/terminal/backends/command/models.py index 801b3cdf1..cb30efddd 100644 --- a/apps/terminal/backends/command/models.py +++ b/apps/terminal/backends/command/models.py @@ -4,7 +4,7 @@ import uuid from datetime import datetime from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils.common import lazyproperty from orgs.mixins.models import OrgModelMixin @@ -12,7 +12,6 @@ from terminal.const import RiskLevelChoices class AbstractSessionCommand(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=64, db_index=True, verbose_name=_("User")) asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset")) diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py index f6870344d..c793bb7a1 100644 --- a/apps/terminal/connect_methods.py +++ b/apps/terminal/connect_methods.py @@ -5,7 +5,7 @@ from collections import defaultdict from django.conf import settings from django.db.models import TextChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import Protocol from .const import TerminalType @@ -19,40 +19,36 @@ class WebMethod(TextChoices): @classmethod def get_spec_methods(cls): methods = { - Protocol.ssh: [cls.web_cli, cls.web_sftp], + Protocol.sftp: [cls.web_sftp] } return methods class NativeClient(TextChoices): # Koko - ssh = 'ssh', 'SSH' - putty = 'putty', 'PuTTY' - xshell = 'xshell', 'Xshell' - + ssh_client = 'ssh_client', _('SSH Client') + ssh_guide = 'ssh_guide', _('SSH Guide') + sftp_client = 'sftp_client', _('SFTP Client') # Magnus + db_guide = 'db_guide', _('DB Guide') db_client = 'db_client', _('DB Client') - # Razor - mstsc = 'mstsc', 'Remote Desktop' + mstsc = 'mstsc', _('Remote Desktop') @classmethod def get_native_clients(cls): # native client 关注的是 endpoint 的 protocol, # 比如 telnet mysql, koko 都支持,到那时暴露的是 ssh 协议 clients = { - Protocol.ssh: { - 'default': [cls.ssh], - 'windows': [cls.putty], - }, + Protocol.ssh: [cls.ssh_client, cls.ssh_guide], + Protocol.sftp: [cls.sftp_client], Protocol.rdp: [cls.mstsc], - Protocol.mysql: [cls.db_client], - Protocol.mariadb: [cls.db_client], - Protocol.redis: [cls.db_client], - Protocol.mongodb: [cls.db_client], - - Protocol.oracle: [cls.db_client], - Protocol.postgresql: [cls.db_client], + Protocol.mysql: [cls.db_client, cls.db_guide], + Protocol.mariadb: [cls.db_client, cls.db_guide], + Protocol.redis: [cls.db_client, cls.db_guide], + Protocol.mongodb: [cls.db_client, cls.db_guide], + Protocol.oracle: [cls.db_client, cls.db_guide], + Protocol.postgresql: [cls.db_client, cls.db_guide], } return clients @@ -81,13 +77,11 @@ class NativeClient(TextChoices): for protocol, _clients in clients_map.items(): if not settings.XPACK_ENABLED and protocol in xpack_protocols: continue - if isinstance(_clients, dict): if os == 'all': _clients = list(itertools.chain(*_clients.values())) else: _clients = _clients.get(os, _clients['default']) - for client in _clients: if not settings.XPACK_ENABLED and client in cls.xpack_methods(): continue @@ -98,26 +92,6 @@ class NativeClient(TextChoices): }) return methods - @classmethod - def get_launch_command(cls, name, token, endpoint, os='windows'): - username = f'JMS-{token.id}' - commands = { - cls.ssh: f'ssh {username}@{endpoint.host} -p {endpoint.ssh_port}', - cls.putty: f'putty.exe -ssh {username}@{endpoint.host} -P {endpoint.ssh_port}', - cls.xshell: f'xshell.exe -url ssh://{username}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', - # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', - # cls.psql: { - # 'default': 'psql -h {hostname} -p {port} -U {username} -W', - # 'windows': 'psql /h {hostname} /p {port} /U {username} -W', - # }, - # cls.sqlplus: 'sqlplus {username}/{password}@{hostname}:{port}', - # cls.redis: 'redis-cli -h {hostname} -p {port} -a {password}', - } - command = commands.get(name) - if isinstance(command, dict): - command = command.get(os, command.get('default')) - return command - class AppletMethod: @classmethod @@ -125,7 +99,6 @@ class AppletMethod: from .models import Applet, AppletHost methods = defaultdict(list) - has_applet_hosts = AppletHost.objects.all().exists() applets = Applet.objects.filter(is_active=True) for applet in applets: @@ -148,14 +121,18 @@ class ConnectMethodUtil: protocols = { TerminalType.koko: { 'web_methods': [WebMethod.web_cli], - 'listen': [Protocol.http, Protocol.ssh], + 'listen': [Protocol.http, Protocol.ssh, Protocol.sftp], 'support': [ - Protocol.ssh, Protocol.telnet, - Protocol.mysql, Protocol.postgresql, - Protocol.sqlserver, Protocol.mariadb, + Protocol.ssh, Protocol.telnet, Protocol.sftp, Protocol.redis, Protocol.mongodb, Protocol.k8s, Protocol.clickhouse, ], + # 限制客户端的协议,比如 koko 虽然也支持 数据库的 ssh 连接,但是不再这里拉起 + # Listen协议: [Asset协议] + 'client_limits': { + Protocol.sftp: [Protocol.sftp], + Protocol.ssh: [Protocol.ssh, Protocol.telnet], + }, 'match': 'm2m' }, TerminalType.chen: { @@ -262,20 +239,20 @@ class ConnectMethodUtil: methods = defaultdict(list) spec_web_methods = WebMethod.get_spec_methods() - native_methods = NativeClient.get_methods(os) applet_methods = AppletMethod.get_methods() + native_methods = NativeClient.get_methods(os=os) for component, component_protocol in cls.components().items(): support = component_protocol['support'] - component_web_methods = component_protocol.get('web_methods', []) + default_web_methods = component_protocol.get('web_methods', []) + client_limits = component_protocol.get('client_limits', {}) - for protocol in support: + for asset_protocol in support: # Web 方式 - web_methods = spec_web_methods.get(protocol, None) - if web_methods is None: - web_methods = component_web_methods - - methods[str(protocol)].extend([ + web_methods = spec_web_methods.get(asset_protocol, []) + if not web_methods: + web_methods = default_web_methods + methods[str(asset_protocol)].extend([ { 'component': component.value, 'type': 'web', @@ -288,31 +265,32 @@ class ConnectMethodUtil: # 客户端方式 if component_protocol['match'] == 'map': - listen = [protocol] + listen = [asset_protocol] else: listen = component_protocol['listen'] for listen_protocol in listen: - # Native method - if component == TerminalType.koko and protocol.value != Protocol.ssh: - # koko 仅支持 ssh 的 native 方式,其他数据库的 native 方式不提供 + limits = client_limits.get(listen_protocol, []) + if limits and asset_protocol not in limits: continue - methods[str(protocol)].extend([ + # Native method + client_methods = native_methods.get(listen_protocol, []) + methods[str(asset_protocol)].extend([ { 'component': component.value, 'type': 'native', 'endpoint_protocol': listen_protocol, **method } - for method in native_methods[listen_protocol] + for method in client_methods ]) # 远程应用方式,这个只有 tinker 提供,并且协议可能是自定义的 - for protocol, applet_methods in applet_methods.items(): + for asset_protocol, applet_methods in applet_methods.items(): for method in applet_methods: method['listen'] = 'rdp' method['component'] = TerminalType.tinker.value - methods[protocol].extend(applet_methods) + methods[asset_protocol].extend(applet_methods) cls._all_methods[os] = methods return methods diff --git a/apps/terminal/const.py b/apps/terminal/const.py index f34233e3a..c047343a4 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -2,7 +2,7 @@ # from django.db.models import TextChoices, IntegerChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class RiskLevelChoices(IntegerChoices): @@ -88,3 +88,9 @@ class SessionType(TextChoices): class ActionPermission(TextChoices): readonly = "readonly", _('Read Only') writable = "writable", _('Writable') + + +class TaskNameType(TextChoices): + kill_session = "kill_session", _('Kill Session') + lock_session = "lock_session", _('Lock Session') + unlock_session = "unlock_session", _('Unlock Session') diff --git a/apps/terminal/exceptions.py b/apps/terminal/exceptions.py index bb3e43591..7dab48300 100644 --- a/apps/terminal/exceptions.py +++ b/apps/terminal/exceptions.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.exceptions import JMSException @@ -11,4 +11,3 @@ class BulkCreateNotSupport(JMSException): class StorageInvalid(JMSException): default_code = 'storage_invalid' default_detail = _('Storage is invalid') - diff --git a/apps/terminal/migrations/0061_applet_can_concurrent.py b/apps/terminal/migrations/0061_applet_can_concurrent.py index 4ca762e65..5041e5425 100644 --- a/apps/terminal/migrations/0061_applet_can_concurrent.py +++ b/apps/terminal/migrations/0061_applet_can_concurrent.py @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='applet', name='can_concurrent', - field=models.BooleanField(default=True, verbose_name='Can concurrent'), + field=models.BooleanField(default=False, verbose_name='Can concurrent'), ), ] diff --git a/apps/terminal/migrations/0064_auto_20230728_1001.py b/apps/terminal/migrations/0064_auto_20230728_1001.py new file mode 100644 index 000000000..900abb5ba --- /dev/null +++ b/apps/terminal/migrations/0064_auto_20230728_1001.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.10 on 2023-07-28 02:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0063_auto_20230621_1133'), + ] + + operations = [ + migrations.AddField( + model_name='applethost', + name='accounts_create_amount', + field=models.IntegerField(default=100, verbose_name='Accounts create amount'), + ), + migrations.AddField( + model_name='applethost', + name='auto_create_accounts', + field=models.BooleanField(default=True, verbose_name='Auto create accounts'), + ), + migrations.AddField( + model_name='sessionsharing', + name='origin', + field=models.URLField(blank=True, null=True, verbose_name='Origin'), + ), + migrations.AddField( + model_name='session', + name='cmd_amount', + field=models.IntegerField(default=-1, verbose_name='Command amount'), + ), + migrations.AlterField( + model_name='task', + name='name', + field=models.CharField(choices=[('kill_session', 'Kill Session'), ('lock_session', 'Lock Session'), ('unlock_session', 'Unlock Session')], max_length=128, verbose_name='Name'), + ), + ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index edf7b332b..ccd1a8e52 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -39,7 +39,7 @@ class Applet(JMSBaseModel): is_active = models.BooleanField(default=True, verbose_name=_('Is active')) builtin = models.BooleanField(default=False, verbose_name=_('Builtin')) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) - can_concurrent = models.BooleanField(default=True, verbose_name=_('Can concurrent')) + can_concurrent = models.BooleanField(default=False, verbose_name=_('Can concurrent')) tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) hosts = models.ManyToManyField( @@ -156,10 +156,11 @@ class Applet(JMSBaseModel): spec_label = asset.labels.filter(name__in=['AppletHost', '发布机']).first() if spec_label: - host = [host for host in hosts if host.name == spec_label.value] - if host: - return host[0] + matched = [host for host in hosts if host.name == spec_label.value] + if matched: + return matched[0] + hosts = [h for h in hosts if h.auto_create_accounts] prefer_key = 'applet_host_prefer_{}'.format(user.id) prefer_host_id = cache.get(prefer_key, None) pref_host = [host for host in hosts if host.id == prefer_host_id] @@ -193,44 +194,79 @@ class Applet(JMSBaseModel): cache.set(prefer_host_account_key, account.id, timeout=None) return account + accounts_using_key_tmpl = 'applet_host_accounts_{}_{}_{}' + + def select_a_public_account(self, user, host, valid_accounts): + using_keys = cache.keys(self.accounts_using_key_tmpl.format(host.id, '*', '*')) or [] + accounts_username_used = list(cache.get_many(using_keys).values()) + logger.debug('Applet host account using: {}: {}'.format(host.name, accounts_username_used)) + accounts = valid_accounts.exclude(username__in=accounts_username_used) + public_accounts = accounts.filter(username__startswith='jms_') + if not public_accounts: + public_accounts = accounts.exclude(username__in=['Administrator', 'root']) + account = self.random_select_prefer_account(user, host, public_accounts) + return account + + def try_to_use_private_account(self, user, host, valid_accounts): + host_can_concurrent = str(host.deploy_options.get('RDS_fSingleSessionPerUser', 0)) == '0' + app_can_concurrent = self.can_concurrent or self.type == 'web' + all_can_concurrent = host_can_concurrent and app_can_concurrent + + private_account = valid_accounts.filter(username='js_{}'.format(user.username)).first() + if not private_account: + logger.debug('Private account not found ...') + return None + # 优先使用 private account,支持并发或者不支持并发时,如果私有没有被占用,则使用私有 + account = None + # 如果都支持,不管私有是否被占用,都使用私有 + if all_can_concurrent: + logger.debug('All can concurrent, use private account') + account = private_account + # 如果主机都不支持并发,则查询一下私有账号有没有任何应用使用,如果没有被使用,则使用私有 + elif not host_can_concurrent: + private_using_key = self.accounts_using_key_tmpl.format(host.id, private_account.username, '*') + private_is_using = len(cache.keys(private_using_key)) + logger.debug("Private account is using: {}".format(private_is_using)) + if not private_is_using: + account = private_account + # 如果主机支持,但是应用不支持并发,则查询一下私有账号有没有被这个应用使用, 如果没有被使用,则使用私有 + elif host_can_concurrent and not app_can_concurrent: + private_app_using_key = self.accounts_using_key_tmpl.format(host.id, private_account.username, self.name) + private_is_using_by_this_app = cache.get(private_app_using_key, False) + logger.debug("Private account is using {} by {}".format(private_is_using_by_this_app, self.name)) + if not private_is_using_by_this_app: + account = private_account + return account + def select_host_account(self, user, asset): # 选择激活的发布机 host = self.select_host(user, asset) + logger.info('Select applet host: {}'.format(host.name)) if not host: return None - host_concurrent = str(host.deploy_options.get('RDS_fSingleSessionPerUser', 0)) == '0' - can_concurrent = (self.can_concurrent or self.type == 'web') and host_concurrent - accounts = host.accounts.all().filter(is_active=True, privileged=False) - private_account = accounts.filter(username='js_{}'.format(user.username)).first() - accounts_using_key_tmpl = 'applet_host_accounts_{}_{}' + valid_accounts = host.accounts.all().filter(is_active=True, privileged=False) + account = self.try_to_use_private_account(user, host, valid_accounts) + if not account: + logger.debug('No private account, try to use public account') + account = self.select_a_public_account(user, host, valid_accounts) - if private_account and can_concurrent: - account = private_account - else: - using_keys = cache.keys(accounts_using_key_tmpl.format(host.id, '*')) or [] - accounts_username_used = list(cache.get_many(using_keys).values()) - logger.debug('Applet host account using: {}: {}'.format(host.name, accounts_username_used)) + if not account: + logger.debug('No available account for applet host: {}'.format(host.name)) + return None - # 优先使用 private account - if private_account and private_account.username not in accounts_username_used: - account = private_account - else: - accounts = accounts.exclude(username__in=accounts_username_used) \ - .filter(username__startswith='jms_') - account = self.random_select_prefer_account(user, host, accounts) - if not account: - return ttl = 60 * 60 * 24 - lock_key = accounts_using_key_tmpl.format(host.id, account.username) + lock_key = self.accounts_using_key_tmpl.format(host.id, account.username, self.name) cache.set(lock_key, account.username, ttl) - return { + res = { 'host': host, 'account': account, 'lock_key': lock_key, 'ttl': ttl } + logger.debug('Select host and account: {}'.format(res)) + return res def delete(self, using=None, keep_parents=False): platform = self.get_related_platform() diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index b0e7c478c..d7f5a9a6a 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -1,4 +1,3 @@ -import os from collections import defaultdict from django.db import models @@ -17,6 +16,8 @@ __all__ = ['AppletHost', 'AppletHostDeployment'] class AppletHost(Host): deploy_options = models.JSONField(default=dict, verbose_name=_('Deploy options')) + auto_create_accounts = models.BooleanField(default=True, verbose_name=_('Auto create accounts')) + accounts_create_amount = models.IntegerField(default=100, verbose_name=_('Accounts create amount')) inited = models.BooleanField(default=False, verbose_name=_('Inited')) date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) @@ -84,13 +85,14 @@ class AppletHost(Host): return random_string(16, special_char=True) def generate_accounts(self): + if not self.auto_create_accounts: + return self.generate_public_accounts() self.generate_private_accounts() def generate_public_accounts(self): - public_amount = int(os.getenv('TERMINAL_ACCOUNTS_AMOUNT', 100)) now_count = self.accounts.filter(privileged=False, username__startswith='jms').count() - need = public_amount - now_count + need = self.accounts_create_amount - now_count accounts = [] account_model = self.accounts.model @@ -123,6 +125,7 @@ class AppletHost(Host): from users.models import User usernames = User.objects \ .filter(is_active=True, is_service_account=False) \ + .exclude(username__startswith='[') \ .values_list('username', flat=True) account_usernames = self.accounts.all().values_list('username', flat=True) account_usernames = [username[3:] for username in account_usernames if username.startswith('js_')] @@ -144,7 +147,7 @@ class AppletHostDeployment(JMSBaseModel): def start(self, **kwargs): # 重新初始化部署,applet host 关联的终端需要删除 - # 否则 tinker 会因终端注册名称相同,造成冲突,执行任务失败 + # 否则 tinker 会因组件注册名称相同,造成冲突,执行任务失败 if self.host.terminal: terminal = self.host.terminal self.host.terminal = None diff --git a/apps/terminal/models/component/endpoint.py b/apps/terminal/models/component/endpoint.py index c68c06307..88d2f6c8f 100644 --- a/apps/terminal/models/component/endpoint.py +++ b/apps/terminal/models/component/endpoint.py @@ -1,6 +1,6 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.models import Asset from common.db.fields import PortField @@ -41,6 +41,8 @@ class Endpoint(JMSBaseModel): protocol == Protocol.oracle: port = db_port_manager.get_port_by_db(target_instance) else: + if protocol == Protocol.sftp: + protocol = Protocol.ssh port = getattr(self, f'{protocol}_port', 0) return port diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index d273b9dac..2f74ae18d 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -3,7 +3,7 @@ import uuid from django.core.cache import cache from django.db import models from django.forms.models import model_to_dict -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger diff --git a/apps/terminal/models/component/storage.py b/apps/terminal/models/component/storage.py index 0d4a5347f..6a34f3f5a 100644 --- a/apps/terminal/models/component/storage.py +++ b/apps/terminal/models/component/storage.py @@ -7,7 +7,7 @@ from importlib import import_module import jms_storage from django.conf import settings from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.fields import EncryptJsonDictTextField from common.db.models import JMSBaseModel diff --git a/apps/terminal/models/component/task.py b/apps/terminal/models/component/task.py index 1c081da53..c69a89423 100644 --- a/apps/terminal/models/component/task.py +++ b/apps/terminal/models/component/task.py @@ -1,18 +1,16 @@ from __future__ import unicode_literals from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel +from terminal.const import TaskNameType as SessionTaskChoices from .terminal import Terminal class Task(JMSBaseModel): - NAME_CHOICES = ( - ("kill_session", "Kill Session"), - ) - name = models.CharField(max_length=128, choices=NAME_CHOICES, verbose_name=_("Name")) + name = models.CharField(max_length=128, choices=SessionTaskChoices.choices, verbose_name=_("Name")) args = models.CharField(max_length=1024, verbose_name=_("Args")) kwargs = models.JSONField(default=dict, verbose_name=_("Kwargs")) terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL) diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index cc79212db..9955abf03 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,9 +1,9 @@ -import time import uuid + from django.conf import settings from django.core.cache import cache from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.const.signals import SKIP_SIGNAL from common.db.models import JMSBaseModel @@ -131,6 +131,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel): 'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME, 'SECURITY_SESSION_SHARE': settings.SECURITY_SESSION_SHARE, 'FTP_FILE_MAX_STORE': settings.FTP_FILE_MAX_STORE, + 'SECURITY_MAX_SESSION_TIME': settings.SECURITY_MAX_SESSION_TIME, }) return configs diff --git a/apps/terminal/models/session/command.py b/apps/terminal/models/session/command.py index 3d377523b..9978f9911 100644 --- a/apps/terminal/models/session/command.py +++ b/apps/terminal/models/session/command.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.db.models.signals import post_save -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from terminal.backends.command.models import AbstractSessionCommand diff --git a/apps/terminal/models/session/replay.py b/apps/terminal/models/session/replay.py index 9fd210f48..29d55cd9c 100644 --- a/apps/terminal/models/session/replay.py +++ b/apps/terminal/models/session/replay.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel from .session import Session diff --git a/apps/terminal/models/session/session.py b/apps/terminal/models/session/session.py index ccf5936b8..09f2df9e1 100644 --- a/apps/terminal/models/session/session.py +++ b/apps/terminal/models/session/session.py @@ -8,7 +8,7 @@ from django.core.cache import cache from django.core.files.storage import default_storage from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.models import Asset from common.utils import get_object_or_none, lazyproperty @@ -44,9 +44,11 @@ class Session(OrgModelMixin): date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + cmd_amount = models.IntegerField(default=-1, verbose_name=_("Command amount")) upload_to = 'replay' ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}' + LOCK_CACHE_KEY_PREFIX = 'TOGGLE_LOCKED_SESSION_{}' SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4'} DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz', '.replay.mp4'] @@ -144,6 +146,26 @@ class Session(OrgModelMixin): else: return True + @property + def is_locked(self): + if self.is_finished: + return False + key = self.LOCK_CACHE_KEY_PREFIX.format(self.id) + return bool(cache.get(key)) + + @classmethod + def lock_session(cls, session_id): + key = cls.LOCK_CACHE_KEY_PREFIX.format(session_id) + # 会话锁定时间为 None,表示永不过期 + # You can set TIMEOUT to None so that, by default, cache keys never expire. + # https://docs.djangoproject.com/en/4.1/topics/cache/ + cache.set(key, True, timeout=None) + + @classmethod + def unlock_session(cls, session_id): + key = cls.LOCK_CACHE_KEY_PREFIX.format(session_id) + cache.delete(key) + @lazyproperty def terminal_display(self): display = self.terminal.name if self.terminal else '' @@ -177,6 +199,25 @@ class Session(OrgModelMixin): @property def command_amount(self): + if self.need_update_cmd_amount: + cmd_amount = self.compute_command_amount() + self.cmd_amount = cmd_amount + self.save() + elif self.need_compute_cmd_amount: + cmd_amount = self.compute_command_amount() + else: + cmd_amount = self.cmd_amount + return cmd_amount + + @property + def need_update_cmd_amount(self): + return self.is_finished and self.need_compute_cmd_amount + + @property + def need_compute_cmd_amount(self): + return self.cmd_amount == -1 + + def compute_command_amount(self): command_store = get_multi_command_storage() return command_store.count(session=str(self.id)) diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py index c0e83dfda..92a75f9c4 100644 --- a/apps/terminal/models/session/sharing.py +++ b/apps/terminal/models/session/sharing.py @@ -2,7 +2,8 @@ import datetime from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from django.utils.functional import cached_property from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin @@ -30,10 +31,10 @@ class SessionSharing(JMSBaseModel, OrgModelMixin): default=0, verbose_name=_('Expired time (min)'), db_index=True ) users = models.TextField(blank=True, verbose_name=_("User")) - action_permission = models.CharField( max_length=16, verbose_name=_('Action permission'), default='writable' ) + origin = models.URLField(blank=True, null=True, verbose_name=_('Origin')) class Meta: ordering = ('-date_created',) @@ -45,15 +46,24 @@ class SessionSharing(JMSBaseModel, OrgModelMixin): def __str__(self): return 'Creator: {}'.format(self.creator) + @cached_property + def url(self): + return '%s/koko/share/%s/' % (self.origin, self.id) + + @cached_property def users_display(self): if not self.users: return [] with tmp_to_root_org(): - user_ids = self.users.split(',') - users = User.objects.filter(id__in=user_ids) + users = self.users_queryset users = [str(user) for user in users] return users + @cached_property + def users_queryset(self): + user_ids = self.users.split(',') + return User.objects.filter(id__in=user_ids) + @property def date_expired(self): return self.date_created + datetime.timedelta(minutes=self.expired_time) diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py index cfcac4396..0d2501450 100644 --- a/apps/terminal/notifications.py +++ b/apps/terminal/notifications.py @@ -2,7 +2,7 @@ from typing import Callable from django.conf import settings from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger, reverse from common.utils import lazyproperty @@ -10,9 +10,9 @@ from common.utils.timezone import local_now_display from notifications.backends import BACKEND from notifications.models import SystemMsgSubscription from notifications.notifications import SystemMessage, UserMessage +from terminal.const import RiskLevelChoices from terminal.models import Session, Command from users.models import User -from terminal.const import RiskLevelChoices logger = get_logger(__name__) @@ -255,3 +255,27 @@ class StorageConnectivityMessage(SystemMessage): 'subject': subject, 'message': message } + + +class SessionSharingMessage(UserMessage): + message_type_label = _('Session sharing') + + def __init__(self, user, instance): + super().__init__(user) + self.instance = instance + + def get_html_msg(self) -> dict: + instance = self.instance + context = { + 'asset': instance.session.asset, + 'created_by': instance.created_by, + 'account': instance.session.account, + 'url': instance.url, + 'verify_code': instance.verify_code, + 'org': instance.org_name, + } + message = render_to_string('terminal/_msg_session_sharing.html', context) + return { + 'subject': self.message_type_label + ' ' + self.instance.created_by, + 'message': message + } diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index 92d535c65..dc23362f9 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- # from .applet import * -from .command import * -from .session import * -from .storage import * -from .sharing import * -from .terminal import * -from .endpoint import * from .applet_host import * +from .command import * +from .endpoint import * +from .session import * +from .sharing import * +from .storage import * +from .task import * +from .terminal import * diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index aa3623436..fad8e9780 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -27,8 +27,10 @@ class AppletPublicationSerializer(serializers.ModelSerializer): class AppletSerializer(serializers.ModelSerializer): icon = serializers.ReadOnlyField(label=_("Icon")) type = LabeledChoiceField(choices=Applet.Type.choices, label=_("Type")) - edition = LabeledChoiceField(choices=Applet.Edition.choices, label=_("Edition"), required=False, - default=Applet.Edition.community) + edition = LabeledChoiceField( + choices=Applet.Edition.choices, label=_("Edition"), required=False, + default=Applet.Edition.community + ) class Meta: model = Applet diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index 392b2e2e3..221c093d2 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -15,7 +15,7 @@ from ..models import AppletHost, AppletHostDeployment __all__ = [ 'AppletHostSerializer', 'AppletHostDeploymentSerializer', 'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer', - 'AppletHostStartupSerializer', 'AppletHostDeployAppletSerializer' + 'AppletHostStartupSerializer' ] @@ -62,11 +62,19 @@ class AppletHostSerializer(HostSerializer): class Meta(HostSerializer.Meta): model = AppletHost fields = HostSerializer.Meta.fields + [ + 'auto_create_accounts', 'accounts_create_amount', 'load', 'date_synced', 'deploy_options' ] extra_kwargs = { **HostSerializer.Meta.extra_kwargs, - 'date_synced': {'read_only': True} + 'date_synced': {'read_only': True}, + 'auto_create_accounts': {'help_text': _( + 'These accounts are used to connect to the published application, ' + 'the account is now divided into two types, one is dedicated to each account, ' + 'each user has a private account, the other is public, ' + 'when the application does not support multiple open and the special has been used, ' + 'the public account will be used to connect')}, + 'accounts_create_amount': {'help_text': _('The number of public accounts created automatically')}, } def __init__(self, *args, data=None, **kwargs): @@ -116,13 +124,6 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer): fields = fields_mini + ['comment'] + read_only_fields -class AppletHostDeployAppletSerializer(AppletHostDeploymentSerializer): - applet_id = serializers.UUIDField(write_only=True, allow_null=True, required=False) - - class Meta(AppletHostDeploymentSerializer.Meta): - fields = AppletHostDeploymentSerializer.Meta.fields + ['applet_id'] - - class AppletHostAccountSerializer(serializers.ModelSerializer): class Meta: model = Account diff --git a/apps/terminal/serializers/command.py b/apps/terminal/serializers/command.py index b8983a59c..11b16f5ad 100644 --- a/apps/terminal/serializers/command.py +++ b/apps/terminal/serializers/command.py @@ -1,18 +1,17 @@ # ~*~ coding: utf-8 ~*~ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from common.utils import pretty_string, is_uuid, get_logger from common.serializers.fields import LabeledChoiceField -from terminal.models import Command +from common.utils import pretty_string, is_uuid, get_logger from terminal.const import RiskLevelChoices +from terminal.models import Command logger = get_logger(__name__) __all__ = ['SessionCommandSerializer', 'InsecureCommandAlertSerializer'] class SimpleSessionCommandSerializer(serializers.ModelSerializer): - """ 简单Session命令序列类, 用来提取公共字段 """ user = serializers.CharField(label=_("User")) # 限制 64 字符,见 validate_user asset = serializers.CharField(max_length=128, label=_("Asset")) diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index dc736a522..6e5476c3e 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -1,7 +1,8 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import LabeledChoiceField +from common.utils import pretty_string from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .terminal import TerminalSmallSerializer from ..const import SessionType @@ -22,6 +23,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer): can_replay = serializers.BooleanField(read_only=True, label=_("Can replay")) can_join = serializers.BooleanField(read_only=True, label=_("Can join")) can_terminate = serializers.BooleanField(read_only=True, label=_("Can terminate")) + asset = serializers.CharField(label=_("Asset"), style={'base_template': 'textarea.html'}) class Meta: model = Session @@ -30,7 +32,8 @@ class SessionSerializer(BulkOrgResourceModelSerializer): "user", "asset", "user_id", "asset_id", 'account', 'account_id', "protocol", 'type', "login_from", "remote_addr", "is_success", "is_finished", "has_replay", "has_command", - "date_start", "date_end", "comment", "terminal_display" + "date_start", "date_end", "comment", "terminal_display", "is_locked", + 'command_amount', ] fields_fk = ["terminal", ] fields_custom = ["can_replay", "can_join", "can_terminate"] @@ -49,6 +52,11 @@ class SessionSerializer(BulkOrgResourceModelSerializer): 'terminal_display': {'label': _('Terminal display')}, } + def validate_asset(self, value): + max_length = self.Meta.model.asset.field.max_length + value = pretty_string(value, max_length=max_length) + return value + class SessionDisplaySerializer(SessionSerializer): command_amount = serializers.IntegerField(read_only=True, label=_('Command amount')) diff --git a/apps/terminal/serializers/sharing.py b/apps/terminal/serializers/sharing.py index c7b8f2e4e..c891a70b5 100644 --- a/apps/terminal/serializers/sharing.py +++ b/apps/terminal/serializers/sharing.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import LabeledChoiceField @@ -26,7 +26,7 @@ class SessionSharingSerializer(OrgResourceModelSerializerMixin): fields_small = fields_mini + [ 'verify_code', 'is_active', 'expired_time', 'created_by', 'date_created', 'date_updated', 'users', 'users_display', - 'action_permission' + 'action_permission', 'origin', 'url', ] fields_fk = ['session', 'creator'] fields = fields_small + fields_fk diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index dd0f7f677..2c9a1b1c2 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -1,15 +1,17 @@ # -*- coding: utf-8 -*- # +from urllib.parse import urlparse + +from django.db.models import TextChoices +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.validators import UniqueValidator -from urllib.parse import urlparse -from django.utils.translation import ugettext_lazy as _ -from django.db.models import TextChoices -from common.serializers.fields import LabeledChoiceField + from common.serializers import MethodSerializer +from common.serializers.fields import LabeledChoiceField from common.serializers.fields import ReadableHiddenField, EncryptedField -from ..models import ReplayStorage, CommandStorage from .. import const +from ..models import ReplayStorage, CommandStorage # Replay storage serializers diff --git a/apps/terminal/serializers/task.py b/apps/terminal/serializers/task.py new file mode 100644 index 000000000..4f077dac6 --- /dev/null +++ b/apps/terminal/serializers/task.py @@ -0,0 +1,10 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from terminal.const import TaskNameType as SessionTaskChoices + + +class LockTaskSessionSerializer(serializers.Serializer): + + session_id = serializers.CharField(max_length=40, label=_('Session id')) + task_name = serializers.ChoiceField(choices=SessionTaskChoices.choices, label=_('Task name')) diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index 2904c7938..a65249551 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers import BulkModelSerializer diff --git a/apps/terminal/signal_handlers/__init__.py b/apps/terminal/signal_handlers/__init__.py index 265f6416d..bd61c885c 100644 --- a/apps/terminal/signal_handlers/__init__.py +++ b/apps/terminal/signal_handlers/__init__.py @@ -1,3 +1,5 @@ from .applet import * from .db_port import * +from .session import * +from .session_sharing import * from .terminal import * diff --git a/apps/terminal/signal_handlers/applet.py b/apps/terminal/signal_handlers/applet.py index 4fe390b8e..3b1239901 100644 --- a/apps/terminal/signal_handlers/applet.py +++ b/apps/terminal/signal_handlers/applet.py @@ -23,18 +23,22 @@ def on_applet_host_create(sender, instance, created=False, **kwargs): applets = Applet.objects.all() instance.applets.set(applets) - applet_host_generate_accounts.delay(instance.id) applet_host_change_pub_sub.publish(True) + if instance.auto_create_accounts: + applet_host_generate_accounts.delay(instance.id) @receiver(post_save, sender=User) -def on_user_create_create_account(sender, instance, created=False, **kwargs): +def on_user_create_create_account(sender, instance: User, created=False, **kwargs): if not created: return - + if instance.is_service_account: + return with tmp_to_builtin_org(system=1): applet_hosts = AppletHost.objects.all() for host in applet_hosts: + if not host.auto_create_accounts: + continue host.generate_private_accounts_by_usernames([instance.username]) @@ -57,7 +61,6 @@ def on_applet_create(sender, instance, created=False, **kwargs): return hosts = AppletHost.objects.all() instance.hosts.set(hosts) - applet_host_change_pub_sub.publish(True) diff --git a/apps/terminal/signal_handlers/session.py b/apps/terminal/signal_handlers/session.py new file mode 100644 index 000000000..7aa35307d --- /dev/null +++ b/apps/terminal/signal_handlers/session.py @@ -0,0 +1,18 @@ +from django.db.models.signals import pre_save, post_save +from django.dispatch import receiver + +from terminal.models import Session + + +@receiver(pre_save, sender=Session) +def on_session_pre_save(sender, instance, **kwargs): + if instance.need_update_cmd_amount: + instance.cmd_amount = instance.compute_command_amount() + + +@receiver(post_save, sender=Session) +def on_session_finished(sender, instance: Session, created, **kwargs): + if not instance.is_finished: + return + # 清理一次可能因 task 未执行的缓存数据 + Session.unlock_session(instance.id) diff --git a/apps/terminal/signal_handlers/session_sharing.py b/apps/terminal/signal_handlers/session_sharing.py new file mode 100644 index 000000000..f20ccf665 --- /dev/null +++ b/apps/terminal/signal_handlers/session_sharing.py @@ -0,0 +1,13 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from terminal.models import SessionSharing +from terminal.notifications import SessionSharingMessage + + +@receiver(post_save, sender=SessionSharing) +def on_session_sharing_created(sender, instance: SessionSharing, created, **kwargs): + if not created: + return + for user in instance.users_queryset: + SessionSharingMessage(user, instance).publish_async() diff --git a/apps/terminal/signal_handlers/terminal.py b/apps/terminal/signal_handlers/terminal.py index 9a1536e02..a227c0cf1 100644 --- a/apps/terminal/signal_handlers/terminal.py +++ b/apps/terminal/signal_handlers/terminal.py @@ -5,7 +5,8 @@ from django.utils.functional import LazyObject from common.decorators import on_transaction_commit from common.utils import get_logger from common.utils.connection import RedisPubSub -from ..models import Task +from ..const import TaskNameType +from ..models import Task, Session from ..utils import DBPortManager db_port_manager: DBPortManager @@ -24,7 +25,17 @@ component_event_chan = ComponentEventChan() @receiver(post_save, sender=Task) @on_transaction_commit def on_task_created(sender, instance: Task, created, **kwargs): - if not created: + if not created and instance.is_finished: + # 当组件完成 task 时,修改 session 的 lock 状态 + session_id = instance.args + name = instance.name + if name == TaskNameType.lock_session: + Session.lock_session(session_id) + elif name == TaskNameType.unlock_session: + Session.unlock_session(session_id) + logger.debug(f"signal task post save: {instance.name}, " + f"session: {session_id}, " + f"is_finished: {instance.is_finished}") return event = { "type": instance.name, diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 380bd94f6..65fae3ded 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -98,12 +98,13 @@ def run_applet_host_deployment(did): @shared_task( verbose_name=_('Install applet'), - activity_callback=lambda self, did, applet_id, *args, **kwargs: ([did],) + activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,) ) -def run_applet_host_deployment_install_applet(did, applet_id): +def run_applet_host_deployment_install_applet(ids, applet_id): with tmp_to_builtin_org(system=1): - deployment = AppletHostDeployment.objects.get(id=did) - deployment.install_applet(applet_id) + for did in ids: + deployment = AppletHostDeployment.objects.get(id=did) + deployment.install_applet(applet_id) @shared_task( diff --git a/apps/terminal/templates/terminal/_msg_session_sharing.html b/apps/terminal/templates/terminal/_msg_session_sharing.html new file mode 100644 index 000000000..e13d5b808 --- /dev/null +++ b/apps/terminal/templates/terminal/_msg_session_sharing.html @@ -0,0 +1,16 @@ +{% load i18n %} + +
+ {% trans 'Asset' %}: {{ asset }} +
+ {% trans 'User' %}: {{ created_by }} +
+ {% trans 'Account' %}: {{ account }} +
+ {% trans 'Session share' %}: {% trans 'View' %} +
+ {% trans 'Verify code' %}: {{ verify_code }} +
+ {% trans 'Organization' %}: {{ org }} +
+
diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index c13892346..3c38bb934 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -2,10 +2,9 @@ # -*- coding: utf-8 -*- # -from django.urls import path, re_path +from django.urls import path from rest_framework_bulk.routes import BulkRouter -from common import api as capi from .. import api app_name = 'terminal' diff --git a/apps/terminal/utils/components.py b/apps/terminal/utils/components.py index 100391dbd..2c3786dbf 100644 --- a/apps/terminal/utils/components.py +++ b/apps/terminal/utils/components.py @@ -68,11 +68,16 @@ class TypedComponentsStatusMetricsUtil(object): metrics = [] for _tp, components in self.grouped_components: metric = { - 'normal': 0, 'high': 0, 'critical': 0, 'offline': 0, - 'total': 0, 'session_active': 0, 'type': _tp + 'total': 0, + 'type': _tp, + 'session_active': 0, + ComponentLoad.high: [], + ComponentLoad.normal: [], + ComponentLoad.offline: [], + ComponentLoad.critical: [], } for component in components: - metric[component.load] += 1 + metric[component.load].append(component.name) metric['total'] += 1 metric['session_active'] += component.get_online_session_count() metrics.append(metric) diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index 9d335859a..9878fa9ef 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -1,6 +1,6 @@ from django.conf import settings from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from assets.const import DatabaseTypes from assets.models import Database diff --git a/apps/terminal/ws.py b/apps/terminal/ws.py index c414ecfcf..70dd7b6f3 100644 --- a/apps/terminal/ws.py +++ b/apps/terminal/ws.py @@ -7,8 +7,8 @@ from rest_framework.renderers import JSONRenderer from common.db.utils import safe_db_connection from common.utils import get_logger from common.utils.connection import Subscription -from terminal.models import Terminal -from terminal.models import Session +from terminal.const import TaskNameType +from terminal.models import Session, Terminal from terminal.serializers import TaskSerializer, StatSerializer from .signal_handlers import component_event_chan @@ -45,7 +45,7 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer): with safe_db_connection(): serializer.save() - def send_kill_tasks_msg(self, task_id=None): + def send_tasks_msg(self, task_id=None): content = self.get_terminal_tasks(task_id) self.send(bytes_data=content) @@ -60,7 +60,7 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer): def watch_component_event(self): # 先发一次已有的任务 - self.send_kill_tasks_msg() + self.send_tasks_msg() ws = self @@ -68,8 +68,8 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer): logger.debug('New component task msg recv: {}'.format(msg)) msg_type = msg.get('type') payload = msg.get('payload') - if msg_type == "kill_session": - ws.send_kill_tasks_msg(payload.get('id')) + if msg_type in TaskNameType.names: + ws.send_tasks_msg(payload.get('id')) return component_event_chan.subscribe(handle_task_msg_recv) diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 2fa710ed6..bbe4ca4d4 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import gettext_lazy as _ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.exceptions import MethodNotAllowed @@ -38,9 +39,9 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): ordering = ('-date_created',) rbac_perms = { 'open': 'tickets.view_ticket', + 'bulk': 'tickets.change_ticket', } - def retrieve(self, request, *args, **kwargs): instance = self.get_object() with tmp_to_root_org(): @@ -57,6 +58,10 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): def destroy(self, request, *args, **kwargs): raise MethodNotAllowed(self.action) + def ticket_not_allowed(self): + if self.model == Ticket: + raise MethodNotAllowed(self.action) + def get_queryset(self): with tmp_to_root_org(): queryset = self.model.get_user_related_tickets(self.request.user) @@ -74,6 +79,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): @action(detail=True, methods=[PUT, PATCH], permission_classes=[IsAssignee, ]) def approve(self, request, *args, **kwargs): + self.ticket_not_allowed() + partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) @@ -95,6 +102,27 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): instance.close() return Response('ok') + @action(detail=False, methods=[PUT], permission_classes=[RBACPermission, ]) + def bulk(self, request, *args, **kwargs): + self.ticket_not_allowed() + + allow_action = ('approve', 'reject') + action_ = request.query_params.get('action') + if action_ not in allow_action: + msg = _("The parameter 'action' must be [{}]").format(','.join(allow_action)) + return Response({'error': msg}, status=400) + + ticket_ids = request.data.get('tickets', []) + queryset = self.get_queryset().filter(state='pending').filter(id__in=ticket_ids) + for obj in queryset: + if not obj.has_current_assignee(request.user): + return Response( + {'error': f"{_('User does not have permission')}: {obj}"}, status=400 + ) + handler = getattr(obj, action_) + handler(processor=request.user) + return Response('ok') + class ApplyAssetTicketViewSet(TicketViewSet): model = ApplyAssetTicket diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py index 317ac592e..49f818b7e 100644 --- a/apps/tickets/apps.py +++ b/apps/tickets/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class TicketsConfig(AppConfig): @@ -7,6 +7,6 @@ class TicketsConfig(AppConfig): verbose_name = _('Tickets') def ready(self): - from . import signal_handlers - from . import notifications + from . import signal_handlers # noqa + from . import notifications # noqa return super().ready() diff --git a/apps/tickets/const.py b/apps/tickets/const.py index ccd044bbe..a2a5ec981 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -1,5 +1,5 @@ from django.db.models import TextChoices, IntegerChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ TICKET_DETAIL_URL = '/ui/#/tickets/tickets/{id}?type={type}' diff --git a/apps/tickets/errors.py b/apps/tickets/errors.py index 716eeca94..5bd73a95f 100644 --- a/apps/tickets/errors.py +++ b/apps/tickets/errors.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.exceptions import JMSException diff --git a/apps/tickets/handlers/apply_asset.py b/apps/tickets/handlers/apply_asset.py index f2b2ee842..3b4c70729 100644 --- a/apps/tickets/handlers/apply_asset.py +++ b/apps/tickets/handlers/apply_asset.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from orgs.utils import tmp_to_org from perms.models import AssetPermission diff --git a/apps/tickets/handlers/base.py b/apps/tickets/handlers/base.py index 6548bfa8f..fbf6a8e46 100644 --- a/apps/tickets/handlers/base.py +++ b/apps/tickets/handlers/base.py @@ -1,6 +1,6 @@ import html2text from django.template.loader import render_to_string -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from common.utils import get_logger from tickets.const import TicketState, TicketType diff --git a/apps/tickets/migrations/0013_ticket_serial_num.py b/apps/tickets/migrations/0013_ticket_serial_num.py index 96d0cbc0d..bad05a7f6 100644 --- a/apps/tickets/migrations/0013_ticket_serial_num.py +++ b/apps/tickets/migrations/0013_ticket_serial_num.py @@ -28,7 +28,6 @@ def fill_ticket_serial_number(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ('tickets', '0012_ticketsession'), ] @@ -37,7 +36,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ticket', name='serial_num', - field=models.CharField(max_length=128, null=True, unique=True, verbose_name='Serial number'), + field=models.CharField(max_length=128, null=True, verbose_name='Serial number'), ), migrations.RunPython(fill_ticket_serial_number), + migrations.AlterUniqueTogether( + name='ticket', + unique_together={('serial_num',)}, + ), ] diff --git a/apps/tickets/models/comment.py b/apps/tickets/models/comment.py index 6577b65e2..20d427497 100644 --- a/apps/tickets/models/comment.py +++ b/apps/tickets/models/comment.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel diff --git a/apps/tickets/models/flow.py b/apps/tickets/models/flow.py index 0b7518fc6..c339e4685 100644 --- a/apps/tickets/models/flow.py +++ b/apps/tickets/models/flow.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin diff --git a/apps/tickets/models/relation.py b/apps/tickets/models/relation.py index ca159804d..98bb1dc87 100644 --- a/apps/tickets/models/relation.py +++ b/apps/tickets/models/relation.py @@ -1,10 +1,12 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class TicketSession(models.Model): - ticket = models.ForeignKey('tickets.Ticket', related_name='session_relation', on_delete=models.CASCADE, db_constraint=False) - session = models.ForeignKey('terminal.Session', related_name='ticket_relation', on_delete=models.CASCADE, db_constraint=False) + ticket = models.ForeignKey('tickets.Ticket', related_name='session_relation', on_delete=models.CASCADE, + db_constraint=False) + session = models.ForeignKey('terminal.Session', related_name='ticket_relation', on_delete=models.CASCADE, + db_constraint=False) class Meta: verbose_name = _("Ticket session relation") diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index 4d6c29483..f7c78a4a1 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -8,13 +8,13 @@ from django.db.models import Prefetch, Q from django.db.models.fields import related from django.db.utils import IntegrityError from django.forms import model_to_dict -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.encoder import ModelJSONFieldEncoder from common.db.models import JMSBaseModel from common.exceptions import JMSException -from common.utils.timezone import as_current_tz from common.utils import reverse +from common.utils.timezone import as_current_tz from orgs.models import Organization from orgs.utils import tmp_to_org from tickets.const import ( @@ -296,7 +296,7 @@ class Ticket(StatusMixin, JMSBaseModel): ) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) rel_snapshot = models.JSONField(verbose_name=_('Relation snapshot'), default=dict) - serial_num = models.CharField(_('Serial number'), max_length=128, unique=True, null=True) + serial_num = models.CharField(_('Serial number'), max_length=128, null=True) meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta")) org_id = models.CharField( max_length=36, blank=True, default='', verbose_name=_('Organization'), db_index=True @@ -305,6 +305,9 @@ class Ticket(StatusMixin, JMSBaseModel): class Meta: ordering = ('-date_created',) verbose_name = _('Ticket') + unique_together = ( + ('serial_num',), + ) def __str__(self): return '{}({})'.format(self.title, self.applicant) @@ -328,10 +331,10 @@ class Ticket(StatusMixin, JMSBaseModel): queries = Q(applicant=user) | Q(ticket_steps__ticket_assignees__assignee=user) # TODO: 与 StatusMixin.process_map 内连表查询有部分重叠 有优化空间 待验证排除是否不影响其它调用 prefetch_ticket_assignee = Prefetch('ticket_steps__ticket_assignees', - queryset=TicketAssignee.objects.select_related('assignee'), ) - tickets = cls.objects.prefetch_related(prefetch_ticket_assignee)\ - .select_related('applicant')\ - .filter(queries)\ + queryset=TicketAssignee.objects.select_related('assignee'), ) + tickets = cls.objects.prefetch_related(prefetch_ticket_assignee) \ + .select_related('applicant') \ + .filter(queries) \ .distinct() return tickets diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index 5da077496..91c0a047d 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -6,7 +6,7 @@ from django.core.cache import cache from django.forms import model_to_dict from django.shortcuts import reverse from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db.encoder import ModelJSONFieldEncoder from common.utils import get_logger, random_string diff --git a/apps/tickets/serializers/flow.py b/apps/tickets/serializers/flow.py index f3b9c0215..01951c1ae 100644 --- a/apps/tickets/serializers/flow.py +++ b/apps/tickets/serializers/flow.py @@ -1,13 +1,13 @@ -from rest_framework import serializers from django.db.transaction import atomic -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers +from common.serializers.fields import LabeledChoiceField +from orgs.mixins.serializers import OrgResourceModelSerializerMixin from orgs.models import Organization from orgs.utils import get_current_org_id -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from common.serializers.fields import LabeledChoiceField -from tickets.models import TicketFlow, ApprovalRule from tickets.const import TicketApprovalStrategy, TicketType +from tickets.models import TicketFlow, ApprovalRule __all__ = ['TicketFlowSerializer'] diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 98587f266..49dcc09a0 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from assets.models import Asset, Node diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index 8ec463337..90204c18a 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -1,6 +1,6 @@ from django.db.models import Model from django.db.transaction import atomic -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework import serializers from orgs.utils import tmp_to_org diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index ff76ee9eb..edeb97ad2 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import LabeledChoiceField diff --git a/apps/tickets/views/approve.py b/apps/tickets/views/approve.py index a3a265005..21e5b4af6 100644 --- a/apps/tickets/views/approve.py +++ b/apps/tickets/views/approve.py @@ -2,11 +2,13 @@ # from __future__ import unicode_literals + from django.core.cache import cache from django.shortcuts import redirect, reverse +from django.utils.translation import gettext as _ from django.views.generic.base import TemplateView -from django.utils.translation import ugettext as _ +from common.utils import get_logger, FlashMessageUtil from orgs.utils import tmp_to_root_org from tickets.const import TicketType from tickets.errors import AlreadyClosed @@ -14,7 +16,6 @@ from tickets.models import ( Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) -from common.utils import get_logger, FlashMessageUtil logger = get_logger(__name__) diff --git a/apps/users/api/__init__.py b/apps/users/api/__init__.py index d5db61888..6231e874b 100644 --- a/apps/users/api/__init__.py +++ b/apps/users/api/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # -from .user import * from .group import * from .profile import * -from .service_account import * from .relation import * +from .service import * +from .user import * diff --git a/apps/users/api/service_account.py b/apps/users/api/service.py similarity index 100% rename from apps/users/api/service_account.py rename to apps/users/api/service.py diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 7bb8a7d8f..524571025 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ from collections import defaultdict -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework import generics from rest_framework.decorators import action from rest_framework.response import Response @@ -118,7 +118,6 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV self.check_object_permissions(self.request, user) return super().perform_bulk_update(serializer) - def perform_bulk_destroy(self, objects): for obj in objects: self.check_object_permissions(self.request, obj) @@ -137,6 +136,10 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV users = validated_data['users'] org_roles = validated_data['org_roles'] + has_self = any([str(u.id) == str(request.user.id) for u in users]) + if has_self and not request.user.is_superuser: + error = {"error": _("Can not invite self")} + return Response(error, status=400) for user in users: user.org_roles.set(org_roles) return Response(serializer.data, status=201) diff --git a/apps/users/apps.py b/apps/users/apps.py index ead9987be..f5aa7e269 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.utils.translation import ugettext_lazy as _ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class UsersConfig(AppConfig): @@ -9,7 +9,7 @@ class UsersConfig(AppConfig): verbose_name = _('Users') def ready(self): - from . import signal_handlers - from . import notifications - from . import tasks + from . import signal_handlers # noqa + from . import tasks # noqa + from . import notifications # noqa super().ready() diff --git a/apps/users/const.py b/apps/users/const.py index 41f350a28..0de518098 100644 --- a/apps/users/const.py +++ b/apps/users/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models import TextChoices -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ TICKET_DETAIL_URL = '/ui/#/tickets/tickets/{id}' diff --git a/apps/users/migrations/0034_auto_20210506_1448.py b/apps/users/migrations/0034_auto_20210506_1448.py index f08b34b3e..449f0ff6e 100644 --- a/apps/users/migrations/0034_auto_20210506_1448.py +++ b/apps/users/migrations/0034_auto_20210506_1448.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('users', '0033_user_need_update_password'), ] @@ -13,11 +12,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='dingtalk_id', - field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='DingTalk'), + field=models.CharField(default=None, max_length=128, null=True, verbose_name='DingTalk'), ), migrations.AddField( model_name='user', name='wecom_id', - field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='WeCom'), + field=models.CharField(default=None, max_length=128, null=True, verbose_name='WeCom'), ), ] diff --git a/apps/users/migrations/0036_user_feishu_id.py b/apps/users/migrations/0036_user_feishu_id.py index 472bc0970..83c8c0d17 100644 --- a/apps/users/migrations/0036_user_feishu_id.py +++ b/apps/users/migrations/0036_user_feishu_id.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('users', '0035_auto_20210526_1100'), ] @@ -13,6 +12,10 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='feishu_id', - field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='FeiShu'), + field=models.CharField(default=None, max_length=128, null=True, verbose_name='FeiShu'), + ), + migrations.AlterUniqueTogether( + name='user', + unique_together={('feishu_id',), ('wecom_id',), ('dingtalk_id',)}, ), ] diff --git a/apps/users/models/group.py b/apps/users/models/group.py index e92e65c81..914c22f75 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.utils import lazyproperty from orgs.mixins.models import JMSOrgBaseModel diff --git a/apps/users/models/user.py b/apps/users/models/user.py index a156478bd..05c8ea084 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -8,6 +8,7 @@ import string import uuid from typing import Callable +import sshpubkeys from django.conf import settings from django.contrib.auth.hashers import check_password from django.contrib.auth.models import AbstractUser @@ -16,7 +17,7 @@ from django.db import models from django.shortcuts import reverse from django.utils import timezone from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.db import fields, models as jms_models from common.utils import ( @@ -43,6 +44,8 @@ class AuthMixin: set_password: Callable save: Callable history_passwords: models.Manager + sect_cache_tpl = 'user_sect_{}' + id: str @property def password_raw(self): @@ -105,7 +108,6 @@ class AuthMixin: return '' if self.public_key: - import sshpubkeys try: return sshpubkeys.SSHKey(self.public_key) except (TabError, TypeError): @@ -153,21 +155,48 @@ class AuthMixin: return False @staticmethod - def get_public_key_body(key): - for i in key.split(): - if len(i) > 256: - return i - return key + def get_public_key_md5(key): + try: + key_obj = sshpubkeys.SSHKey(key) + return key_obj.hash_md5() + except Exception as e: + return '' def check_public_key(self, key): if not self.public_key: return False - key = self.get_public_key_body(key) - key_saved = self.get_public_key_body(self.public_key) - if key == key_saved: - return True - else: + key_md5 = self.get_public_key_md5(key) + if not key_md5: return False + self_key_md5 = self.get_public_key_md5(self.public_key) + return key_md5 == self_key_md5 + + def cache_login_password_if_need(self, password): + from common.utils import signer + if not settings.CACHE_LOGIN_PASSWORD_ENABLED: + return + backend = getattr(self, 'backend', '') + if backend.lower().find('ldap') < 0: + return + if not password: + return + key = self.sect_cache_tpl.format(self.id) + ttl = settings.CACHE_LOGIN_PASSWORD_TTL + if not isinstance(ttl, int) or ttl <= 0: + return + secret = signer.sign(password) + cache.set(key, secret, ttl) + + def get_cached_password_if_has(self): + from common.utils import signer + if not settings.CACHE_LOGIN_PASSWORD_ENABLED: + return '' + key = self.sect_cache_tpl.format(self.id) + secret = cache.get(key) + if not secret: + return '' + password = signer.unsign(secret) + return password class RoleManager(models.Manager): @@ -359,6 +388,11 @@ class RoleMixin: def workbench_orgs(self): return self.cached_orgs['workbench_orgs'] + @lazyproperty + def joined_orgs(self): + from rbac.models import RoleBinding + return RoleBinding.get_user_joined_orgs(self) + @lazyproperty def cached_orgs(self): from rbac.models import RoleBinding @@ -811,9 +845,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract default=False, verbose_name=_('Need update password') ) date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) - wecom_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('WeCom')) - dingtalk_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('DingTalk')) - feishu_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('FeiShu')) + wecom_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('WeCom')) + dingtalk_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('DingTalk')) + feishu_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('FeiShu')) DATE_EXPIRED_WARNING_DAYS = 5 @@ -945,6 +979,11 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract class Meta: ordering = ['username'] verbose_name = _("User") + unique_together = ( + ('dingtalk_id',), + ('wecom_id',), + ('feishu_id',), + ) permissions = [ ('invite_user', _('Can invite user')), ('remove_user', _('Can remove user')), diff --git a/apps/users/notifications.py b/apps/users/notifications.py index cbc6e1976..95af2386e 100644 --- a/apps/users/notifications.py +++ b/apps/users/notifications.py @@ -1,10 +1,10 @@ -from urllib.parse import urljoin from collections import defaultdict +from urllib.parse import urljoin -from django.utils import timezone -from django.utils.translation import ugettext as _ from django.conf import settings from django.template.loader import render_to_string +from django.utils import timezone +from django.utils.translation import gettext as _ from common.utils import reverse, get_request_ip_or_data, get_request_user_agent from notifications.notifications import UserMessage diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index dc61eee21..af4e349e0 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models import Count -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from common.serializers.mixin import ObjectRelatedField from orgs.mixins.serializers import BulkOrgResourceModelSerializer diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index 7502a7525..f03bb133b 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers.fields import EncryptedField, LabeledChoiceField diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 099bddd1d..ea7568622 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -3,7 +3,7 @@ from functools import partial -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.serializers import CommonBulkSerializerMixin @@ -169,6 +169,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer "is_active": {"label": _("Is active")}, "is_valid": {"label": _("Is valid")}, "is_service_account": {"label": _("Is service account")}, + "is_org_admin": {"label": _("Is org admin")}, "is_expired": {"label": _("Is expired")}, "avatar_url": {"label": _("Avatar url")}, "created_by": {"read_only": True, "allow_blank": True}, diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py index 2b2054ea3..7db379d4a 100644 --- a/apps/users/signal_handlers.py +++ b/apps/users/signal_handlers.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django_auth_ldap.backend import populate_user from django_cas_ng.signals import cas_user_authenticated diff --git a/apps/users/signals.py b/apps/users/signals.py index 739379d93..d85139c62 100644 --- a/apps/users/signals.py +++ b/apps/users/signals.py @@ -1,7 +1,6 @@ from django.dispatch import Signal - -post_user_create = Signal(providing_args=('user',)) -post_user_change_password = Signal(providing_args=('user',)) -pre_user_leave_org = Signal(providing_args=('user', 'org')) -post_user_leave_org = Signal(providing_args=('user', 'org')) +post_user_create = Signal() +post_user_change_password = Signal() +pre_user_leave_org = Signal() +post_user_leave_org = Signal() diff --git a/apps/users/tasks.py b/apps/users/tasks.py index bc146bf12..c363940fe 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -2,15 +2,18 @@ # from celery import shared_task +from django.conf import settings +from django.db.models import Max from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from audits.models import UserLoginLog from common.const.crontab import CRONTAB_AT_AM_TEN, CRONTAB_AT_PM_TWO from common.utils import get_logger -from ops.celery.decorator import after_app_ready_start -from ops.celery.utils import ( - create_or_update_celery_periodic_tasks -) +from common.utils.timezone import utc_now +from ops.celery.decorator import after_app_ready_start, register_as_period_task +from ops.celery.utils import create_or_update_celery_periodic_tasks +from orgs.utils import tmp_to_root_org from users.notifications import PasswordExpirationReminderMsg from users.notifications import UserExpirationReminderMsg from .models import User @@ -75,3 +78,23 @@ def check_user_expired_periodic(): } } create_or_update_celery_periodic_tasks(tasks) + + +@shared_task(verbose_name=_('Check unused users')) +@register_as_period_task(crontab=CRONTAB_AT_PM_TWO) +@tmp_to_root_org() +def check_unused_users(): + now = utc_now() + unused_usernames = [] + usernames_max_datetime = UserLoginLog.objects.values('username').annotate(max_datetime=Max('datetime')) + for i in usernames_max_datetime: + username = i['username'] + max_datetime = i['max_datetime'] + uncommon_users_ttl = settings.SECURITY_UNCOMMON_USERS_TTL + if (now - max_datetime).seconds > uncommon_users_ttl * 24 * 60 * 60: + unused_usernames.append(username) + + if not unused_usernames: + return + + User.objects.filter(username__in=unused_usernames).update(is_active=False) diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index 5b4333d37..a42a7a513 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -1,21 +1,19 @@ # ~*~ coding: utf-8 ~*~ -import time +from django.contrib.auth import logout as auth_logout +from django.http.response import HttpResponseRedirect +from django.shortcuts import redirect from django.urls import reverse -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.base import TemplateView from django.views.generic.edit import FormView -from django.contrib.auth import logout as auth_logout -from django.shortcuts import redirect -from django.http.response import HttpResponseRedirect -from authentication.mixins import AuthMixin -from authentication.mfa import MFAOtp, otp_failed_msg from authentication.errors import SessionEmptyError +from authentication.mfa import MFAOtp, otp_failed_msg +from authentication.mixins import AuthMixin +from common.permissions import IsValidUser from common.utils import get_logger, FlashMessageUtil from common.views.mixins import PermissionsMixin -from common.permissions import IsValidUser -from .password import UserVerifyPasswordView from ... import forms from ...utils import ( generate_otp_uri, check_otp_code, diff --git a/apps/users/views/profile/password.py b/apps/users/views/profile/password.py index bb51b8aa4..87d0bf6e7 100644 --- a/apps/users/views/profile/password.py +++ b/apps/users/views/profile/password.py @@ -1,15 +1,12 @@ # ~*~ coding: utf-8 ~*~ -import time -from django.conf import settings from django.contrib.auth import authenticate from django.shortcuts import redirect -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.edit import FormView -from authentication.mixins import AuthMixin from authentication import errors - +from authentication.mixins import AuthMixin from common.utils import get_logger from ... import forms from ...utils import ( diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index 2972225ef..b8e18a647 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -2,16 +2,16 @@ from __future__ import unicode_literals -from common.utils import FlashMessageUtil, get_object_or_none, random_string -from common.utils.verify_code import SendAndVerifyCodeUtil from django.conf import settings from django.core.cache import cache from django.shortcuts import redirect, reverse from django.urls import reverse_lazy -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic import FormView, RedirectView -from users.notifications import ResetPasswordSuccessMsg +from common.utils import FlashMessageUtil, get_object_or_none, random_string +from common.utils.verify_code import SendAndVerifyCodeUtil +from users.notifications import ResetPasswordSuccessMsg from ... import forms from ...models import User from ...utils import check_password_rules, get_password_check_rules diff --git a/config_example.yml b/config_example.yml index a21edb01b..03cc0aa03 100644 --- a/config_example.yml +++ b/config_example.yml @@ -96,3 +96,6 @@ REDIS_PORT: 6379 # 仅允许已存在的用户登录,不允许第三方认证后,自动创建用户 # ONLY_ALLOW_EXIST_USER_AUTH: False +# 开启 Vault 账号存储 +# VAULT_ENABLED: False + diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..7a3397d29 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,7226 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "adal" +version = "1.2.7" +description = "Note: This library is already replaced by MSAL Python, available here: https://pypi.org/project/msal/ .ADAL Python remains available here as a legacy. The ADAL for Python library makes it easy for python application to authenticate to Azure Active Directory (AAD) in order to access AAD protected web resources." +optional = false +python-versions = "*" +files = [ + {file = "adal-1.2.7-py2.py3-none-any.whl", hash = "sha256:2a7451ed7441ddbc57703042204a3e30ef747478eea022c70f789fc7f084bc3d"}, + {file = "adal-1.2.7.tar.gz", hash = "sha256:d74f45b81317454d96e982fd1c50e6fb5c99ac2223728aea8764433a39f566f1"}, +] + +[package.dependencies] +cryptography = ">=1.1.0" +PyJWT = ">=1.0.0,<3" +python-dateutil = ">=2.1.0,<3" +requests = ">=2.0.0,<3" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aiofiles" +version = "23.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"}, + {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-credentials" +version = "0.3.2" +description = "The alibabacloud credentials module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_credentials-0.3.2.tar.gz", hash = "sha256:65f0e1d5fed1b0751dc56b9dc565144e528803185193e4771a21ef9d9d138d59"}, +] + +[package.dependencies] +alibabacloud-tea = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-dysmsapi20170525" +version = "2.0.24" +description = "Alibaba Cloud Dysmsapi (20170525) SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_dysmsapi20170525-2.0.24-py3-none-any.whl", hash = "sha256:f65cad7e506a4408d51fe1dcfcc09cefb5b2a6dbcedaa3fd7a4d87ab72696bad"}, + {file = "alibabacloud_dysmsapi20170525-2.0.24.tar.gz", hash = "sha256:6223dfb9254669c761e573339cd01df80a9c6ca5b717291fc01e4a151a347570"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.3,<1.0.0" +alibabacloud-openapi-util = ">=0.2.1,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.6,<1.0.0" +alibabacloud-tea-util = ">=0.3.9,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-endpoint-util" +version = "0.0.3" +description = "The endpoint-util module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_endpoint_util-0.0.3.tar.gz", hash = "sha256:8c0efb76fdcc3af4ca716ef24bbce770201a3f83f98c0afcf81655f684b9c7d2"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.0.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-gateway-spi" +version = "0.0.1" +description = "Alibaba Cloud Gateway SPI SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_gateway_spi-0.0.1.tar.gz", hash = "sha256:1b259855708afc3c04d8711d8530c63f7645e1edc0cf97e2fd15461b08e11c30"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.2.0,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-openapi-util" +version = "0.2.1" +description = "Aliyun Tea OpenApi Library for Python" +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_openapi_util-0.2.1.tar.gz", hash = "sha256:5de2158a6e894b3364d9627090afdb6de4f6b92d1adf3a00e2c69206df30de15"}, +] + +[package.dependencies] +alibabacloud_tea_util = ">=0.0.2" +cryptography = ">=3.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-tea" +version = "0.3.3" +description = "The tea module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud-tea-0.3.3.tar.gz", hash = "sha256:90f1b3310552f7cb004f713e2b55d129acd4a885bd79342df6d621422de23775"}, +] + +[package.dependencies] +aiohttp = ">=3.7.0,<4.0.0" +requests = ">=2.21.0,<3.0.0" +urllib3 = "<2.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-tea-openapi" +version = "0.3.7" +description = "Alibaba Cloud openapi SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_tea_openapi-0.3.7.tar.gz", hash = "sha256:bf677f3ddd35eeb499f08c0a38a024379c47dbcc0ba7a777a2ef7562abbe9c9f"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.3.1,<1.0.0" +alibabacloud_gateway_spi = ">=0.0.1,<1.0.0" +alibabacloud_openapi_util = ">=0.2.1,<1.0.0" +alibabacloud_tea_util = ">=0.3.8,<1.0.0" +alibabacloud_tea_xml = ">=0.0.2,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-tea-util" +version = "0.3.11" +description = "The tea-util module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_tea_util-0.3.11.tar.gz", hash = "sha256:f68231e36ccd788ccf3b8d72d67bf76a0fa8f5b7a5f01489484a1f133b448f71"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.3.3" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "alibabacloud-tea-xml" +version = "0.0.2" +description = "The tea-xml module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_tea_xml-0.0.2.tar.gz", hash = "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.0.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aliyun-python-sdk-core" +version = "2.13.36" +description = "The core module of Aliyun Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "aliyun-python-sdk-core-2.13.36.tar.gz", hash = "sha256:20bd54984fa316da700c7f355a51ab0b816690e2a0fcefb7b5ef013fed0da928"}, +] + +[package.dependencies] +cryptography = ">=2.6.0" +jmespath = ">=0.9.3,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aliyun-python-sdk-core-v3" +version = "2.13.33" +description = "The core module of Aliyun Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "aliyun-python-sdk-core-v3-2.13.33.tar.gz", hash = "sha256:d7df820fa31193be3f0a3a991c4126051900b3d2f09c0fc5ff7af43cf36ac245"}, +] + +[package.dependencies] +cryptography = ">=2.6.0" +jmespath = ">=0.9.3,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aliyun-python-sdk-ecs" +version = "4.24.64" +description = "The ecs module of Aliyun Python sdk." +optional = false +python-versions = "*" +files = [ + {file = "aliyun-python-sdk-ecs-4.24.64.tar.gz", hash = "sha256:9ae49bfd6f79aa295117d22f8f3b0a1a62eeff0c2df2026a7c7b6a8299e411be"}, + {file = "aliyun_python_sdk_ecs-4.24.64-py2.py3-none-any.whl", hash = "sha256:c0f366f06bdf45e359f22d7e1864439687e00bd9de6d59d29dbd30d0019b9347"}, +] + +[package.dependencies] +aliyun-python-sdk-core = ">=2.11.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "aliyun-python-sdk-kms" +version = "2.16.1" +description = "The kms module of Aliyun Python sdk." +optional = false +python-versions = "*" +files = [ + {file = "aliyun-python-sdk-kms-2.16.1.tar.gz", hash = "sha256:a372737715682014bace68bd40fe83332f4fd925009a3eb110d41bc66f270e7a"}, + {file = "aliyun_python_sdk_kms-2.16.1-py2.py3-none-any.whl", hash = "sha256:9bc39c693ba83944f5dfb871b118a2925eb8a5ee214dfcce61ee2ea3b6317ef1"}, +] + +[package.dependencies] +aliyun-python-sdk-core = ">=2.11.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "amqp" +version = "5.1.1" +description = "Low-level AMQP client for Python (fork of amqplib)." +optional = false +python-versions = ">=3.6" +files = [ + {file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, + {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, +] + +[package.dependencies] +vine = ">=5.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ansible" +version = "7.1.0" +description = "Radically simple IT automation" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ansible-7.1.0-py3-none-any.whl", hash = "sha256:dd6d24dab08793e416eb1564ea716c586caeb104d4abbcada05bed94f9cd01cc"}, + {file = "ansible-7.1.0.tar.gz", hash = "sha256:1e47238c4aa9e68c0c5367a3fd707ba6c3949b4aaf912b06440ad78dd2bf018d"}, +] + +[package.dependencies] +ansible-core = ">=2.14.1,<2.15.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ansible-core" +version = "2.14.1" +description = "Radically simple IT automation" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ansible-2.14.1.2.zip", hash = "sha256:813b39d8e03d5ea23b47703b3ba4d0372f68141b33e2b1be28deb6ad28b31c73"}, +] + +[package.dependencies] +cryptography = "*" +jinja2 = ">=3.0.0" +packaging = "*" +PyYAML = ">=5.1" +resolvelib = ">=0.5.3,<0.9.0" + +[package.source] +type = "url" +url = "https://github.com/jumpserver/ansible/releases/download/v2.14.1.2/ansible-2.14.1.2.zip" + +[[package]] +name = "ansible-runner" +version = "2.3.3" +description = "\"Consistent Ansible Python API and CLI with container and process isolation runtime capabilities\"" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ansible-runner-2.3.3.tar.gz", hash = "sha256:38ff635e4b94791de2956c81e265836ec4965b30e9ee35d72fcf3271dc46b98b"}, + {file = "ansible_runner-2.3.3-py3-none-any.whl", hash = "sha256:c57ae0d096760d66b2897b0f9009856c7b83fd5428dcb831f470cba348346396"}, +] + +[package.dependencies] +packaging = "*" +pexpect = ">=4.5" +python-daemon = "*" +pyyaml = "*" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +optional = false +python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "autobahn" +version = "23.6.2" +description = "WebSocket client & server library, WAMP real-time framework" +optional = false +python-versions = ">=3.9" +files = [ + {file = "autobahn-23.6.2.tar.gz", hash = "sha256:ec9421c52a2103364d1ef0468036e6019ee84f71721e86b36fe19ad6966c1181"}, +] + +[package.dependencies] +cryptography = ">=3.4.6" +hyperlink = ">=21.0.0" +setuptools = "*" +txaio = ">=21.2.1" + +[package.extras] +all = ["PyGObject (>=3.40.0)", "argon2_cffi (>=20.1.0)", "attrs (>=20.3.0)", "base58 (>=2.1.0)", "bitarray (>=2.7.5)", "cbor2 (>=5.2.0)", "cffi (>=1.14.5)", "click (>=8.1.2)", "ecdsa (>=0.16.1)", "eth-abi (>=4.0.0)", "flatbuffers (>=22.12.6)", "hkdf (>=0.0.3)", "jinja2 (>=2.11.3)", "mnemonic (>=0.19)", "msgpack (>=1.0.2)", "passlib (>=1.7.4)", "py-ecc (>=5.1.0)", "py-eth-sig-utils (>=0.4.0)", "py-multihash (>=2.0.1)", "py-ubjson (>=0.16.1)", "pynacl (>=1.4.0)", "pyopenssl (>=20.0.1)", "python-snappy (>=0.6.0)", "pytrie (>=0.4.0)", "qrcode (>=7.3.1)", "rlp (>=2.0.1)", "service_identity (>=18.1.0)", "spake2 (>=0.8)", "twisted (>=20.3.0)", "ujson (>=4.0.2)", "web3[ipfs] (>=6.0.0)", "xbr (>=21.2.1)", "yapf (==0.29.0)", "zlmdb (>=21.2.1)", "zope.interface (>=5.2.0)"] +compress = ["python-snappy (>=0.6.0)"] +dev = ["backports.tempfile (>=1.0)", "bumpversion (>=0.5.3)", "codecov (>=2.0.15)", "flake8 (<5)", "humanize (>=0.5.1)", "mypy (>=0.610)", "passlib", "pep8-naming (>=0.3.3)", "pip (>=9.0.1)", "pyenchant (>=1.6.6)", "pyflakes (>=1.0.0)", "pyinstaller (>=4.2)", "pylint (>=1.9.2)", "pytest (>=3.4.2)", "pytest-aiohttp", "pytest-asyncio (>=0.14.0)", "pytest-runner (>=2.11.1)", "pyyaml (>=4.2b4)", "qualname", "sphinx (>=1.7.1)", "sphinx-autoapi (>=1.7.0)", "sphinx_rtd_theme (>=0.1.9)", "sphinxcontrib-images (>=0.9.1)", "tox (>=4.2.8)", "tox-gh-actions (>=2.2.0)", "twine (>=3.3.0)", "twisted (>=22.10.0)", "txaio (>=20.4.1)", "watchdog (>=0.8.3)", "wheel (>=0.36.2)", "yapf (==0.29.0)"] +encryption = ["pynacl (>=1.4.0)", "pyopenssl (>=20.0.1)", "pytrie (>=0.4.0)", "qrcode (>=7.3.1)", "service_identity (>=18.1.0)"] +nvx = ["cffi (>=1.14.5)"] +scram = ["argon2_cffi (>=20.1.0)", "cffi (>=1.14.5)", "passlib (>=1.7.4)"] +serialization = ["cbor2 (>=5.2.0)", "flatbuffers (>=22.12.6)", "msgpack (>=1.0.2)", "py-ubjson (>=0.16.1)", "ujson (>=4.0.2)"] +twisted = ["attrs (>=20.3.0)", "twisted (>=20.3.0)", "zope.interface (>=5.2.0)"] +ui = ["PyGObject (>=3.40.0)"] +xbr = ["base58 (>=2.1.0)", "bitarray (>=2.7.5)", "cbor2 (>=5.2.0)", "click (>=8.1.2)", "ecdsa (>=0.16.1)", "eth-abi (>=4.0.0)", "hkdf (>=0.0.3)", "jinja2 (>=2.11.3)", "mnemonic (>=0.19)", "py-ecc (>=5.1.0)", "py-eth-sig-utils (>=0.4.0)", "py-multihash (>=2.0.1)", "rlp (>=2.0.1)", "spake2 (>=0.8)", "twisted (>=20.3.0)", "web3[ipfs] (>=6.0.0)", "xbr (>=21.2.1)", "yapf (==0.29.0)", "zlmdb (>=21.2.1)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "automat" +version = "22.10.0" +description = "Self-service finite-state machines for the programmer on the go." +optional = false +python-versions = "*" +files = [ + {file = "Automat-22.10.0-py2.py3-none-any.whl", hash = "sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180"}, + {file = "Automat-22.10.0.tar.gz", hash = "sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +six = "*" + +[package.extras] +visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-common" +version = "1.1.28" +description = "Microsoft Azure Client Library for Python (Common)" +optional = false +python-versions = "*" +files = [ + {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, + {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-core" +version = "1.29.2" +description = "Microsoft Azure Core Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-core-1.29.2.zip", hash = "sha256:beb0fe88d1043d8457318e8fb841d9caa648211092eda213c16b376401f3710d"}, + {file = "azure_core-1.29.2-py3-none-any.whl", hash = "sha256:8e6602f322dc1070caf7e17754beb53b69ffa09df0f4786009a3107e9a00c793"}, +] + +[package.dependencies] +requests = ">=2.18.4" +six = ">=1.11.0" +typing-extensions = ">=4.6.0" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-identity" +version = "1.13.0" +description = "Microsoft Azure Identity Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-identity-1.13.0.zip", hash = "sha256:c931c27301ffa86b07b4dcf574e29da73e3deba9ab5d1fe4f445bb6a3117e260"}, + {file = "azure_identity-1.13.0-py3-none-any.whl", hash = "sha256:bd700cebb80cd9862098587c29d8677e819beca33c62568ced6d5a8e5e332b82"}, +] + +[package.dependencies] +azure-core = ">=1.11.0,<2.0.0" +cryptography = ">=2.5" +msal = ">=1.20.0,<2.0.0" +msal-extensions = ">=0.3.0,<2.0.0" +six = ">=1.12.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-mgmt-compute" +version = "30.0.0" +description = "Microsoft Azure Compute Management Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-compute-30.0.0.zip", hash = "sha256:7320fbafc392770b03ed02b68760185e6094552ed98d7fd5ea77210a5a513c78"}, + {file = "azure_mgmt_compute-30.0.0-py3-none-any.whl", hash = "sha256:004fc790f6ed70cd17a45a3c3ebc0665391c96f101ee80eb8738330a53f0d959"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-mgmt-core" +version = "1.4.0" +description = "Microsoft Azure Management Core Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-core-1.4.0.zip", hash = "sha256:d195208340094f98e5a6661b781cde6f6a051e79ce317caabd8ff97030a9b3ae"}, + {file = "azure_mgmt_core-1.4.0-py3-none-any.whl", hash = "sha256:81071675f186a585555ef01816f2774d49c1c9024cb76e5720c3c0f6b337bb7d"}, +] + +[package.dependencies] +azure-core = ">=1.26.2,<2.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-mgmt-network" +version = "23.1.0" +description = "Microsoft Azure Network Management Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-network-23.1.0.zip", hash = "sha256:37172699113831d7ce1bb9da35c635007c73cffc549a85a58f55542b54b12bf3"}, + {file = "azure_mgmt_network-23.1.0-py3-none-any.whl", hash = "sha256:83afb63fdf7a526a4b2ee32e0a6038e9d05d4182bcaac1e0190a59152165648d"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-mgmt-subscription" +version = "3.1.1" +description = "Microsoft Azure Subscription Management Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-subscription-3.1.1.zip", hash = "sha256:4e255b4ce9b924357bb8c5009b3c88a2014d3203b2495e2256fa027bf84e800e"}, + {file = "azure_mgmt_subscription-3.1.1-py3-none-any.whl", hash = "sha256:38d4574a8d47fa17e3587d756e296cb63b82ad8fb21cd8543bcee443a502bf48"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +msrest = ">=0.7.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "azure-storage-blob" +version = "12.17.0" +description = "Microsoft Azure Blob Storage Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-storage-blob-12.17.0.zip", hash = "sha256:c14b785a17050b30fc326a315bdae6bc4a078855f4f94a4c303ad74a48dc8c63"}, + {file = "azure_storage_blob-12.17.0-py3-none-any.whl", hash = "sha256:0016e0c549a80282d7b4920c03f2f4ba35c53e6e3c7dbcd2a4a8c8eb3882c1e7"}, +] + +[package.dependencies] +azure-core = ">=1.28.0,<2.0.0" +cryptography = ">=2.1.4" +isodate = ">=0.6.1" +typing-extensions = ">=4.3.0" + +[package.extras] +aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "bce-python-sdk" +version = "0.8.87" +description = "BCE SDK for python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" +files = [ + {file = "bce-python-sdk-0.8.87.tar.gz", hash = "sha256:be04dfd47883fd1ff59b6e33abfbc1f83b762b369c7f078fadf7b5ab48c1d2a9"}, + {file = "bce_python_sdk-0.8.87-py3-none-any.whl", hash = "sha256:75cbf523c2a106026b0f3c57a5d5c7a0853edb0900a52d585ea0561179972aef"}, +] + +[package.dependencies] +future = ">=0.6.0" +pycryptodome = ">=3.8.0" +six = ">=1.4.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "bcrypt" +version = "4.0.1" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.6" +files = [ + {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, + {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, + {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, + {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "billiard" +version = "4.1.0" +description = "Python multiprocessing fork with improvements and bugfixes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"}, + {file = "billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "boto" +version = "2.49.0" +description = "Amazon Web Services Library" +optional = false +python-versions = "*" +files = [ + {file = "boto-2.49.0-py2.py3-none-any.whl", hash = "sha256:147758d41ae7240dc989f0039f27da8ca0d53734be0eb869ef16e3adcfa462e8"}, + {file = "boto-2.49.0.tar.gz", hash = "sha256:ea0d3b40a2d852767be77ca343b58a9e3a4b00d9db440efb8da74b4e58025e5a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "boto3" +version = "1.28.9" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.28.9-py3-none-any.whl", hash = "sha256:01f078047eb4d238c6b9c6cc623f2af33b4ae67980c5326691e35cb5493ff6c7"}, + {file = "boto3-1.28.9.tar.gz", hash = "sha256:4cc0c6005be910e52077227e670930ab55a41ba86cdb6d1c052571d08cd4d32c"}, +] + +[package.dependencies] +botocore = ">=1.31.9,<1.32.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "botocore" +version = "1.31.9" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.31.9-py3-none-any.whl", hash = "sha256:e56ccd3536a90094ea5b176b5dd33bfe4f049efdf71af468ea1661bd424c787d"}, + {file = "botocore-1.31.9.tar.gz", hash = "sha256:bd849d3ac95f1781385ed831d753a04a3ec870a59d6598175aaedd71dc2baf5f"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.16.26)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "cachetools" +version = "5.3.1" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "celery" +version = "5.3.1" +description = "Distributed Task Queue." +optional = false +python-versions = ">=3.8" +files = [ + {file = "celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {file = "celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] + +[package.dependencies] +billiard = ">=4.1.0,<5.0" +click = ">=8.1.2,<9.0" +click-didyoumean = ">=0.3.0" +click-plugins = ">=1.1.1" +click-repl = ">=0.2.0" +kombu = ">=5.3.1,<6.0" +python-dateutil = ">=2.8.2" +tzdata = ">=2022.7" +vine = ">=5.0.0,<6.0" + +[package.extras] +arangodb = ["pyArango (>=2.0.1)"] +auth = ["cryptography (==41.0.1)"] +azureblockblob = ["azure-storage-blob (>=12.15.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (>=3.25.0,<4)"] +consul = ["python-consul2 (==0.1.5)"] +cosmosdbsql = ["pydocumentdb (==2.3.5)"] +couchbase = ["couchbase (>=3.0.0)"] +couchdb = ["pycouchdb (==1.14.2)"] +django = ["Django (>=2.2.28)"] +dynamodb = ["boto3 (>=1.26.143)"] +elasticsearch = ["elasticsearch (<8.0)"] +eventlet = ["eventlet (>=0.32.0)"] +gevent = ["gevent (>=1.5.0)"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +memcache = ["pylibmc (==1.6.3)"] +mongodb = ["pymongo[srv] (>=4.0.2)"] +msgpack = ["msgpack (==1.0.5)"] +pymemcache = ["python-memcached (==1.59)"] +pyro = ["pyro4 (==4.82)"] +pytest = ["pytest-celery (==0.0.0)"] +redis = ["redis (>=4.5.2,!=4.5.5)"] +s3 = ["boto3 (>=1.26.143)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem (==4.1.4)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard (==0.21.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "channels" +version = "4.0.0" +description = "Brings async, event-driven capabilities to Django 3.2 and up." +optional = false +python-versions = ">=3.7" +files = [ + {file = "channels-4.0.0-py3-none-any.whl", hash = "sha256:2253334ac76f67cba68c2072273f7e0e67dbdac77eeb7e318f511d2f9a53c5e4"}, + {file = "channels-4.0.0.tar.gz", hash = "sha256:0ce53507a7da7b148eaa454526e0e05f7da5e5d1c23440e4886cf146981d8420"}, +] + +[package.dependencies] +asgiref = ">=3.5.0,<4" +Django = ">=3.2" + +[package.extras] +daphne = ["daphne (>=4.0.0)"] +tests = ["async-timeout", "coverage (>=4.5,<5.0)", "pytest", "pytest-asyncio", "pytest-django"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "channels-redis" +version = "4.1.0" +description = "Redis-backed ASGI channel layer implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "channels_redis-4.1.0-py3-none-any.whl", hash = "sha256:3696f5b9fe367ea495d402ba83d7c3c99e8ca0e1354ff8d913535976ed0abf73"}, + {file = "channels_redis-4.1.0.tar.gz", hash = "sha256:6bd4f75f4ab4a7db17cee495593ace886d7e914c66f8214a1f247ff6659c073a"}, +] + +[package.dependencies] +asgiref = ">=3.2.10,<4" +channels = "*" +msgpack = ">=1.0,<2.0" +redis = ">=4.5.3" + +[package.extras] +cryptography = ["cryptography (>=1.3.0)"] +tests = ["async-timeout", "cryptography (>=1.3.0)", "pytest", "pytest-asyncio", "pytest-timeout"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "chardet" +version = "5.1.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, + {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +description = "Enables git-like *did-you-mean* feature in click" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[package.dependencies] +click = ">=7" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "click-repl" +version = "0.3.0" +description = "REPL plugin for Click" +optional = false +python-versions = ">=3.6" +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + +[package.dependencies] +click = ">=7.0" +prompt-toolkit = ">=3.0.36" + +[package.extras] +testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "configparser" +version = "6.0.0" +description = "Updated configparser from stdlib for earlier Pythons." +optional = false +python-versions = ">=3.8" +files = [ + {file = "configparser-6.0.0-py3-none-any.whl", hash = "sha256:900ea2bb01b2540b1a644ad3d5351e9b961a4a012d4732f619375fb8f641ee19"}, + {file = "configparser-6.0.0.tar.gz", hash = "sha256:ec914ab1e56c672de1f5c3483964e68f71b34e457904b7b76e06b922aec067a8"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "types-backports"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "constantly" +version = "15.1.0" +description = "Symbolic constants in Python" +optional = false +python-versions = "*" +files = [ + {file = "constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"}, + {file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "coreapi" +version = "2.3.3" +description = "Python client library for Core API." +optional = false +python-versions = "*" +files = [ + {file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"}, + {file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"}, +] + +[package.dependencies] +coreschema = "*" +itypes = "*" +requests = "*" +uritemplate = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "coreschema" +version = "0.0.4" +description = "Core Schema." +optional = false +python-versions = "*" +files = [ + {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"}, + {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"}, +] + +[package.dependencies] +jinja2 = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "crcmod" +version = "1.7" +description = "CRC Generator" +optional = false +python-versions = "*" +files = [ + {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "cron-descriptor" +version = "1.4.0" +description = "A Python library that converts cron expressions into human readable strings." +optional = false +python-versions = "*" +files = [ + {file = "cron_descriptor-1.4.0.tar.gz", hash = "sha256:b6ff4e3a988d7ca04a4ab150248e9f166fb7a5c828a85090e75bcc25aa93b4dd"}, +] + +[package.extras] +dev = ["polib"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "cython" +version = "3.0.0" +description = "The Cython compiler for writing C extensions in the Python language." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Cython-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c7d728e1a49ad01d41181e3a9ea80b8d14e825f4679e4dd837cbf7bca7998a5"}, + {file = "Cython-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:626a4a6ef4b7ced87c348ea805488e4bd39dad9d0b39659aa9e1040b62bbfedf"}, + {file = "Cython-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33c900d1ca9f622b969ac7d8fc44bdae140a4a6c7d8819413b51f3ccd0586a09"}, + {file = "Cython-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a65bc50dc1bc2faeafd9425defbdef6a468974f5c4192497ff7f14adccfdcd32"}, + {file = "Cython-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b71b399b10b038b056ad12dce1e317a8aa7a96e99de7e4fa2fa5d1c9415cfb9"}, + {file = "Cython-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f42f304c097cc53e9eb5f1a1d150380353d5018a3191f1b77f0de353c762181e"}, + {file = "Cython-3.0.0-cp310-cp310-win32.whl", hash = "sha256:3e234e2549e808d9259fdb23ebcfd145be30c638c65118326ec33a8d29248dc2"}, + {file = "Cython-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:829c8333195100448a23863cf64a07e1334fae6a275aefe871458937911531b6"}, + {file = "Cython-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06db81b1a01858fcc406616f8528e686ffb6cf7c3d78fb83767832bfecea8ad8"}, + {file = "Cython-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c93634845238645ce7abf63a56b1c5b6248189005c7caff898fd4a0dac1c5e1e"}, + {file = "Cython-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa606675c6bd23478b1d174e2a84e3c5a2c660968f97dc455afe0fae198f9d3d"}, + {file = "Cython-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3355e6f690184f984eeb108b0f5bbc4bcf8b9444f8168933acf79603abf7baf"}, + {file = "Cython-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93a34e1ca8afa4b7075b02ed14a7e4969256297029fb1bfd4cbe48f7290dbcff"}, + {file = "Cython-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb1165ca9e78823f9ad1efa5b3d83156f868eabd679a615d140a3021bb92cd65"}, + {file = "Cython-3.0.0-cp311-cp311-win32.whl", hash = "sha256:2fadde1da055944f5e1e17625055f54ddd11f451889110278ef30e07bd5e1695"}, + {file = "Cython-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:254ed1f03a6c237fa64f0c6e44862058de65bfa2e6a3b48ca3c205492e0653aa"}, + {file = "Cython-3.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e212237b7531759befb92699c452cd65074a78051ae4ee36ff8b237395ecf3d"}, + {file = "Cython-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f29307463eba53747b31f71394ed087e3e3e264dcc433e62de1d51f5c0c966c"}, + {file = "Cython-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53328a8af0806bebbdb48a4191883b11ee9d9dfb084d84f58fa5a8ab58baefc9"}, + {file = "Cython-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5962e70b15e863e72bed6910e8c6ffef77d36cc98e2b31c474378f3b9e49b0e3"}, + {file = "Cython-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9e69139f4e60ab14c50767a568612ea64d6907e9c8e0289590a170eb495e005f"}, + {file = "Cython-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c40bdbcb2286f0aeeb5df9ce53d45da2d2a9b36a16b331cd0809d212d22a8fc7"}, + {file = "Cython-3.0.0-cp312-cp312-win32.whl", hash = "sha256:8abb8915eb2e57fa53d918afe641c05d1bcc6ed1913682ec1f28de71f4e3f398"}, + {file = "Cython-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:30a4bd2481e59bd7ab2539f835b78edc19fc455811e476916f56026b93afd28b"}, + {file = "Cython-3.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0e1e4b7e4bfbf22fecfa5b852f0e499c442d4853b7ebd33ae37cdec9826ed5d8"}, + {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b00df42cdd1a285a64491ba23de08ab14169d3257c840428d40eb7e8e9979af"}, + {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:650d03ddddc08b051b4659778733f0f173ca7d327415755c05d265a6c1ba02fb"}, + {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4965f2ebade17166f21a508d66dd60d2a0b3a3b90abe3f72003baa17ae020dd6"}, + {file = "Cython-3.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4123c8d03167803df31da6b39de167cb9c04ac0aa4e35d4e5aa9d08ad511b84d"}, + {file = "Cython-3.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:296c53b6c0030cf82987eef163444e8d7631cc139d995f9d58679d9fd1ddbf31"}, + {file = "Cython-3.0.0-cp36-cp36m-win32.whl", hash = "sha256:0d2c1e172f1c81bafcca703093608e10dc16e3e2d24c5644c17606c7fdb1792c"}, + {file = "Cython-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bc816d8eb3686d6f8d165f4156bac18c1147e1035dc28a76742d0b7fb5b7c032"}, + {file = "Cython-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8d86651347bbdbac1aca1824696c5e4c0a3b162946c422edcca2be12a03744d1"}, + {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84176bd04ce9f3cc8799b47ec6d1959fa1ea5e71424507df7bbf0b0915bbedef"}, + {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35abcf07b8277ec95bbe49a07b5c8760a2d941942ccfe759a94c8d2fe5602e9f"}, + {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a44d6b9a29b2bff38bb648577b2fcf6a68cf8b1783eee89c2eb749f69494b98d"}, + {file = "Cython-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4dc6bbe7cf079db37f1ebb9b0f10d0d7f29e293bb8688e92d50b5ea7a91d82f3"}, + {file = "Cython-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e28763e75e380b8be62b02266a7995a781997c97c119efbdccb8fb954bcd7574"}, + {file = "Cython-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:edae615cb4af51d5173e76ba9aea212424d025c57012e9cdf2f131f774c5ba71"}, + {file = "Cython-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:20c604e974832aaf8b7a1f5455ee7274b34df62a35ee095cd7d2ed7e818e6c53"}, + {file = "Cython-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c85fd2b1cbd9400d60ebe074795bb9a9188752f1612be3b35b0831a24879b91f"}, + {file = "Cython-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090256c687106932339f87f888b95f0d69c617bc9b18801555545b695d29d8ab"}, + {file = "Cython-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec2a67a0a7d9d4399758c0657ca03e5912e37218859cfbf046242cc532bfb3b"}, + {file = "Cython-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1cdd01ce45333bc264a218c6e183700d6b998f029233f586a53c9b13455c2d2"}, + {file = "Cython-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecee663d2d50ca939fc5db81f2f8a219c2417b4651ad84254c50a03a9cb1aadd"}, + {file = "Cython-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30f10e79393b411af7677c270ea69807acb9fc30205c8ff25561f4deef780ec1"}, + {file = "Cython-3.0.0-cp38-cp38-win32.whl", hash = "sha256:609777d3a7a0a23b225e84d967af4ad2485c8bdfcacef8037cf197e87d431ca0"}, + {file = "Cython-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f4a6dfd42ae0a45797f50fc4f6add702abf46ab3e7cd61811a6c6a97a40e1a2"}, + {file = "Cython-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2d8158277c8942c0b20ff4c074fe6a51c5b89e6ac60cef606818de8c92773596"}, + {file = "Cython-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e34f99b2a8c1e11478541b2822e6408c132b98b6b8f5ed89411e5e906631ea"}, + {file = "Cython-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877d1c8745df59dd2061a0636c602729e9533ba13f13aa73a498f68662e1cbde"}, + {file = "Cython-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204690be60f0ff32eb70b04f28ef0d1e50ffd7b3f77ba06a7dc2389ee3b848e0"}, + {file = "Cython-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06fcb4628ccce2ba5abc8630adbeaf4016f63a359b4c6c3827b2d80e0673981c"}, + {file = "Cython-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:090e24cfa31c926d0b13d8bb2ef48175acdd061ae1413343c94a2b12a4a4fa6f"}, + {file = "Cython-3.0.0-cp39-cp39-win32.whl", hash = "sha256:4cd00f2158dc00f7f93a92444d0f663eda124c9c29bbbd658964f4e89c357fe8"}, + {file = "Cython-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5b4cc896d49ce2bae8d6a030f9a4c64965b59c38acfbf4617685e17f7fcf1731"}, + {file = "Cython-3.0.0-py2.py3-none-any.whl", hash = "sha256:ff1aef1a03cfe293237c7a86ae9625b0411b2df30c53d1a7f29a8d381f38a1df"}, + {file = "Cython-3.0.0.tar.gz", hash = "sha256:350b18f9673e63101dbbfcf774ee2f57c20ac4636d255741d76ca79016b1bd82"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "daphne" +version = "4.0.0" +description = "Django ASGI (HTTP/WebSocket) server" +optional = false +python-versions = ">=3.7" +files = [ + {file = "daphne-4.0.0-py3-none-any.whl", hash = "sha256:a288ece46012b6b719c37150be67c69ebfca0793a8521bf821533bad983179b2"}, + {file = "daphne-4.0.0.tar.gz", hash = "sha256:cce9afc8f49a4f15d4270b8cfb0e0fe811b770a5cc795474e97e4da287497666"}, +] + +[package.dependencies] +asgiref = ">=3.5.2,<4" +autobahn = ">=22.4.2" +twisted = {version = ">=22.4", extras = ["tls"]} + +[package.extras] +tests = ["django", "hypothesis", "pytest", "pytest-asyncio"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "data-tree" +version = "0.0.1" +description = "" +optional = false +python-versions = "*" +files = [ + {file = "data_tree-0.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:904cc0545cb3c8bc3fb81928c754b10510c1ae8ca9229de0c5cc8817eb301113"}, + {file = "data_tree-0.0.1.tar.gz", hash = "sha256:06f2a18b372cf2451166d426591f6e6fc73a7aabcad97255d50927aa3c3d5a0e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "debtcollector" +version = "2.5.0" +description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner." +optional = false +python-versions = ">=3.6" +files = [ + {file = "debtcollector-2.5.0-py3-none-any.whl", hash = "sha256:1393a527d2c72f143ffa6a629e9c33face6642634eece475b48cab7b04ba61f3"}, + {file = "debtcollector-2.5.0.tar.gz", hash = "sha256:dc9d1ad3f745c43f4bbedbca30f9ffe8905a8c028c9926e61077847d5ea257ab"}, +] + +[package.dependencies] +wrapt = ">=1.7.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django" +version = "4.1.10" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Django-4.1.10-py3-none-any.whl", hash = "sha256:26d0260c2fb8121009e62ffc548b2398dea2522b6454208a852fb0ef264c206c"}, + {file = "Django-4.1.10.tar.gz", hash = "sha256:56343019a9fd839e2e5bf203daf45f25af79d5bffa4c71d56eae4f4404d82ade"}, +] + +[package.dependencies] +asgiref = ">=3.5.2,<4" +sqlparse = ">=0.2.2" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-auth-ldap" +version = "4.4.0" +description = "Django LDAP authentication backend" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-auth-ldap-4.4.0.tar.gz", hash = "sha256:797931ac5d55c4ca14c179d5892420cc2a98aa81e44323d595f5419c1b45c306"}, + {file = "django_auth_ldap-4.4.0-py3-none-any.whl", hash = "sha256:da85f315a05b1327575aa67075049fab8753ddc05521a6fc6195755ff9ab72fd"}, +] + +[package.dependencies] +Django = ">=3.2" +python-ldap = ">=3.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-bootstrap3" +version = "23.4" +description = "Bootstrap 3 for Django" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django_bootstrap3-23.4-py3-none-any.whl", hash = "sha256:fc54b9afc6e0d33b9e2ac039dd022996ee95fc19bdf8320f4fd01ec611143ee3"}, + {file = "django_bootstrap3-23.4.tar.gz", hash = "sha256:975e6017bb25b29a86416c4fbac6020f15bfd36d66861f42a20dd4ccfdab435d"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.8.0" +django = ">=3.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-cas-ng" +version = "4.3.0" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-cas-ng-4.3.1.zip", hash = "sha256:aeea96ad7958e3cb40d9bb5ef6a1add66f720835dfe87cc1dfe163f92d084690"}, +] + +[package.dependencies] +Django = ">=2.2" +python-cas = ">=1.6.0" + +[package.source] +type = "url" +url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.1/django-cas-ng-4.3.1.zip" + +[[package]] +name = "django-celery-beat" +version = "2.5.0" +description = "Database-backed Periodic Tasks." +optional = false +python-versions = "*" +files = [ + {file = "django-celery-beat-2.5.0.tar.gz", hash = "sha256:cd0a47f5958402f51ac0c715bc942ae33d7b50b4e48cba91bc3f2712be505df1"}, + {file = "django_celery_beat-2.5.0-py3-none-any.whl", hash = "sha256:ae460faa5ea142fba0875409095d22f6bd7bcc7377889b85e8cab5c0dfb781fe"}, +] + +[package.dependencies] +celery = ">=5.2.3,<6.0" +cron-descriptor = ">=1.2.32" +Django = ">=2.2,<5.0" +django-timezone-field = ">=5.0" +python-crontab = ">=2.3.4" +tzdata = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-debug-toolbar" +version = "4.1.0" +description = "A configurable set of panels that display various debug information about the current request/response." +optional = false +python-versions = ">=3.8" +files = [ + {file = "django_debug_toolbar-4.1.0-py3-none-any.whl", hash = "sha256:a0b532ef5d52544fd745d1dcfc0557fa75f6f0d1962a8298bd568427ef2fa436"}, + {file = "django_debug_toolbar-4.1.0.tar.gz", hash = "sha256:f57882e335593cb8e74c2bda9f1116bbb9ca8fc0d81b50a75ace0f83de5173c7"}, +] + +[package.dependencies] +django = ">=3.2.4" +sqlparse = ">=0.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-filter" +version = "23.2" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-filter-23.2.tar.gz", hash = "sha256:2fe15f78108475eda525692813205fa6f9e8c1caf1ae65daa5862d403c6dbf00"}, + {file = "django_filter-23.2-py3-none-any.whl", hash = "sha256:d12d8e0fc6d3eb26641e553e5d53b191eb8cec611427d4bdce0becb1f7c172b5"}, +] + +[package.dependencies] +Django = ">=3.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-formtools" +version = "2.4.1" +description = "A set of high-level abstractions for Django forms" +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-formtools-2.4.1.tar.gz", hash = "sha256:21f8d5dac737f1e636fa8a0a10969c1c32f525a6dfa27c29592827ba70d9643a"}, + {file = "django_formtools-2.4.1-py3-none-any.whl", hash = "sha256:49ea8a64ddef4728a558bf5f8f622c0f4053b979edcf193bf00dd80432ab2f12"}, +] + +[package.dependencies] +Django = ">=3.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-private-storage" +version = "3.1" +description = "Private media file storage for Django projects" +optional = false +python-versions = "*" +files = [ + {file = "django-private-storage-3.1.tar.gz", hash = "sha256:e3f0440f5d8f0da0fdb9da0285dad00e279ea43da68324a8aaf0c31c999c8e8c"}, + {file = "django_private_storage-3.1-py3-none-any.whl", hash = "sha256:cd11fa3c40e15bf902b3566dde8082c86f1cbb2900115f15c63e4b5278d96fe7"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-proxy" +version = "1.2.2" +description = "A simple HTTP proxy service as a Django app" +optional = false +python-versions = "*" +files = [ + {file = "django_proxy-1.2.2-py3-none-any.whl", hash = "sha256:f3e54e102cf2d328124f51ce8e76a08cb058ed4aea01bea23148bc785bbccafc"}, +] + +[package.dependencies] +requests = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-radius" +version = "1.4.0" +description = "Django authentication backend for RADIUS" +optional = false +python-versions = "*" +files = [ + {file = "1.5.0.zip", hash = "sha256:b50680dd1c577a099f90d510856b3e55aa496d8c876beabb5b0a79fc94fcfae9"}, +] + +[package.dependencies] +pyrad = ">=1.2,<2.2 || >2.2" + +[package.source] +type = "url" +url = "https://github.com/ibuler/django-radius/archive/refs/tags/1.5.0.zip" + +[[package]] +name = "django-ranged-response" +version = "0.2.0" +description = "Modified Django FileResponse that adds Content-Range headers." +optional = false +python-versions = "*" +files = [ + {file = "django-ranged-response-0.2.0.tar.gz", hash = "sha256:f71fff352a37316b9bead717fc76e4ddd6c9b99c4680cdf4783b9755af1cf985"}, +] + +[package.dependencies] +django = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-redis" +version = "5.3.0" +description = "Full featured redis cache backend for Django." +optional = false +python-versions = ">=3.6" +files = [ + {file = "django-redis-5.3.0.tar.gz", hash = "sha256:8bc5793ec06b28ea802aad85ec437e7646511d4e571e07ccad19cfed8b9ddd44"}, + {file = "django_redis-5.3.0-py3-none-any.whl", hash = "sha256:2d8660d39f586c41c9907d5395693c477434141690fd7eca9d32376af00b0aac"}, +] + +[package.dependencies] +Django = ">=3.2" +redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" + +[package.extras] +hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-rest-swagger" +version = "2.2.0" +description = "Swagger UI for Django REST Framework 3.5+" +optional = false +python-versions = "*" +files = [ + {file = "django-rest-swagger-2.2.0.tar.gz", hash = "sha256:48f6aded9937e90ae7cbe9e6c932b9744b8af80cc4e010088b3278c700e0685b"}, + {file = "django_rest_swagger-2.2.0-py2.py3-none-any.whl", hash = "sha256:b039b0288bab4665cd45dc5d16f94b13911bc4ad0ed55f74ad3b90aa31c87c17"}, +] + +[package.dependencies] +coreapi = ">=2.3.0" +djangorestframework = ">=3.5.4" +openapi-codec = ">=1.3.1" +simplejson = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-simple-captcha" +version = "0.5.18" +description = "A very simple, yet powerful, Django captcha application" +optional = false +python-versions = "*" +files = [ + {file = "django-simple-captcha-0.5.18.tar.gz", hash = "sha256:6e1fcc4f4005f7d69ee7a2e59a7e863b5d3918f36a85a4d811498984aecc48ce"}, + {file = "django_simple_captcha-0.5.18-py2.py3-none-any.whl", hash = "sha256:567ad84fa64c86508c679b8425cc1410c44b3cd6467e54f8d31cf077d9366407"}, +] + +[package.dependencies] +Django = ">=3.2" +django-ranged-response = "0.2.0" +Pillow = ">=6.2.0" + +[package.extras] +test = ["testfixtures"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-simple-history" +version = "3.3.0" +description = "Store model history and view/revert changes from admin site." +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-simple-history-3.3.0.tar.gz", hash = "sha256:2313d2d346f15a1e7a92adb3b6696b226f1cd0c1d920869ec40c4c4076614c41"}, + {file = "django_simple_history-3.3.0-py3-none-any.whl", hash = "sha256:dc1f98e558a0a1e0b6371c3b8efb85f86e02a6db56e83d0ec198343b7408d00a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "django-timezone-field" +version = "5.1" +description = "A Django app providing DB, form, and REST framework fields for zoneinfo and pytz timezone objects." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "django_timezone_field-5.1-py3-none-any.whl", hash = "sha256:16ca9955a4e16064e32168b1a0d1cdb2839679c6cb56856c1f49f506e2ca4281"}, + {file = "django_timezone_field-5.1.tar.gz", hash = "sha256:73fc49519273cd5da1c7f16abc04a4bcad87b00cc02968d0d384c0fecf9a8a86"}, +] + +[package.dependencies] +Django = ">=2.2,<3.0.dev0 || >=3.2.dev0,<5.0" +pytz = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "djangorestframework" +version = "3.14.0" +description = "Web APIs for Django, made easy." +optional = false +python-versions = ">=3.6" +files = [ + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, +] + +[package.dependencies] +django = ">=3.0" +pytz = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "djangorestframework-bulk" +version = "0.2.1" +description = "Django REST Framework bulk CRUD view mixins" +optional = false +python-versions = "*" +files = [ + {file = "djangorestframework-bulk-0.2.1.tar.gz", hash = "sha256:39230d8379acebd86d313df6c9150cafecb636eae1d097c30a26389ab9fee5b1"}, +] + +[package.dependencies] +django = "*" +djangorestframework = "*" +setuptools = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "drf-nested-routers" +version = "0.93.4" +description = "Nested resources for the Django Rest Framework" +optional = false +python-versions = ">=3.5" +files = [ + {file = "drf-nested-routers-0.93.4.tar.gz", hash = "sha256:01aa556b8c08608bb74fb34f6ca065a5183f2cda4dc0478192cc17a2581d71b0"}, + {file = "drf_nested_routers-0.93.4-py2.py3-none-any.whl", hash = "sha256:996b77f3f4dfaf64569e7b8f04e3919945f90f95366838ca5b8bed9dd709d6c5"}, +] + +[package.dependencies] +Django = ">=1.11" +djangorestframework = ">=3.6.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "drf-writable-nested" +version = "0.7.0" +description = "Writable nested helpers for django-rest-framework's serializers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "drf_writable_nested-0.7.0-py3-none-any.whl", hash = "sha256:154c0381e8a3a477e0fd539d5e1caf8ff4c1097a9c0c0fe741d4858b11b0455b"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "drf-yasg" +version = "1.21.7" +description = "Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code." +optional = false +python-versions = ">=3.6" +files = [ + {file = "drf-yasg-1.21.7.tar.gz", hash = "sha256:4c3b93068b3dfca6969ab111155e4dd6f7b2d680b98778de8fd460b7837bdb0d"}, + {file = "drf_yasg-1.21.7-py3-none-any.whl", hash = "sha256:f85642072c35e684356475781b7ecf5d218fff2c6185c040664dd49f0a4be181"}, +] + +[package.dependencies] +django = ">=2.2.16" +djangorestframework = ">=3.10.3" +inflection = ">=0.3.1" +packaging = ">=21.0" +pytz = ">=2021.1" +pyyaml = ">=5.1" +uritemplate = ">=3.0.0" + +[package.extras] +coreapi = ["coreapi (>=2.3.3)", "coreschema (>=0.0.4)"] +validation = ["swagger-spec-validator (>=2.1.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "elasticsearch" +version = "7.8.0" +description = "Python client for Elasticsearch" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +files = [ + {file = "elasticsearch-7.8.0-py2.py3-none-any.whl", hash = "sha256:6fb566dd23b91b5871ce12212888674b4cf33374e92b71b1080916c931e44dcb"}, + {file = "elasticsearch-7.8.0.tar.gz", hash = "sha256:e637d8cf4e27e279b5ff8ca8edc0c086f4b5df4bf2b48e2f950b7833aca3a792"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.21.1" + +[package.extras] +async = ["aiohttp (>=3,<4)", "yarl"] +develop = ["black", "coverage", "jinja2", "mock", "pytest", "pytest-cov", "pyyaml", "requests (>=2.0.0,<3.0.0)", "sphinx (<1.7)", "sphinx-rtd-theme"] +docs = ["sphinx (<1.7)", "sphinx-rtd-theme"] +requests = ["requests (>=2.4.0,<3.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "enum-compat" +version = "0.0.3" +description = "enum/enum34 compatibility package" +optional = false +python-versions = "*" +files = [ + {file = "enum-compat-0.0.3.tar.gz", hash = "sha256:3677daabed56a6f724451d585662253d8fb4e5569845aafa8bb0da36b1a8751e"}, + {file = "enum_compat-0.0.3-py3-none-any.whl", hash = "sha256:88091b617c7fc3bbbceae50db5958023c48dc40b50520005aa3bf27f8f7ea157"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ephem" +version = "4.1.4" +description = "Compute positions of the planets and stars" +optional = false +python-versions = "*" +files = [ + {file = "ephem-4.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:024752ae7074c660630046929e12650cc6e72e1c11b7554ccefbe16f8ce3c48d"}, + {file = "ephem-4.1.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:158bf9fb2b58cb77c606687c9ad35dc82903ed00617a12c93dd2d89a65d6374d"}, + {file = "ephem-4.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11be09d245e77457e87988a4fdc811bdc6c5f1daaa73fb24289b220db720831d"}, + {file = "ephem-4.1.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:649bd2c85f5fc450136dacc0416af7127a07c0b2ce84bfdc89c1bc78129216a3"}, + {file = "ephem-4.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6918b012365791b786ed0f30e8ea3941cbc49486dcf6c28d151f119c62d5be8"}, + {file = "ephem-4.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d343e9ca26f04a05b01fcaaf800224da5d15ad76902d7dc452c216e448293893"}, + {file = "ephem-4.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d0403738f59aefe81ee5b0219cd909f2a05b03b7da465f9b233da57d3dea0de6"}, + {file = "ephem-4.1.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d61f38f35c25049ca2b95aa4e12e952e5ef56b31eac4a9d6d4e24aacf9373101"}, + {file = "ephem-4.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4039aa2f0b8c204283fc478551d8b29c9473137ad8a910a5ff60ae3be6593c7b"}, + {file = "ephem-4.1.4-cp310-cp310-win32.whl", hash = "sha256:8979429643ac4e29a5496321c9c41a20cd7a6a530aee9865c7fab0008450ef28"}, + {file = "ephem-4.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:589a2235f49232b92ee0247923360a264086a57b2c39d4191348f95ba5ce0c3d"}, + {file = "ephem-4.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feab5f68ae9fa622d6316790c9cfc399e8b78f7958a61da4edf2cb5e0e675d44"}, + {file = "ephem-4.1.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df9c482c8d25e69271311606b4de0e177e44ceb11781c7ebacf17cc5b391f832"}, + {file = "ephem-4.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cceea883856817f3ea3f73442e3be3a4a56558cdb77cc85d02db4429ce5ab16"}, + {file = "ephem-4.1.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3e9ea626bc522fd4dd1ff795f0922fcb4c4eb57effe91b6284411166ddd1fd7"}, + {file = "ephem-4.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad22e769bd35415cad8f2ef6d4d875190d9bdad4fed2842fd19dac6f1ccb9dd"}, + {file = "ephem-4.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3327218495235ec215ffe43253c0bf3fa982f59504b351c53b024f1c7b63752"}, + {file = "ephem-4.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2c56238a1aec72a78ad7061f5c531df97a66ea0cca45204b6b8141c3bb1540e"}, + {file = "ephem-4.1.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fffe176bd94f4f61be5a54b54a966988cceb6ba69a6ed729aab166020fdb37c3"}, + {file = "ephem-4.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8f9b27117e7a82f7f70db9cb23b5cc36d37b166a2f73c55e14d7225d0ab95afa"}, + {file = "ephem-4.1.4-cp311-cp311-win32.whl", hash = "sha256:9bb21c0b117c9122c0141b0a71ee6fbbb087ed2aab4a7ab60f009e95e9f4a521"}, + {file = "ephem-4.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:55d7fb5c34b2e453e01fa4ca7ee375b19b438c9401ae8c4099ae4a3a37656972"}, + {file = "ephem-4.1.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:40067fc050c946c8d4c2d779805b61f063471a091e6124cbabcf61ac538011b2"}, + {file = "ephem-4.1.4-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e2abe97aa2b091090012768b4d94793213cc01f0bf040dcc311a380ab08df69"}, + {file = "ephem-4.1.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2677d3a5b42aedc578de10b0eecdba6a50731f159cb28f7ad38c5f62143494"}, + {file = "ephem-4.1.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80a73da8ec61f86e5a97f73311159e61279dabdfbd17c9d4e2791a25a836f9ce"}, + {file = "ephem-4.1.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a146db114cfc942d123a38c301a8b8ca7eef2e37d2c5a4bd59e4abc99123c083"}, + {file = "ephem-4.1.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4f3650c27c3ab6b73e2de7fd8de10e1c0d73f4683c9c5fb2e7113722ec2c2b53"}, + {file = "ephem-4.1.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:171fc5e7c4e9523f900dfd5ab6520bceb260a2b59fcb558d9aec17fd562b6251"}, + {file = "ephem-4.1.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:8974799afb37f17ac71e16e479d0e315d74bea4bed2becaf21d1b9304299bbaf"}, + {file = "ephem-4.1.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3c39fb62c240f57533ea618295e510c44e466e74f2f4a899fbaa04b166b62d04"}, + {file = "ephem-4.1.4-cp36-cp36m-win32.whl", hash = "sha256:23e1432f021c69b2965c87a693ffd25caf08416e92bcb0fed91425083a374210"}, + {file = "ephem-4.1.4-cp36-cp36m-win_amd64.whl", hash = "sha256:86d6dda3581e61f6bad5479e26bca9e560671852ac00a5a8ed10f722635ddf71"}, + {file = "ephem-4.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e731c3e2f1767fab14e5d4077a3519f70afd22cb7dd113274c2850f8ef8ff828"}, + {file = "ephem-4.1.4-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8b79b47c7be0d64013fb5d97dd6bbfb9bf63ae07b2ec917be19d3b4cc5782b8"}, + {file = "ephem-4.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f759ce8e3489d15b9f3796d210196085dcb84cacdf24b2ece791b029245544"}, + {file = "ephem-4.1.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daf1a1280102e14c718d684989da34151697a426522f8ae18b1081e8bad705c9"}, + {file = "ephem-4.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3da3b76d5d5e339461059c3314013c152ef569c798bfd578bcfb504b875a837"}, + {file = "ephem-4.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aac0a0e41deb2a197cf67e800a3d0f4029139b9ce12bed148ffe994ec78593f9"}, + {file = "ephem-4.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:85809803e349bb4a0d56880067549abdc2b93fddf93ac3d55486040cbec1553f"}, + {file = "ephem-4.1.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:93d8f8b4e6206d3401dbdb0cdabb0d15c59cf9c2a7ee7c586da8c7dbf1f2a136"}, + {file = "ephem-4.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5beaa0cb659951211aec33a7c132557a1a161dacf53f1b1493830489cfc68215"}, + {file = "ephem-4.1.4-cp37-cp37m-win32.whl", hash = "sha256:bbd4727498928ece694ec1b33023f16b6d050d9952d4052129b24e08e04d67fd"}, + {file = "ephem-4.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:39c710d73449b1c495b58d803da881363a0cae4b728de9fa332f77bcb4686ac8"}, + {file = "ephem-4.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24b7e90c731e851a56ab5e9d538915faaa54945e9b5211cfdf04e570fc27c529"}, + {file = "ephem-4.1.4-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab40ad7a5ccd66cad4161ca2295c04f01a74ec596c936c3af97a67733e2cd5bf"}, + {file = "ephem-4.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555d63d70e36e46e43b955c37cdb0f8b82ee2051c575960c4b01948be9f04e5e"}, + {file = "ephem-4.1.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a29de7c737047cc2412edada9d03b761339d3560d7db471cd04f257e1e02f2f"}, + {file = "ephem-4.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e737b49643a300fa15b788accc72802af93b49cd5d071e53111e08e3fba6570"}, + {file = "ephem-4.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:56c448a83290dabd1df5865fbf9e39d17400abcef37cb36de90ea1a860c0a08e"}, + {file = "ephem-4.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3bd7da534a542d937b10f3c643301dc9b8bc09f7a40350b32efc32910232da9e"}, + {file = "ephem-4.1.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0493ad1b3d2505acbf442e31aadb86fba096ba30008a586fe6361a9de5974ed3"}, + {file = "ephem-4.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3442fba6afae0bcb643c9b069765033b67d2c8fe4350f9beb4f2f5cfdaaa7442"}, + {file = "ephem-4.1.4-cp38-cp38-win32.whl", hash = "sha256:8e0bb8379fb6b709a3cbceb6a11a3dc0f25e5b16a6f009b48e09d6b95f896d9c"}, + {file = "ephem-4.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:a940cd4d8d7aed68efd3d6b717f393bbedf541d388ba11eb3ed56a9fc6cbb1ca"}, + {file = "ephem-4.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f51a3bcd5f69c4070e8a6936e4a61019ad2d6b94bc8b5ca1e498dea0962a373"}, + {file = "ephem-4.1.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41680b48aeae5b992371bf7ec1bc07457500ff4a6ea7d333793e945b97951de0"}, + {file = "ephem-4.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f11edaef2e4a4d010e21b5ff8bcd9435fbc7fe9e16923f81143f248ae8ae8e9d"}, + {file = "ephem-4.1.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72078b49748318cbbbe1a49ab5dcd05e63c917151351175b590833e6163a1506"}, + {file = "ephem-4.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2ba977ad0402ac44fe66af6e1119632efe84b7d1255f8f6f94d7768d9487453"}, + {file = "ephem-4.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a21a11285904f43c3bc6909727d09109b8e38dc2e3cda662089601cb37b3d082"}, + {file = "ephem-4.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9aabc3cab5fb9440564dfdf79e39ee01d16554d7bfb8228cddfe9eada460dba9"}, + {file = "ephem-4.1.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:5327fd48fc8ff966023a6f177813fc058bb2a496c70b53a79f85bf2eba3ca93d"}, + {file = "ephem-4.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e429f6e0e05e4c8c54802e951cd1bde440dd6f896c2b5691ef5ebd9bc3ba489"}, + {file = "ephem-4.1.4-cp39-cp39-win32.whl", hash = "sha256:a8d125d04800425a9d944109710687bbb3703e8f04ac3bc8445779bb0ad5dcc2"}, + {file = "ephem-4.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:4e8ec4e29c7f04d6334215775a8c4dc77eae8ed698384530d9415a98500ed01c"}, + {file = "ephem-4.1.4.tar.gz", hash = "sha256:73a59f0d2162d1624535c3c3b75f956556bdbb2055eaf554a7bef147d3f9c760"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "esdk-obs-python" +version = "3.21.4" +description = "OBS Python SDK" +optional = false +python-versions = "*" +files = [ + {file = "esdk-obs-python-3.21.4.tar.gz", hash = "sha256:a3b2a01b0a10768b5b02812abe239048feaae199256cbde67315870121d8ab1e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "eventlet" +version = "0.33.3" +description = "Highly concurrent networking library" +optional = false +python-versions = "*" +files = [ + {file = "eventlet-0.33.3-py2.py3-none-any.whl", hash = "sha256:e43b9ae05ba4bb477a10307699c9aff7ff86121b2640f9184d29059f5a687df8"}, + {file = "eventlet-0.33.3.tar.gz", hash = "sha256:722803e7eadff295347539da363d68ae155b8b26ae6a634474d0a920be73cfda"}, +] + +[package.dependencies] +dnspython = ">=1.15.0" +greenlet = ">=0.3" +six = ">=1.10.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "flower" +version = "2.0.0" +description = "Celery Flower" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flower-2.0.0-py2.py3-none-any.whl", hash = "sha256:571f9ed1c57a622e862de35eceb8a4244f023fbcfb7175f53e45ebe679f46d90"}, + {file = "flower-2.0.0.tar.gz", hash = "sha256:5657785d728a54914256c34fd0551fe2d7152aab08062ebc645bf86b97b8aec5"}, +] + +[package.dependencies] +celery = ">=5.0.5" +humanize = "*" +prometheus-client = ">=0.8.0" +pytz = "*" +tornado = ">=5.0.0,<7.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "forgerypy3" +version = "0.3.1" +description = "A forged data generator updated to reflect current state of the original Ruby forgery gem" +optional = false +python-versions = "*" +files = [ + {file = "ForgeryPy3-0.3.1.tar.gz", hash = "sha256:03db26b2129252dc8c8c91aa5661171725a64707773c03c3ca0251b1dd173c93"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "future" +version = "0.18.3" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "geoip2" +version = "4.7.0" +description = "MaxMind GeoIP2 API" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geoip2-4.7.0-py2.py3-none-any.whl", hash = "sha256:078fcd4cce26ea029b1e3252a0f0ec20a1f42e7ab0f19b7be3864f20f4db2b51"}, + {file = "geoip2-4.7.0.tar.gz", hash = "sha256:3bdde4994f6bc917eafab5b51e772d737b2ae00037a5b85001fb06dc68f779df"}, +] + +[package.dependencies] +aiohttp = ">=3.6.2,<4.0.0" +maxminddb = ">=2.3.0,<3.0.0" +requests = ">=2.24.0,<3.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "gmssl" +version = "3.2.2" +description = "Pure-Python SM2/SM3/SM4 implementation" +optional = false +python-versions = "*" +files = [ + {file = "gmssl-3.2.2-py3-none-any.whl", hash = "sha256:59f069a91eb19ef59b9e7be4d436ed01c92ce064d3d7d45a8778fc07fd2cd068"}, +] + +[package.dependencies] +pycryptodomex = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "google-api-core" +version = "2.11.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.11.1.tar.gz", hash = "sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a"}, + {file = "google_api_core-2.11.1-py3-none-any.whl", hash = "sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +grpcio = [ + {version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, +] +grpcio-status = [ + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, +] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "google-auth" +version = "2.22.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "google-auth-2.22.0.tar.gz", hash = "sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce"}, + {file = "google_auth-2.22.0-py2.py3-none-any.whl", hash = "sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" +six = ">=1.9.0" +urllib3 = "<2.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "google-cloud-compute" +version = "1.13.0" +description = "Google Cloud Compute API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-compute-1.13.0.tar.gz", hash = "sha256:93b72129c6443c898da5a060d2021bc2d11c2a57ef2fbb9306afbb5126a376b9"}, + {file = "google_cloud_compute-1.13.0-py2.py3-none-any.whl", hash = "sha256:0f75d6c09cc504e43f4eceb2a466de9e9eb6fca819ca8ffdcf098ca5b21c7dc1"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "googleapis-common-protos" +version = "1.60.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.60.0.tar.gz", hash = "sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708"}, + {file = "googleapis_common_protos-1.60.0-py2.py3-none-any.whl", hash = "sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "grpcio" +version = "1.56.2" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.56.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:bf0b9959e673505ee5869950642428046edb91f99942607c2ecf635f8a4b31c9"}, + {file = "grpcio-1.56.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:5144feb20fe76e73e60c7d73ec3bf54f320247d1ebe737d10672480371878b48"}, + {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a72797549935c9e0b9bc1def1768c8b5a709538fa6ab0678e671aec47ebfd55e"}, + {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3f3237a57e42f79f1e560726576aedb3a7ef931f4e3accb84ebf6acc485d316"}, + {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:900bc0096c2ca2d53f2e5cebf98293a7c32f532c4aeb926345e9747452233950"}, + {file = "grpcio-1.56.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97e0efaebbfd222bcaac2f1735c010c1d3b167112d9d237daebbeedaaccf3d1d"}, + {file = "grpcio-1.56.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0c85c5cbe8b30a32fa6d802588d55ffabf720e985abe9590c7c886919d875d4"}, + {file = "grpcio-1.56.2-cp310-cp310-win32.whl", hash = "sha256:06e84ad9ae7668a109e970c7411e7992751a116494cba7c4fb877656527f9a57"}, + {file = "grpcio-1.56.2-cp310-cp310-win_amd64.whl", hash = "sha256:10954662f77dc36c9a1fb5cc4a537f746580d6b5734803be1e587252682cda8d"}, + {file = "grpcio-1.56.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:c435f5ce1705de48e08fcbcfaf8aee660d199c90536e3e06f2016af7d6a938dd"}, + {file = "grpcio-1.56.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:6108e5933eb8c22cd3646e72d5b54772c29f57482fd4c41a0640aab99eb5071d"}, + {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8391cea5ce72f4a12368afd17799474015d5d3dc00c936a907eb7c7eaaea98a5"}, + {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750de923b456ca8c0f1354d6befca45d1f3b3a789e76efc16741bd4132752d95"}, + {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fda2783c12f553cdca11c08e5af6eecbd717280dc8fbe28a110897af1c15a88c"}, + {file = "grpcio-1.56.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9e04d4e4cfafa7c5264e535b5d28e786f0571bea609c3f0aaab13e891e933e9c"}, + {file = "grpcio-1.56.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89a49cc5ad08a38b6141af17e00d1dd482dc927c7605bc77af457b5a0fca807c"}, + {file = "grpcio-1.56.2-cp311-cp311-win32.whl", hash = "sha256:6a007a541dff984264981fbafeb052bfe361db63578948d857907df9488d8774"}, + {file = "grpcio-1.56.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4063ef2b11b96d949dccbc5a987272f38d55c23c4c01841ea65a517906397f"}, + {file = "grpcio-1.56.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:a6ff459dac39541e6a2763a4439c4ca6bc9ecb4acc05a99b79246751f9894756"}, + {file = "grpcio-1.56.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:f20fd21f7538f8107451156dd1fe203300b79a9ddceba1ee0ac8132521a008ed"}, + {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:d1fbad1f9077372b6587ec589c1fc120b417b6c8ad72d3e3cc86bbbd0a3cee93"}, + {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee26e9dfb3996aff7c870f09dc7ad44a5f6732b8bdb5a5f9905737ac6fd4ef1"}, + {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c60abd950d6de3e4f1ddbc318075654d275c29c846ab6a043d6ed2c52e4c8c"}, + {file = "grpcio-1.56.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1c31e52a04e62c8577a7bf772b3e7bed4df9c9e0dd90f92b6ffa07c16cab63c9"}, + {file = "grpcio-1.56.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:345356b307cce5d14355e8e055b4ca5f99bc857c33a3dc1ddbc544fca9cd0475"}, + {file = "grpcio-1.56.2-cp37-cp37m-win_amd64.whl", hash = "sha256:42e63904ee37ae46aa23de50dac8b145b3596f43598fa33fe1098ab2cbda6ff5"}, + {file = "grpcio-1.56.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:7c5ede2e2558f088c49a1ddda19080e4c23fb5d171de80a726b61b567e3766ed"}, + {file = "grpcio-1.56.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:33971197c47965cc1d97d78d842163c283e998223b151bab0499b951fd2c0b12"}, + {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d39f5d4af48c138cb146763eda14eb7d8b3ccbbec9fe86fb724cd16e0e914c64"}, + {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded637176addc1d3eef35331c39acc598bac550d213f0a1bedabfceaa2244c87"}, + {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c90da4b124647547a68cf2f197174ada30c7bb9523cb976665dfd26a9963d328"}, + {file = "grpcio-1.56.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3ccb621749a81dc7755243665a70ce45536ec413ef5818e013fe8dfbf5aa497b"}, + {file = "grpcio-1.56.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4eb37dd8dd1aa40d601212afa27ca5be255ba792e2e0b24d67b8af5e012cdb7d"}, + {file = "grpcio-1.56.2-cp38-cp38-win32.whl", hash = "sha256:ddb4a6061933bd9332b74eac0da25f17f32afa7145a33a0f9711ad74f924b1b8"}, + {file = "grpcio-1.56.2-cp38-cp38-win_amd64.whl", hash = "sha256:8940d6de7068af018dfa9a959a3510e9b7b543f4c405e88463a1cbaa3b2b379a"}, + {file = "grpcio-1.56.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:51173e8fa6d9a2d85c14426bdee5f5c4a0654fd5fddcc21fe9d09ab0f6eb8b35"}, + {file = "grpcio-1.56.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:373b48f210f43327a41e397391715cd11cfce9ded2fe76a5068f9bacf91cc226"}, + {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:42a3bbb2bc07aef72a7d97e71aabecaf3e4eb616d39e5211e2cfe3689de860ca"}, + {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5344be476ac37eb9c9ad09c22f4ea193c1316bf074f1daf85bddb1b31fda5116"}, + {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3fa3ab0fb200a2c66493828ed06ccd1a94b12eddbfb985e7fd3e5723ff156c6"}, + {file = "grpcio-1.56.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b975b85d1d5efc36cf8b237c5f3849b64d1ba33d6282f5e991f28751317504a1"}, + {file = "grpcio-1.56.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cbdf2c498e077282cd427cfd88bdce4668019791deef0be8155385ab2ba7837f"}, + {file = "grpcio-1.56.2-cp39-cp39-win32.whl", hash = "sha256:139f66656a762572ae718fa0d1f2dce47c05e9fbf7a16acd704c354405b97df9"}, + {file = "grpcio-1.56.2-cp39-cp39-win_amd64.whl", hash = "sha256:830215173ad45d670140ff99aac3b461f9be9a6b11bee1a17265aaaa746a641a"}, + {file = "grpcio-1.56.2.tar.gz", hash = "sha256:0ff789ae7d8ddd76d2ac02e7d13bfef6fc4928ac01e1dcaa182be51b6bcc0aaa"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.56.2)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "grpcio-status" +version = "1.56.2" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.6" +files = [ + {file = "grpcio-status-1.56.2.tar.gz", hash = "sha256:a046b2c0118df4a5687f4585cca9d3c3bae5c498c4dff055dcb43fb06a1180c8"}, + {file = "grpcio_status-1.56.2-py3-none-any.whl", hash = "sha256:63f3842867735f59f5d70e723abffd2e8501a6bcd915612a1119e52f10614782"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.56.2" +protobuf = ">=4.21.6" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "gunicorn" +version = "21.2.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, + {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "html2text" +version = "2020.1.16" +description = "Turn HTML into equivalent Markdown-structured text." +optional = false +python-versions = ">=3.5" +files = [ + {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, + {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "httpsig" +version = "1.3.0" +description = "Secure HTTP request signing using the HTTP Signature draft specification" +optional = false +python-versions = "*" +files = [ + {file = "httpsig-1.3.0-py2.py3-none-any.whl", hash = "sha256:ce3ebd489a9b3325810adf0f4992718a3c931d026fe04cafc1177c24be1ec4d3"}, + {file = "httpsig-1.3.0.tar.gz", hash = "sha256:71d6d50246129c4f7cfec20f5e57e351d2b8492d631cc2aa967914acf91f6ce6"}, +] + +[package.dependencies] +pycryptodome = ">=3,<4" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "huaweicloudsdkcore" +version = "3.1.52" +description = "HuaweiCloud SDK Python Core" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +files = [ + {file = "huaweicloudsdkcore-3.1.52-py2.py3-none-any.whl", hash = "sha256:1c8abc831e4f28460c41fcf46112e6fc7b38035b639d9899b04b8753d8bc835d"}, +] + +[package.dependencies] +PyYAML = ">=5.4.1" +requests = "*" +requests-toolbelt = ">=0.10.1" +simplejson = ">=3.18.0" +six = ">=1.16.0" +typing-extensions = "*" + +[package.extras] +python-version-2-7- = ["certifi (>=2022.12.7)", "configparser (>=4.0.2)", "futures (>=3.3.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "huaweicloudsdkecs" +version = "3.1.52" +description = "ECS" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +files = [ + {file = "huaweicloudsdkecs-3.1.52-py2.py3-none-any.whl", hash = "sha256:39631ec15082b438c10dbc310619cea5cd71319ac95caf9989666193fd456de3"}, +] + +[package.dependencies] +huaweicloudsdkcore = ">=3.1.52" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "humanize" +version = "4.8.0" +description = "Python humanize utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "humanize-4.8.0-py3-none-any.whl", hash = "sha256:8bc9e2bb9315e61ec06bf690151ae35aeb65651ab091266941edf97c90836404"}, + {file = "humanize-4.8.0.tar.gz", hash = "sha256:9783373bf1eec713a770ecaa7c2d7a7902c98398009dfa3d8a2df91eec9311e8"}, +] + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "hvac" +version = "1.1.1" +description = "HashiCorp Vault API client" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "hvac-1.1.1-py3-none-any.whl", hash = "sha256:466e883665b4082933106b292649f9fba3bc0709a1ec1729e9e35b29477164b3"}, + {file = "hvac-1.1.1.tar.gz", hash = "sha256:f9dbcc46b98b250c785eb1050aa11ee34a0c8b6616b75218cf1346a9817992f9"}, +] + +[package.dependencies] +pyhcl = ">=0.4.4,<0.5.0" +requests = ">=2.27.1,<3.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "hyperlink" +version = "21.0.0" +description = "A featureful, immutable, and correct URL for Python." +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"}, + {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"}, +] + +[package.dependencies] +idna = ">=2.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "incremental" +version = "22.10.0" +description = "\"A small library that versions your Python projects.\"" +optional = false +python-versions = "*" +files = [ + {file = "incremental-22.10.0-py2.py3-none-any.whl", hash = "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"}, + {file = "incremental-22.10.0.tar.gz", hash = "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0"}, +] + +[package.extras] +mypy = ["click (>=6.0)", "mypy (==0.812)", "twisted (>=16.4.0)"] +scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "inflection" +version = "0.5.1" +description = "A port of Ruby on Rails inflector to Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ipip-ipdb" +version = "1.6.1" +description = "IPIP.net officially supported IP database ipdb format parsing library" +optional = false +python-versions = "*" +files = [ + {file = "ipip-ipdb-1.6.1.tar.gz", hash = "sha256:4f396f8f8b1a2fc7fe3c41e1b05b479aac9aa1bc310b5b0182dbaa5376dc0bb9"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ipy" +version = "1.01" +description = "Class and tools for handling of IPv4 and IPv6 addresses and networks" +optional = false +python-versions = "*" +files = [ + {file = "IPy-1.01.tar.gz", hash = "sha256:edeca741dea2d54aca568fa23740288c3fe86c0f3ea700344571e9ef14a7cc1a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ipython" +version = "8.14.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, + {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "iso8601" +version = "2.0.0" +description = "Simple module to parse ISO 8601 dates" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "iso8601-2.0.0-py3-none-any.whl", hash = "sha256:ebe10061b932edb8a8e33cc635d661926c59b9c3bed7a4f4edca8c62d400af10"}, + {file = "iso8601-2.0.0.tar.gz", hash = "sha256:739960d37c74c77bd9bd546a76562ccb581fe3d4820ff5c3141eb49c839fda8f"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "itypes" +version = "1.2.0" +description = "Simple immutable types for python." +optional = false +python-versions = "*" +files = [ + {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, + {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "jedi" +version = "0.19.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "jmespath" +version = "0.10.0" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, + {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "jms-storage" +version = "0.0.51" +description = "Jumpserver storage python sdk tools" +optional = false +python-versions = "*" +files = [ + {file = "jms-storage-0.0.51.tar.gz", hash = "sha256:47a50ac4d952a21693b0e2f926f42fa0d02bc1fa8e507a8284059743b2b81911"}, +] + +[package.dependencies] +azure-storage-blob = "12.17.0" +boto = "2.49.0" +boto3 = "1.28.9" +botocore = "1.31.9" +certifi = "2023.7.22" +chardet = "5.1.0" +crcmod = "1.7" +docutils = "0.20.1" +elasticsearch = "7.8.0" +esdk-obs-python = "3.21.4" +idna = "3.4" +oss2 = "2.18.1" +python-dateutil = "2.8.2" +pytz = "2023.3" +requests = "2.31.0" +s3transfer = "0.6.1" +six = "1.16.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "jsonfield2" +version = "4.0.0.post0" +description = "A reusable Django field that allows you to store validated JSON in your model." +optional = false +python-versions = "*" +files = [ + {file = "jsonfield2-4.0.0.post0-py3-none-any.whl", hash = "sha256:bab284d3fc721067d5f3107a0adc87ee772e9b09cac5eca042fad6311c4842bd"}, + {file = "jsonfield2-4.0.0.post0.tar.gz", hash = "sha256:cbce2d7c52563550cd2048fcf3a57631019d3547f5ed142f42141d1d2dc53283"}, +] + +[package.dependencies] +Django = ">=2.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "keystoneauth1" +version = "5.2.1" +description = "Authentication Library for OpenStack Identity" +optional = false +python-versions = ">=3.8" +files = [ + {file = "keystoneauth1-5.2.1-py3-none-any.whl", hash = "sha256:d2fcfdcfe347df8d92390e0806b4969289d884cd9ec3519e4c5aec53e66d0767"}, + {file = "keystoneauth1-5.2.1.tar.gz", hash = "sha256:f79b1c27ed5a69be4d03a5bc4967df3dfab0c5d76e85226fa2060cffadff74a1"}, +] + +[package.dependencies] +iso8601 = ">=0.1.11" +os-service-types = ">=1.2.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +requests = ">=2.14.2" +stevedore = ">=1.20.0" + +[package.extras] +betamax = ["betamax (>=0.7.0)", "fixtures (>=3.0.0)", "mock (>=2.0.0)"] +kerberos = ["requests-kerberos (>=0.8.0)"] +oauth1 = ["oauthlib (>=0.6.2)"] +saml2 = ["lxml (>=4.2.0)"] +test = ["PyYAML (>=3.12)", "bandit (>=1.1.0,<1.6.0)", "betamax (>=0.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "flake8-docstrings (>=1.6.0,<1.7.0)", "flake8-import-order (>=0.17.1)", "hacking (>=4.1.0,<4.2.0)", "lxml (>=4.2.0)", "oauthlib (>=0.6.2)", "oslo.config (>=5.2.0)", "oslo.utils (>=3.33.0)", "oslotest (>=3.2.0)", "reno (>=3.1.0)", "requests-kerberos (>=0.8.0)", "requests-mock (>=1.2.0)", "stestr (>=1.0.0)", "testresources (>=2.0.0)", "testtools (>=2.2.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "kombu" +version = "5.3.1" +description = "Messaging library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "kombu-5.3.1-py3-none-any.whl", hash = "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9"}, + {file = "kombu-5.3.1.tar.gz", hash = "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2"}, +] + +[package.dependencies] +amqp = ">=5.1.1,<6.0.0" +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.10.0)"] +azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] +confluentkafka = ["confluent-kafka (==2.1.1)"] +consul = ["python-consul2"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +mongodb = ["pymongo (>=4.1.1)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=4.5.2)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=2.8.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "kubernetes" +version = "27.2.0" +description = "Kubernetes python client" +optional = false +python-versions = ">=3.6" +files = [ + {file = "kubernetes-27.2.0-py2.py3-none-any.whl", hash = "sha256:0f9376329c85cf07615ed6886bf9bf21eb1cbfc05e14ec7b0f74ed8153cd2815"}, + {file = "kubernetes-27.2.0.tar.gz", hash = "sha256:d479931c6f37561dbfdf28fc5f46384b1cb8b28f9db344ed4a232ce91990825a"}, +] + +[package.dependencies] +certifi = ">=14.05.14" +google-auth = ">=1.0.1" +oauthlib = ">=3.2.2" +python-dateutil = ">=2.5.3" +pyyaml = ">=5.4.1" +requests = "*" +requests-oauthlib = "*" +six = ">=1.9.0" +urllib3 = ">=1.24.2" +websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" + +[package.extras] +adal = ["adal (>=1.0.2)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ldap3" +version = "2.9.1" +description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library" +optional = false +python-versions = "*" +files = [ + {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"}, + {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "lml" +version = "0.1.0" +description = "Load me later. A lazy plugin management system." +optional = false +python-versions = "*" +files = [ + {file = "lml-0.1.0-py2.py3-none-any.whl", hash = "sha256:ec06e850019942a485639c8c2a26bdb99eae24505bee7492b649df98a0bed101"}, + {file = "lml-0.1.0.tar.gz", hash = "sha256:57a085a29bb7991d70d41c6c3144c560a8e35b4c1030ffb36d85fa058773bcc5"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "maxminddb" +version = "2.4.0" +description = "Reader for the MaxMind DB format" +optional = false +python-versions = ">=3.7" +files = [ + {file = "maxminddb-2.4.0.tar.gz", hash = "sha256:81e54e53408bd502650e5969ccba16780af659ec1db1c44b2c997e4330a5ed96"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "msal" +version = "1.23.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." +optional = false +python-versions = "*" +files = [ + {file = "msal-1.23.0-py2.py3-none-any.whl", hash = "sha256:3342e0837a047007f9d479e814b559c3219767453d57920dc40a31986862048b"}, + {file = "msal-1.23.0.tar.gz", hash = "sha256:25c9a33acf84301f93d1fdbe9f1a9c60cd38af0d5fffdbfa378138fc7bc1e86b"}, +] + +[package.dependencies] +cryptography = ">=0.6,<44" +PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +requests = ">=2.0.0,<3" + +[package.extras] +broker = ["pymsalruntime (>=0.13.2,<0.14)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "msal-extensions" +version = "1.0.0" +description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." +optional = false +python-versions = "*" +files = [ + {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, + {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, +] + +[package.dependencies] +msal = ">=0.4.1,<2.0.0" +portalocker = [ + {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "msgpack" +version = "1.0.5" +description = "MessagePack serializer" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "msrest" +version = "0.7.1" +description = "AutoRest swagger generator Python client runtime." +optional = false +python-versions = ">=3.6" +files = [ + {file = "msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32"}, + {file = "msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9"}, +] + +[package.dependencies] +azure-core = ">=1.24.0" +certifi = ">=2017.4.17" +isodate = ">=0.6.0" +requests = ">=2.16,<3.0" +requests-oauthlib = ">=0.5.0" + +[package.extras] +async = ["aiodns", "aiohttp (>=3.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "msrestazure" +version = "0.6.4" +description = "AutoRest swagger generator Python client runtime. Azure-specific module." +optional = false +python-versions = "*" +files = [ + {file = "msrestazure-0.6.4-py2.py3-none-any.whl", hash = "sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9"}, + {file = "msrestazure-0.6.4.tar.gz", hash = "sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189"}, +] + +[package.dependencies] +adal = ">=0.6.0,<2.0.0" +msrest = ">=0.6.0,<2.0.0" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "mysqlclient" +version = "2.2.0" +description = "Python interface to MySQL" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mysqlclient-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:68837b6bb23170acffb43ae411e47533a560b6360c06dac39aa55700972c93b2"}, + {file = "mysqlclient-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114"}, + {file = "mysqlclient-2.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:004fe1d30d2c2ff8072f8ea513bcec235fd9b896f70dad369461d0ad7e570e98"}, + {file = "mysqlclient-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c6b142836c7dba4f723bf9c93cc46b6e5081d65b2af807f400dda9eb85a16d0"}, + {file = "mysqlclient-2.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede"}, + {file = "mysqlclient-2.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a"}, + {file = "mysqlclient-2.2.0.tar.gz", hash = "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "netaddr" +version = "0.8.0" +description = "A network address manipulation library for Python" +optional = false +python-versions = "*" +files = [ + {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, + {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "netifaces" +version = "0.11.0" +description = "Portable network interface information." +optional = false +python-versions = "*" +files = [ + {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1"}, + {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4"}, + {file = "netifaces-0.11.0-cp27-cp27m-win32.whl", hash = "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1"}, + {file = "netifaces-0.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85"}, + {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea"}, + {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89"}, + {file = "netifaces-0.11.0-cp34-cp34m-win32.whl", hash = "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4"}, + {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4"}, + {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b"}, + {file = "netifaces-0.11.0-cp35-cp35m-win32.whl", hash = "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac"}, + {file = "netifaces-0.11.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be"}, + {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1"}, + {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0"}, + {file = "netifaces-0.11.0-cp36-cp36m-win32.whl", hash = "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7"}, + {file = "netifaces-0.11.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8"}, + {file = "netifaces-0.11.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246"}, + {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5"}, + {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9"}, + {file = "netifaces-0.11.0-cp37-cp37m-win32.whl", hash = "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150"}, + {file = "netifaces-0.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5"}, + {file = "netifaces-0.11.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c"}, + {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3"}, + {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4"}, + {file = "netifaces-0.11.0-cp38-cp38-win32.whl", hash = "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048"}, + {file = "netifaces-0.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05"}, + {file = "netifaces-0.11.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d"}, + {file = "netifaces-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff"}, + {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f"}, + {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1"}, + {file = "netifaces-0.11.0.tar.gz", hash = "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "olefile" +version = "0.46" +description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "openapi-codec" +version = "1.3.2" +description = "An OpenAPI codec for Core API." +optional = false +python-versions = "*" +files = [ + {file = "openapi-codec-1.3.2.tar.gz", hash = "sha256:1bce63289edf53c601ea3683120641407ff6b708803b8954c8a876fe778d2145"}, +] + +[package.dependencies] +coreapi = ">=2.2.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "openpyxl" +version = "3.0.10" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.0.10-py2.py3-none-any.whl", hash = "sha256:0ab6d25d01799f97a9464630abacbb34aafecdcaa0ef3cba6d6b3499867d0355"}, + {file = "openpyxl-3.0.10.tar.gz", hash = "sha256:e47805627aebcf860edb4edf7987b1309c1b3632f3750538ed962bbcc3bd7449"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oracledb" +version = "1.4.0" +description = "Python interface to Oracle Database" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oracledb-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3893da5f390a2ff8cb0914c025c33255cda6e0c9da506b26c3da6c309a2e9b2a"}, + {file = "oracledb-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38c0ad3cf563123080d0c36bf1e7b28884663b297e4269b69a3f597891808707"}, + {file = "oracledb-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:117e8b33bcb9de98912e13a25d1fed28db3510e7229b27b295b413ba03e2b176"}, + {file = "oracledb-1.4.0-cp310-cp310-win32.whl", hash = "sha256:e9c205e69bafaf8f16ea5f1d06ab713f3f75cab1a4e22995d688f380c92f7aeb"}, + {file = "oracledb-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:411acaf2f11076485a66efb42273ec25ed97cb060df191e0d309f02455f2abbd"}, + {file = "oracledb-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1fc16c738c2287cbc26b2fe5e6292508f78f6b7ae833f3f371c804cc4fd01027"}, + {file = "oracledb-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a1f44bdc068a0051058dcb639bb504c78167cc48c6d5181b1c066cba1e5050"}, + {file = "oracledb-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eec1b828a6bb1d626c1555746ea411a9d853ed8428aafed8eef0aff6d90b66c9"}, + {file = "oracledb-1.4.0-cp311-cp311-win32.whl", hash = "sha256:5a452671d4c079a35c4086fed2f4635f4a902d607c05bab48ea2eb919794f0d0"}, + {file = "oracledb-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:f1ed9b6f857ec70cf33ace4ab16403ce00232d875e68922168ac5e96c9fbea65"}, + {file = "oracledb-1.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52d6184a161508e759797857ebd6fdc8ba1439072c9572718dde9a196924856"}, + {file = "oracledb-1.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b236339d2cd502a6637b462adeef14616afd358a6393608d83cf71e502495b28"}, + {file = "oracledb-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:72165e6e84f7d6b2c3c290f4fadd123cd03c11f715d99f9a3f0117569bf40f3e"}, + {file = "oracledb-1.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a55c509ec54661901b2ae2a60169b8997de480d0646bb458ac92a05fbb77441"}, + {file = "oracledb-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b9fe90ec793ce13f72e2051e4734ad0002cd12dd009141f585c13dc51c314a9"}, + {file = "oracledb-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:be54c63a9424964a4bdccdfa2368188b77e1b00e065b81776b762cbb8559ff2f"}, + {file = "oracledb-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c7272f60f6da7a921a3597db91c323c7efdd44a9682ec4bbfb4483f6c039c810"}, + {file = "oracledb-1.4.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:473fab2bf20092690c3bf87e3d966e3a6fb9186b8cea40e65a73b5980f4f5bd4"}, + {file = "oracledb-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7890bb93137b6ed369fd25fb54ccdff64e41dccac65b3eaca18f08b4f3dffd4"}, + {file = "oracledb-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf5d1b0ea16e0f5168819fbb25b045c6f82f2c019bebddee18c0a8967946f681"}, + {file = "oracledb-1.4.0-cp38-cp38-win32.whl", hash = "sha256:64574cb4f69c3dd2767779c0f71f7b8c4307688b50183027740c6e3ceb2440f6"}, + {file = "oracledb-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:0dcd691da66b7ee34725cfd55d059360b5474e624f0eb18886d3fc9cefd3bf33"}, + {file = "oracledb-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6432d1d2609c0a65ad23c074c0136025f29c5aa47a7093554481de0447f087e9"}, + {file = "oracledb-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadab02421f76c6e2affc72e1719c8ea0c52022dad391fd46024e76f91ac3a02"}, + {file = "oracledb-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25aa5299b981fb52dee19011282dacbd865e5d616e942435763a2a1b76614da2"}, + {file = "oracledb-1.4.0-cp39-cp39-win32.whl", hash = "sha256:269e4f58d874f067605d12f35e701838d58aff4cac05890be6549deec09fe6ae"}, + {file = "oracledb-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6a3c8b8c526f6b10af47951f30bee5a599286dfe18073b053cae1a9d9b1bdec"}, + {file = "oracledb-1.4.0.tar.gz", hash = "sha256:96ba508f783892c7ca648f268acbcb8a4a9c8037c7dd4a509f05f2c89d6231be"}, +] + +[package.dependencies] +cryptography = ">=3.2.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "os-service-types" +version = "1.7.0" +description = "Python library for consuming OpenStack sevice-types-authority data" +optional = false +python-versions = "*" +files = [ + {file = "os-service-types-1.7.0.tar.gz", hash = "sha256:31800299a82239363995b91f1ebf9106ac7758542a1e4ef6dc737a5932878c6c"}, + {file = "os_service_types-1.7.0-py2.py3-none-any.whl", hash = "sha256:0505c72205690910077fb72b88f2a1f07533c8d39f2fe75b29583481764965d6"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oslo-config" +version = "9.1.1" +description = "Oslo Configuration API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "oslo.config-9.1.1-py3-none-any.whl", hash = "sha256:7cd56e0b41b04f64dbc42e83e8164d5ef03466390f1216fbda2cb0e1c535c22c"}, + {file = "oslo.config-9.1.1.tar.gz", hash = "sha256:b07654b53d87792ae8e739962ad729c529c9938a118d891ece9ee31d59716bc9"}, +] + +[package.dependencies] +debtcollector = ">=1.2.0" +netaddr = ">=0.7.18" +"oslo.i18n" = ">=3.15.3" +PyYAML = ">=5.1" +requests = ">=2.18.0" +rfc3986 = ">=1.2.0" +stevedore = ">=1.20.0" + +[package.extras] +rst-generator = ["rst2txt (>=1.1.0)", "sphinx (>=1.8.0,!=2.1.0)"] +test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "hacking (>=3.0.1,<3.1.0)", "mypy (>=0.720)", "oslo.log (>=3.36.0)", "oslotest (>=3.2.0)", "pre-commit (>=2.6.0)", "requests-mock (>=1.5.0)", "stestr (>=2.1.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oslo-i18n" +version = "6.0.0" +description = "Oslo i18n library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "oslo.i18n-6.0.0-py3-none-any.whl", hash = "sha256:080fedf41b05d4dcd23a91d23ee2dea0863996e860a59695856269a42d939fc1"}, + {file = "oslo.i18n-6.0.0.tar.gz", hash = "sha256:ed10686b75f7c607825177a669155f4e259ce39f6143a375f6359bbcaa4a35cd"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oslo-serialization" +version = "5.1.1" +description = "Oslo Serialization library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "oslo.serialization-5.1.1-py3-none-any.whl", hash = "sha256:c5dfb97ce8ddd1d2708a9a3f4a091063f6c304940c7cb39f532f7f791441fdca"}, + {file = "oslo.serialization-5.1.1.tar.gz", hash = "sha256:8abbda8b1763a06071fc28c5d8a9be547ba285f4830e68a70ff88fe11f16bf43"}, +] + +[package.dependencies] +msgpack = ">=0.5.2" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pytz = ">=2013.6" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oslo-utils" +version = "6.2.0" +description = "Oslo Utility library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "oslo.utils-6.2.0-py3-none-any.whl", hash = "sha256:30ba9fd431be468cd17b5d7c1a0ae6d63bb63aaaf97bf590123f13c6d95254a3"}, + {file = "oslo.utils-6.2.0.tar.gz", hash = "sha256:fe1d166f4cb004fbd6b6bc9adfbc32aedeaf3eb54eeaf70d91a224a87543c6a5"}, +] + +[package.dependencies] +debtcollector = ">=1.2.0" +iso8601 = ">=0.1.11" +netaddr = ">=0.7.18" +netifaces = ">=0.10.4" +"oslo.i18n" = ">=3.15.3" +packaging = ">=20.4" +pyparsing = ">=2.1.0" +pytz = ">=2013.6" +tzdata = ">=2022.4" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "oss2" +version = "2.18.1" +description = "Aliyun OSS (Object Storage Service) SDK" +optional = false +python-versions = "*" +files = [ + {file = "oss2-2.18.1.tar.gz", hash = "sha256:5a901f6c0f3ac42f792e16a1e1c04e60f34e6cc9eb2bc4c0c3ce6e7bda2da4cc"}, +] + +[package.dependencies] +aliyun-python-sdk-core = ">=2.13.12" +aliyun-python-sdk-kms = ">=2.4.1" +crcmod = ">=1.7" +pycryptodome = ">=3.4.7" +requests = "!=2.9.0" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "paramiko" +version = "3.2.0" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "paramiko-3.2.0-py3-none-any.whl", hash = "sha256:df0f9dd8903bc50f2e10580af687f3015bf592a377cd438d2ec9546467a14eb8"}, + {file = "paramiko-3.2.0.tar.gz", hash = "sha256:93cdce625a8a1dc12204439d45033f3261bdb2c201648cfcdc06f9fd0f94ec29"}, +] + +[package.dependencies] +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +optional = false +python-versions = "*" +files = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] +totp = ["cryptography"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pbr" +version = "5.11.1" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, + {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "phonenumbers" +version = "8.13.17" +description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +optional = false +python-versions = "*" +files = [ + {file = "phonenumbers-8.13.17-py2.py3-none-any.whl", hash = "sha256:e8ffd86b2e0b844fd6189fdb0927dbe8707cb03b59102cba5532b3ea305cc1bd"}, + {file = "phonenumbers-8.13.17.tar.gz", hash = "sha256:89671217c706cbaa3ced101deefafa779836feac3e059434d886ac31f09f32c0"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pillow" +version = "10.0.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, + {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, + {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, + {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, + {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, + {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, + {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, + {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, + {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, + {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "portalocker" +version = "2.7.0" +description = "Wraps the portalocker recipe for easy usage" +optional = false +python-versions = ">=3.5" +files = [ + {file = "portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983"}, + {file = "portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "prettytable" +version = "3.8.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "prettytable-3.8.0-py3-none-any.whl", hash = "sha256:03481bca25ae0c28958c8cd6ac5165c159ce89f7ccde04d5c899b24b68bb13b7"}, + {file = "prettytable-3.8.0.tar.gz", hash = "sha256:031eae6a9102017e8c7c7906460d150b7ed78b20fd1d8c8be4edaf88556c07ce"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, + {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, +] + +[package.extras] +twisted = ["twisted"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "proto-plus" +version = "1.22.3" +description = "Beautiful, Pythonic protocol buffers." +optional = false +python-versions = ">=3.6" +files = [ + {file = "proto-plus-1.22.3.tar.gz", hash = "sha256:fdcd09713cbd42480740d2fe29c990f7fbd885a67efc328aa8be6ee3e9f76a6b"}, + {file = "proto_plus-1.22.3-py3-none-any.whl", hash = "sha256:a49cd903bc0b6ab41f76bf65510439d56ca76f868adf0274e738bfdd096894df"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<5.0.0dev" + +[package.extras] +testing = ["google-api-core[grpc] (>=1.31.5)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "protobuf" +version = "4.24.0" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.0-cp310-abi3-win32.whl", hash = "sha256:81cb9c4621d2abfe181154354f63af1c41b00a4882fb230b4425cbaed65e8f52"}, + {file = "protobuf-4.24.0-cp310-abi3-win_amd64.whl", hash = "sha256:6c817cf4a26334625a1904b38523d1b343ff8b637d75d2c8790189a4064e51c3"}, + {file = "protobuf-4.24.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ae97b5de10f25b7a443b40427033e545a32b0e9dda17bcd8330d70033379b3e5"}, + {file = "protobuf-4.24.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:567fe6b0647494845d0849e3d5b260bfdd75692bf452cdc9cb660d12457c055d"}, + {file = "protobuf-4.24.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:a6b1ca92ccabfd9903c0c7dde8876221dc7d8d87ad5c42e095cc11b15d3569c7"}, + {file = "protobuf-4.24.0-cp37-cp37m-win32.whl", hash = "sha256:a38400a692fd0c6944c3c58837d112f135eb1ed6cdad5ca6c5763336e74f1a04"}, + {file = "protobuf-4.24.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5ab19ee50037d4b663c02218a811a5e1e7bb30940c79aac385b96e7a4f9daa61"}, + {file = "protobuf-4.24.0-cp38-cp38-win32.whl", hash = "sha256:e8834ef0b4c88666ebb7c7ec18045aa0f4325481d724daa624a4cf9f28134653"}, + {file = "protobuf-4.24.0-cp38-cp38-win_amd64.whl", hash = "sha256:8bb52a2be32db82ddc623aefcedfe1e0eb51da60e18fcc908fb8885c81d72109"}, + {file = "protobuf-4.24.0-cp39-cp39-win32.whl", hash = "sha256:ae7a1835721086013de193311df858bc12cd247abe4ef9710b715d930b95b33e"}, + {file = "protobuf-4.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:44825e963008f8ea0d26c51911c30d3e82e122997c3c4568fd0385dd7bacaedf"}, + {file = "protobuf-4.24.0-py3-none-any.whl", hash = "sha256:82e6e9ebdd15b8200e8423676eab38b774624d6a1ad696a60d86a2ac93f18201"}, + {file = "protobuf-4.24.0.tar.gz", hash = "sha256:5d0ceb9de6e08311832169e601d1fc71bd8e8c779f3ee38a97a78554945ecb85"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "psycopg2" +version = "2.9.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, + {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, + {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, + {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, + {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, + {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, + {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, + {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, + {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "psycopg2-binary" +version = "2.9.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win32.whl", hash = "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win32.whl", hash = "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win32.whl", hash = "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyasn1" +version = "0.5.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, + {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pycryptodome" +version = "3.18.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, + {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pycryptodomex" +version = "3.18.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodomex-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-win32.whl", hash = "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278"}, + {file = "pycryptodomex-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a"}, + {file = "pycryptodomex-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470"}, + {file = "pycryptodomex-3.18.0.tar.gz", hash = "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyexcel" +version = "0.7.0" +description = "A wrapper library that provides one API to read, manipulate and writedata in different excel formats" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyexcel-0.7.0-py2.py3-none-any.whl", hash = "sha256:ddc6904512bfa2ecda509fb3b58229bb30db14498632fd9e7a5ba7bbfb02ed1b"}, + {file = "pyexcel-0.7.0.tar.gz", hash = "sha256:fbf0eee5d93b96cef6f19a9f00703f22c0a64f19728d91b95428009a52129709"}, +] + +[package.dependencies] +chardet = "*" +lml = ">=0.0.4" +pyexcel-io = ">=0.6.2" +texttable = ">=0.8.2" + +[package.extras] +ods = ["pyexcel-ods3 (>=0.6.0)"] +xls = ["pyexcel-xls (>=0.6.0)"] +xlsx = ["pyexcel-xlsx (>=0.6.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyexcel-io" +version = "0.6.6" +description = "A python library to read and write structured data in csv, zipped csvformat and to/from databases" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyexcel-io-0.6.6.tar.gz", hash = "sha256:f6084bf1afa5fbf4c61cf7df44370fa513821af188b02e3e19b5efb66d8a969f"}, + {file = "pyexcel_io-0.6.6-py2.py3-none-any.whl", hash = "sha256:19ff1d599a8a6c0982e4181ef86aa50e1f8d231410fa7e0e204d62e37551c1d6"}, +] + +[package.dependencies] +lml = ">=0.0.4" + +[package.extras] +ods = ["pyexcel-ods3 (>=0.6.0)"] +xls = ["pyexcel-xls (>=0.6.0)"] +xlsx = ["pyexcel-xlsx (>=0.6.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyexcel-xlsx" +version = "0.6.0" +description = "A wrapper library to read, manipulate and write data in xlsx and xlsmformat" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyexcel-xlsx-0.6.0.tar.gz", hash = "sha256:55754f764252461aca6871db203f4bd1370ec877828e305e6be1de5f9aa6a79d"}, + {file = "pyexcel_xlsx-0.6.0-py2.py3-none-any.whl", hash = "sha256:16530f96a77c97ebcba7941517d2756ac52d3ce2903d81eecd7f300778d5242a"}, +] + +[package.dependencies] +openpyxl = ">=2.6.1" +pyexcel-io = ">=0.6.2" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyfreerdp" +version = "0.0.1" +description = "" +optional = false +python-versions = "*" +files = [ + {file = "pyfreerdp-0.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74b4e757d879b1d01c0bd7e1f91c17ddc007b941b92766f11077696546758490"}, + {file = "pyfreerdp-0.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c73dc9c87c185cd009c00561102a4727bb0cdff69e69eeb9af02a87b1ed627a"}, + {file = "pyfreerdp-0.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363a9caea1b56f82726eea42bc42f47750440867ce1958e3855f5a01aed3d23a"}, + {file = "pyfreerdp-0.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:310a8471afb533d0ab4ff1fdb10c54106884883ed727e1ad6b61491a7fed466f"}, + {file = "pyfreerdp-0.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c869ca101e7e262dfa536472e3ac192c89999351f682328bfbcf056daa98aa99"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a48ac00d35a2c2fd7bacbbd93b9753995d7b80f3202b2cacd35e1ece3154cd9"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f3c7c635316ab2ab8f0379f146b4fdec6636123ed45c0ef6edce6f9d615bcbba"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2edc5d938f2bbb337b70c46413a3a578257daf852211a4fb71ad592c57870436"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee1439e6fc82ed5973ec098eb47c532abdb8de6d48f36b736cc39855f76df217"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0ea400aaff3c00fa6b2fdcdf52696b27cfe16ce83feeffc22969eabebacfde1d"}, + {file = "pyfreerdp-0.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d5f3340086956bedbf8cf0b43bb974e516df1730f80e63e4914fd0c6001e17f"}, + {file = "pyfreerdp-0.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f205f4d29da3bb118681bbc9ae732fb607fc86da210b04b4916a711fa1b67a9d"}, + {file = "pyfreerdp-0.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d424c70a36280f5445b6d213f947f3fffca1142a4414e5b3bb56c870f114397"}, + {file = "pyfreerdp-0.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:38eafc6a60759a25d97ae45f030a0c4e6c8c8a018541fc2076115cf302fd9475"}, + {file = "pyfreerdp-0.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9b26a9ff4e25d04631330da16f3bdad2e805948edb3367f175b5db4cc6eed573"}, + {file = "pyfreerdp-0.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:414ede648e851be7fb0b8bd7b2f38390c5561ef73ea2f15bce2689fbbdaecb8b"}, + {file = "pyfreerdp-0.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cec04cce9aa23268a0e8b1de78c65708b46a9fb8b7664f7bc00932761eed359"}, + {file = "pyfreerdp-0.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f31d916bcadc015c775167895c823be8909aff8286847bc61f1162dd751fc0"}, + {file = "pyfreerdp-0.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:352d30fe3d7f4323b37f474faaed34b21ea3f230a53911e834ec5a51e9cc747b"}, + {file = "pyfreerdp-0.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd3e4cf7f24fb8a3eef4bf2e1147d92437e3298027280eef29a6d08c84b3e7dd"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ba8c23643746d9638469c7a8981f9d57a9bdf068d03fa2c32c28728011db7cc"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:05f0f16dc1621cfbd3da6b3f8acd292dcf09ac2cde3a578aec7af359a65174b9"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4f577beba135c1b603e0cc3b1a71fbfc53bdb53a5aeedc7650f1120a5d52d14"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c8311d355b54cdfadccc2dba7c9d8962aa8179f4c5a852ecb3a33bc9a6f0ee2"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:51ecfecc1a8a40a2461a5b4ff105bc92ef86a57fd6364c3c309c6fe7c66fb5e7"}, + {file = "pyfreerdp-0.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:48cf5c73d98d3f8edaf229c78884828652242036484dff3e61974bf1bce852a3"}, + {file = "pyfreerdp-0.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3adaaf7bb2e32c2288a4bb939130fa0a3afb5d87a11c46c2d6f882c051ee43e"}, + {file = "pyfreerdp-0.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2127d10afa600d691e4b4795f3c6e70cf227e9a65b536bc0ec096f93eb92b4dd"}, + {file = "pyfreerdp-0.0.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed7f0413e4e5c0c03c01f9bcf24eb3fe2b0223fc84947917352421fbd548de5"}, + {file = "pyfreerdp-0.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a91c509fe62ef74341e5ef66b3ac8530f989df87cf5477945de2eb8984522f"}, + {file = "pyfreerdp-0.0.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:509e1643fc87c597ec5bc1ab9505e2839b5e9ca049b5af95a3d70b828e8c4bfd"}, + {file = "pyfreerdp-0.0.1.tar.gz", hash = "sha256:946eca882741e6802794185cbdd47b49626c3181bce99dbd27c5746563c13d43"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyhcl" +version = "0.4.4" +description = "HCL configuration parser for python" +optional = false +python-versions = "*" +files = [ + {file = "pyhcl-0.4.4.tar.gz", hash = "sha256:2d9b9dcdf1023d812bfed561ba72c99104c5b3f52e558d595130a44ce081b003"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyjwkest" +version = "1.4.2" +description = "Python implementation of JWT, JWE, JWS and JWK" +optional = false +python-versions = "*" +files = [ + {file = "pyjwkest-1.4.2.tar.gz", hash = "sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222"}, +] + +[package.dependencies] +future = "*" +pycryptodomex = "*" +requests = "*" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pymongo" +version = "4.4.1" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bbdd6c719cc2ea440d7245ba71ecdda507275071753c6ffe9c8232647246f575"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:a438508dd8007a4a724601c3790db46fe0edc3d7d172acafc5f148ceb4a07815"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3a350d03959f9d5b7f2ea0621f5bb2eb3927b8fc1c4031d12cfd3949839d4f66"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:e6d5d2c97c35f83dc65ccd5d64c7ed16eba6d9403e3744e847aee648c432f0bb"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:1944b16ffef3573ae064196460de43eb1c865a64fed23551b5eac1951d80acca"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:912b0fdc16500125dc1837be8b13c99d6782d93d6cd099d0e090e2aca0b6d100"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:d1b1c8eb21de4cb5e296614e8b775d5ecf9c56b7d3c6000f4bfdb17f9e244e72"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3b508e0de613b906267f2c484cb5e9afd3a64680e1af23386ca8f99a29c6145"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f41feb8cf429799ac43ed34504839954aa7d907f8bd9ecb52ed5ff0d2ea84245"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1897123c4bede1af0c264a3bc389a2505bae50d85e4f211288d352928c02d017"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c4bcd285bf0f5272d50628e4ea3989738e3af1251b2dd7bf50da2d593f3a56"}, + {file = "pymongo-4.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:995b868ccc9df8d36cb28142363e3911846fe9f43348d942951f60cdd7f62224"}, + {file = "pymongo-4.4.1-cp310-cp310-win32.whl", hash = "sha256:a5198beca36778f19a98b56f541a0529502046bc867b352dda5b6322e1ddc4fd"}, + {file = "pymongo-4.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:a86d20210c9805a032cda14225087ec483613aff0955327c7871a3c980562c5b"}, + {file = "pymongo-4.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5a2a1da505ea78787b0382c92dc21a45d19918014394b220c4734857e9c73694"}, + {file = "pymongo-4.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35545583396684ea70a0b005034a469bf3f447732396e5b3d50bec94890b8d5c"}, + {file = "pymongo-4.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5248fdf7244a5e976279fe154d116c73f6206e0be71074ea9d9b1e73b5893dd5"}, + {file = "pymongo-4.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44381b817eeb47a41bbfbd279594a7fb21017e0e3e15550eb0fd3758333097f3"}, + {file = "pymongo-4.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f0bd25de90b804cc95e548f55f430df2b47f242a4d7bbce486db62f3b3c981f"}, + {file = "pymongo-4.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d67f4029c57b36a0278aeae044ce382752c078c7625cef71b5e2cf3e576961f9"}, + {file = "pymongo-4.4.1-cp311-cp311-win32.whl", hash = "sha256:8082eef0d8c711c9c272906fa469965e52b44dbdb8a589b54857b1351dc2e511"}, + {file = "pymongo-4.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:980da627edc1275896d7d4670596433ec66e1f452ec244e07bbb2f91c955b581"}, + {file = "pymongo-4.4.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:6cf08997d3ecf9a1eabe12c35aa82a5c588f53fac054ed46fe5c16a0a20ea43d"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a6750449759f0a83adc9df3a469483a8c3eef077490b76f30c03dc8f7a4b1d66"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:efa67f46c1678df541e8f41247d22430905f80a3296d9c914aaa793f2c9fa1db"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9a5e16a32fb1000c72a8734ddd8ae291974deb5d38d40d1bdd01dbe4024eeb0"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:36b0b06c6e830d190215fced82872e5fd8239771063afa206f9adc09574018a3"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4ec9c6d4547c93cf39787c249969f7348ef6c4d36439af10d57b5ee65f3dfbf9"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:5368801ca6b66aacc5cc013258f11899cd6a4c3bb28cec435dd67f835905e9d2"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:91848d555155ad4594de5e575b6452adc471bc7bc4b4d2b1f4f15a78a8af7843"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0f08a2dba1469252462c414b66cb416c7f7295f2c85e50f735122a251fcb131"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2fe4bbf2b2c91e4690b5658b0fbb98ca6e0a8fba9ececd65b4e7d2d1df3e9b01"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e307d67641d0e2f7e7d6ee3dad880d090dace96cc1d95c99d15bd9f545a1168"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d43634594f2486cc9bb604a1dc0914234878c4faf6604574a25260cb2faaa06"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef0e3279e72cccc3dc7be75b12b1e54cc938d7ce13f5f22bea844b9d9d5fecd4"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05935f5a4bbae0a99482147588351b7b17999f4a4e6e55abfb74367ac58c0634"}, + {file = "pymongo-4.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:854d92d2437e3496742e17342496e1f3d9efb22455501fd6010aa3658138e457"}, + {file = "pymongo-4.4.1-cp37-cp37m-win32.whl", hash = "sha256:ddffc0c6d0e92cf43dc6c47639d1ef9ab3c280db2998a33dbb9953bd864841e1"}, + {file = "pymongo-4.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2259302d8ab51cd56c3d9d5cca325977e35a0bb3a15a297ec124d2da56c214f7"}, + {file = "pymongo-4.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:262a4073d2ee0654f0314ef4d9aab1d8c13dc8dae5c102312e152c02bfa7bdb7"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:022c91e2a41eefbcddc844c534520a13c6f613666c37b9fb9ed039eff47bd2e4"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a0d326c3ba989091026fbc4827638dc169abdbb0c0bbe593716921543f530af6"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5a1e5b931bf729b2eacd720a0e40201c2d5ed0e2bada60863f19b069bb5016c4"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:54d0b8b6f2548e15b09232827d9ba8e03a599c9a30534f7f2c7bae79df2d1f91"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e426e213ab07a73f8759ab8d69e87d05d7a60b3ecbf7673965948dcf8ebc1c9f"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:53831effe4dc0243231a944dfbd87896e42b1cf081776930de5cc74371405e3b"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:977c34b5b0b50bd169fbca1a4dd06fbfdfd8ac47734fdc3473532c10098e16ce"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab52db4d3aa3b73bcf920fb375dbea63bf0df0cb4bdb38c5a0a69e16568cc21"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bb935789276422d8875f051837356edfccdb886e673444d91e4941a8142bd48"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d45243ff4800320c842c45e01c91037e281840e8c6ed2949ed82a70f55c0e6a"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d6d2b7e14bb6bc052f6cba0c1cf4d47a2b49c56ea1ed0f960a02bc9afaefb2"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85b92b3828b2c923ed448f820c147ee51fa4566e35c9bf88415586eb0192ced2"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f345380f6d6d6d1dc6db9fa5c8480c439ea79553b71a2cbe3030a1f20676595"}, + {file = "pymongo-4.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dcc64747b628a96bcfc6405c42acae3762c85d8ae8c1ce18834b8151cad7486"}, + {file = "pymongo-4.4.1-cp38-cp38-win32.whl", hash = "sha256:ebe1683ec85d8bca389183d01ecf4640c797d6f22e6dac3453a6c492920d5ec3"}, + {file = "pymongo-4.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:58c492e28057838792bed67875f982ffbd3c9ceb67341cc03811859fddb8efbf"}, + {file = "pymongo-4.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aed21b3142311ad139629c4e101b54f25447ec40d6f42c72ad5c1a6f4f851f3a"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98764ae13de0ab80ba824ca0b84177006dec51f48dfb7c944d8fa78ab645c67f"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7b7127bb35f10d974ec1bd5573389e99054c558b821c9f23bb8ff94e7ae6e612"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:48409bac0f6a62825c306c9a124698df920afdc396132908a8e88b466925a248"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:55b6ebeeabe32a9d2e38eeb90f07c020cb91098b34b5fca42ff3991cb6e6e621"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:4e6a70c9d437b043fb07eef1796060f476359e5b7d8e23baa49f1a70379d6543"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:0bdbbcc1ef3a56347630c57eda5cd9536bdbdb82754b3108c66cbc51b5233dfb"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:04ec1c5451ad358fdbff28ddc6e8a3d1b5f62178d38cd08007a251bc3f59445a"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a7739bcebdbeb5648edb15af00fd38f2ab5de20851a1341d229494a638284cc"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02dba4ea2a6f22de4b50864d3957a0110b75d3eeb40aeab0b0ff64bcb5a063e6"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:884a35c0740744a48f67210692841581ab83a4608d3a031e7125022989ef65f8"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2aab6d1cff00d68212eca75d2260980202b14038d9298fed7d5c455fe3285c7c"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae1f85223193f249320f695eec4242cdcc311357f5f5064c2e72cfd18017e8ee"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b25d2ccdb2901655cc56c0fc978c5ddb35029c46bfd30d182d0e23fffd55b14b"}, + {file = "pymongo-4.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:334d41649f157c56a47fb289bae3b647a867c1a74f5f3a8a371fb361580bd9d3"}, + {file = "pymongo-4.4.1-cp39-cp39-win32.whl", hash = "sha256:c409e5888a94a3ff99783fffd9477128ffab8416e3f8b2c633993eecdcd5c267"}, + {file = "pymongo-4.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3681caf37edbe05f72f0d351e4a6cb5874ec7ab5eeb99df3a277dbf110093739"}, + {file = "pymongo-4.4.1.tar.gz", hash = "sha256:a4df87dbbd03ac6372d24f2a8054b4dc33de497d5227b50ec649f436ad574284"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["pymongo-auth-aws (<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos"] +ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +zstd = ["zstandard"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pympler" +version = "1.0.1" +description = "A development tool to measure, monitor and analyze the memory behavior of Python objects." +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pympler-1.0.1-py3-none-any.whl", hash = "sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d"}, + {file = "Pympler-1.0.1.tar.gz", hash = "sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pymssql" +version = "2.2.8" +description = "DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)" +optional = false +python-versions = "*" +files = [ + {file = "pymssql-2.2.8-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bfd7b8edef78097ccd3f52ac3f3a5c3cf0019f8a280f306cacbbb165caaf63"}, + {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:049f2e3de919e8e02504780a21ebbf235e21ca8ed5c7538c5b6e705aa6c43d8c"}, + {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd86d8e3e346e34f3f03d12e333747b53a1daa74374a727f4714d5b82ee0dd5"}, + {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:508226a0df7cb6faeda9f8e84e85743690ca427d7b27af9a73d75fcf0c1eef6e"}, + {file = "pymssql-2.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:47859887adeaf184766b5e0bc845dd23611f3808f9521552063bb36eabc10092"}, + {file = "pymssql-2.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d873e553374d5b1c57fe1c43bb75e3bcc2920678db1ef26f6bfed396c7d21b30"}, + {file = "pymssql-2.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf31b8b76634c826a91f9999e15b7bfb0c051a0f53b319fd56481a67e5b903bb"}, + {file = "pymssql-2.2.8-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:821945c2214fe666fd456c61e09a29a00e7719c9e136c801bffb3a254e9c579b"}, + {file = "pymssql-2.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:cc85b609b4e60eac25fa38bbac1ff854fd2c2a276e0ca4a3614c6f97efb644bb"}, + {file = "pymssql-2.2.8-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ebe7f64d5278d807f14bea08951e02512bfbc6219fd4d4f15bb45ded885cf3d4"}, + {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:253af3d39fc0235627966817262d5c4c94ad09dcbea59664748063470048c29c"}, + {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c9d109df536dc5f7dd851a88d285a4c9cb12a9314b621625f4f5ab1197eb312"}, + {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:358d5acf0298d6618edf7fedc4ce3dc8fb5ce8a9db85e7332d5196d29d841821"}, + {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:63e1be8936372c07aee2405203ee0161ce76b03893cafe3d46841be9886f5ffe"}, + {file = "pymssql-2.2.8-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:381d8a47c4665d99f114849bed23bcba1922c9d005accc3ac19cee8a1d3522dc"}, + {file = "pymssql-2.2.8-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4f365033c9b4263b74b8a332bbdf2d7d8d7230f05805439b4f3fbf0a0164acfe"}, + {file = "pymssql-2.2.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03903bdf23a2aac26e9b772b3998efeba079fcb6fcfa6df7abc614e9afa14af0"}, + {file = "pymssql-2.2.8-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:5c83208138f87942c5f08aa50c5fb8d89b7f15340cde58a77b08f49df277e134"}, + {file = "pymssql-2.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7e4538e85d7b5fb3867636391f91e9e18ac2e0aef660d25e97268e04339f2c36"}, + {file = "pymssql-2.2.8-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:e920d6f805a525f19e770e48326a5f96b83d7b8dfd093f5b7015b54ef84bcf4c"}, + {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2446645eb8684c0cb246a3294110455dd89a29608dfa7a58ea88aa42aa1cf005"}, + {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3906993300650844ec140aa58772c0f5f3e9e9d5709c061334fd1551acdcf066"}, + {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7309c7352e4a87c9995c3183ebfe0ff4135e955bb759109637673c61c9f0ca8d"}, + {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9b8d603cc1ec7ae585c5a409a1d45e8da067970c79dd550d45c238ae0aa0f79f"}, + {file = "pymssql-2.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:293cb4d0339e221d877d6b19a1905082b658f0100a1e2ccc9dda10de58938901"}, + {file = "pymssql-2.2.8-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:895041edd002a2e91d8a4faf0906b6fbfef29d9164bc6beb398421f5927fa40e"}, + {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6b2d9c6d38a416c6f2db36ff1cd8e69f9a5387a46f9f4f612623192e0c9404b1"}, + {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63d6f25cf40fe6a03c49be2d4d337858362b8ab944d6684c268e4990807cf0c"}, + {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c83ad3ad20951f3a94894b354fa5fa9666dcd5ebb4a635dad507c7d1dd545833"}, + {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3933f7f082be74698eea835df51798dab9bc727d94d3d280bffc75ab9265f890"}, + {file = "pymssql-2.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:de313375b90b0f554058992f35c4a4beb3f6ec2f5912d8cd6afb649f95b03a9f"}, + {file = "pymssql-2.2.8.tar.gz", hash = "sha256:9baefbfbd07d0142756e2dfcaa804154361ac5806ab9381350aad4e780c3033e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pymysql" +version = "1.1.0" +description = "Pure Python MySQL Driver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, + {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, +] + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyopenssl" +version = "23.2.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, +] + +[package.dependencies] +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyotp" +version = "2.8.0" +description = "Python One Time Password Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyotp-2.8.0-py3-none-any.whl", hash = "sha256:889d037fdde6accad28531fc62a790f089e5dfd5b638773e9ee004cce074a2e5"}, + {file = "pyotp-2.8.0.tar.gz", hash = "sha256:c2f5e17d9da92d8ec1f7de6331ab08116b9115adbabcba6e208d46fc49a98c5a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyrad" +version = "2.4" +description = "RADIUS tools" +optional = false +python-versions = "*" +files = [ + {file = "pyrad-2.4-py3-none-any.whl", hash = "sha256:233de3aefa383875c5bddfdecfd4819d1b1fbac41aa43f6bebe4f81e63dca363"}, + {file = "pyrad-2.4.tar.gz", hash = "sha256:057de4b7e89d8da57ba782c1bde45c63ebee720ae2c0b0a69beaff15c47e30d9"}, +] + +[package.dependencies] +netaddr = "*" +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyspnego" +version = "0.9.1" +description = "Windows Negotiate Authentication Client and Server" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyspnego-0.9.1-cp310-cp310-win32.whl", hash = "sha256:aa43f00ed1c3b8e16a658613e2557a3ff9bea26acef867705eb4ee7f5e469ac3"}, + {file = "pyspnego-0.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:d031a7fa9c9ab3b67725e35affc90f8e6504518fb3ffe21573504e72b5a2fb5e"}, + {file = "pyspnego-0.9.1-cp311-cp311-win32.whl", hash = "sha256:58a17f7ba17f6cee72149911df6cc785ce7072744a386483957b74c62da654d8"}, + {file = "pyspnego-0.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:e7a92321272e3613c30f55a3324ef6d780bba8b723be8d50c35aac8409fc4028"}, + {file = "pyspnego-0.9.1-cp37-cp37m-win32.whl", hash = "sha256:87a2c23e640f4f6ae3c391d1f56e287b72908080a0e6376f2f365da5a2117dca"}, + {file = "pyspnego-0.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ff4fecf488369d6634afb20f9e56eb3b187fb3c883d6551601bbad4f4badab62"}, + {file = "pyspnego-0.9.1-cp38-cp38-win32.whl", hash = "sha256:7515f00418324809eb1adec0afac93da006c03baba6c6fd1a981b5401b798f56"}, + {file = "pyspnego-0.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:6366e39ba251889e2573c7d037e7feec8af86aea6c3b32f22a8af33bf88265b6"}, + {file = "pyspnego-0.9.1-cp39-cp39-win32.whl", hash = "sha256:07417f90328fb57c19a383e59b65060d4fc101441b74c34dbe4ba860775b0a3a"}, + {file = "pyspnego-0.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:4b79d5ba55ada38833d2421c44ed30a7313c00cbf34fa919dd106049616307d3"}, + {file = "pyspnego-0.9.1-py3-none-any.whl", hash = "sha256:c6aebe1fdc3990be2c137f3c3e041062243871b8161bc7adf4d269c3b6deda35"}, + {file = "pyspnego-0.9.1.tar.gz", hash = "sha256:6eea64f511bdfa192c2f80593ddf124258b0ea560327468953d18420e0ab3597"}, +] + +[package.dependencies] +cryptography = "*" + +[package.extras] +kerberos = ["gssapi (>=1.6.0)", "krb5 (>=0.3.0)"] +yaml = ["ruamel.yaml"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-cas" +version = "1.6.0" +description = "Python CAS client library" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "python-cas-1.6.0.tar.gz", hash = "sha256:b8f1dcb1b6c56b3ff4f86bbef47bcdfcf932061ccd4812ae35e3f63954dfdb28"}, + {file = "python_cas-1.6.0-py2.py3-none-any.whl", hash = "sha256:2abc0dae93c3b14097999fb7062f23cd09bc9f4e33d93f03c0cc040bd71ed50e"}, +] + +[package.dependencies] +lxml = ">=3.4" +requests = ">=2.11.1" +six = ">=1.10.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-crontab" +version = "3.0.0" +description = "Python Crontab API" +optional = false +python-versions = "*" +files = [ + {file = "python-crontab-3.0.0.tar.gz", hash = "sha256:79fb7465039ddfd4fb93d072d6ee0d45c1ac8bf1597f0686ea14fd4361dba379"}, + {file = "python_crontab-3.0.0-py3-none-any.whl", hash = "sha256:6d5ba3c190ec76e4d252989a1644fcb233dbf53fbc8fceeb9febe1657b9fb1d4"}, +] + +[package.dependencies] +python-dateutil = "*" + +[package.extras] +cron-description = ["cron-descriptor"] +cron-schedule = ["croniter"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-daemon" +version = "3.0.1" +description = "Library to implement a well-behaved Unix daemon process." +optional = false +python-versions = ">=3" +files = [ + {file = "python-daemon-3.0.1.tar.gz", hash = "sha256:6c57452372f7eaff40934a1c03ad1826bf5e793558e87fef49131e6464b4dae5"}, + {file = "python_daemon-3.0.1-py3-none-any.whl", hash = "sha256:42bb848a3260a027fa71ad47ecd959e471327cb34da5965962edd5926229f341"}, +] + +[package.dependencies] +docutils = "*" +lockfile = ">=0.10" +setuptools = ">=62.4.0" + +[package.extras] +devel = ["coverage", "docutils", "isort", "testscenarios (>=0.4)", "testtools", "twine"] +test = ["coverage", "docutils", "testscenarios (>=0.4)", "testtools"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-keystoneclient" +version = "5.1.0" +description = "Client Library for OpenStack Identity" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-keystoneclient-5.1.0.tar.gz", hash = "sha256:ba09bdfeafa2a2196450a327cd3f46f2a8a9dd9d21b838f8cb9b17a99740c6a1"}, + {file = "python_keystoneclient-5.1.0-py3-none-any.whl", hash = "sha256:9c2e0b1700f553ca625e987f4cd8ef62d7a27ad88c5104e96e16904d2ae1d918"}, +] + +[package.dependencies] +debtcollector = ">=1.2.0" +keystoneauth1 = ">=3.4.0" +"oslo.config" = ">=5.2.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +packaging = ">=20.4" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +requests = ">=2.14.2" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-ldap" +version = "3.4.3" +description = "Python modules for implementing LDAP clients" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"}, +] + +[package.dependencies] +pyasn1 = ">=0.3.7" +pyasn1_modules = ">=0.1.5" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-nmap" +version = "0.7.1" +description = "This is a python class to use nmap and access scan results from python3" +optional = false +python-versions = "*" +files = [ + {file = "python-nmap-0.7.1.tar.gz", hash = "sha256:f75af6b91dd8e3b0c31f869db32163f62ada686945e5b7c25f84bc0f7fad3b64"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-novaclient" +version = "18.3.0" +description = "Client library for OpenStack Compute API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-novaclient-18.3.0.tar.gz", hash = "sha256:50f7587c7a2b2528f73505817f9437ac5c1d04d576e9be264d2deeffdb745a76"}, + {file = "python_novaclient-18.3.0-py3-none-any.whl", hash = "sha256:e5ed24feb6f8bd46ae60d8c6a258295bc0ba0f0f080308c995e96c882ca2e5e3"}, +] + +[package.dependencies] +iso8601 = ">=0.1.11" +keystoneauth1 = ">=3.5.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.2" +stevedore = ">=2.0.1" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python-redis-lock" +version = "4.0.0" +description = "Lock context manager implemented via redis SETNX/BLPOP." +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-redis-lock-4.0.0.tar.gz", hash = "sha256:4abd0bcf49136acad66727bf5486dd2494078ca55e49efa693f794077319091a"}, + {file = "python_redis_lock-4.0.0-py3-none-any.whl", hash = "sha256:ff786e587569415f31e64ca9337fce47c4206e832776e9e42b83bfb9ee1af4bd"}, +] + +[package.dependencies] +redis = ">=2.10.0" + +[package.extras] +django = ["django-redis (>=3.8.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "python3-saml" +version = "1.15.0" +description = "Saml Python Toolkit. Add SAML support to your Python software using this library" +optional = false +python-versions = "*" +files = [ + {file = "python3-saml-1.15.0.tar.gz", hash = "sha256:8c68b31739471faffb93dcdfe3bcab375b9d6a0459cab7fa9cb0d7d874ecf0b0"}, + {file = "python3_saml-1.15.0-py2-none-any.whl", hash = "sha256:3a76a17c6a2384313c5cdb450ea8b2e6d098f30836ee3dddbfe8e870903971d2"}, + {file = "python3_saml-1.15.0-py3-none-any.whl", hash = "sha256:cc0458351ddaa08270ebe29ffaf9e1a41dbd285ba43a176cbd70907af5944c66"}, +] + +[package.dependencies] +isodate = ">=0.6.1" +lxml = ">=4.6.5,<4.7.0 || >4.7.0" +xmlsec = ">=1.3.9" + +[package.extras] +test = ["coverage (>=4.5.2)", "flake8 (>=3.6.0,<=5.0.0)", "freezegun (>=0.3.11,<=1.1.0)", "pytest (>=4.6)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyvmomi" +version = "8.0.1.0.2" +description = "VMware vSphere Python SDK" +optional = false +python-versions = ">=2.7.9, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyvmomi-8.0.1.0.2.tar.gz", hash = "sha256:8aeccd9e4adbbecba2512d7a94488edec44ce481d7072000f5fc524e29c395f3"}, +] + +[package.dependencies] +six = ">=1.7.3" + +[package.extras] +sso = ["lxml", "pyOpenSSL", "pywin32"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pywinrm" +version = "0.4.3" +description = "Python library for Windows Remote Management" +optional = false +python-versions = "*" +files = [ + {file = "pywinrm-0.4.3-py2.py3-none-any.whl", hash = "sha256:c476c1e20dd0875da6fe4684c4d8e242dd537025907c2f11e1990c5aee5c9526"}, + {file = "pywinrm-0.4.3.tar.gz", hash = "sha256:995674bf5ac64b2562c9c56540473109e530d36bde10c262d5a5296121ad5565"}, +] + +[package.dependencies] +requests = ">=2.9.1" +requests-ntlm = ">=1.1.0" +six = "*" +xmltodict = "*" + +[package.extras] +credssp = ["requests-credssp (>=1.0.0)"] +kerberos = ["pykerberos (>=1.2.1,<2.0.0)", "winkerberos (>=0.5.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "pyzipper" +version = "0.3.6" +description = "AES encryption for zipfile." +optional = false +python-versions = ">=3.4" +files = [ + {file = "pyzipper-0.3.6-py2.py3-none-any.whl", hash = "sha256:6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87"}, + {file = "pyzipper-0.3.6.tar.gz", hash = "sha256:0adca90a00c36a93fbe49bfa8c5add452bfe4ef85a1b8e3638739dd1c7b26bfc"}, +] + +[package.dependencies] +pycryptodomex = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "qingcloud-sdk" +version = "1.2.15" +description = "Software Development Kit for QingCloud." +optional = false +python-versions = "*" +files = [ + {file = "qingcloud-sdk-1.2.15.tar.gz", hash = "sha256:4e0cc3587cc4dea94313917c38334daabdbaa99ed9cbc7500a51485d21b6df78"}, + {file = "qingcloud_sdk-1.2.15-py2-none-any.whl", hash = "sha256:a4fffd8f8fcfe4fab2768a6157fff3170898be17e0387694bfe00b2731002d75"}, +] + +[package.dependencies] +future = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "redis" +version = "4.6.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "requests-ntlm" +version = "1.2.0" +description = "This package allows for HTTP NTLM authentication using the requests library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests_ntlm-1.2.0-py3-none-any.whl", hash = "sha256:b7781090c647308a88b55fb530c7b3705cef45349e70a83b8d6731e7889272a6"}, + {file = "requests_ntlm-1.2.0.tar.gz", hash = "sha256:33c285f5074e317cbdd338d199afa46a7c01132e5c111d36bd415534e9b916a8"}, +] + +[package.dependencies] +cryptography = ">=1.3" +pyspnego = ">=0.1.6" +requests = ">=2.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "resolvelib" +version = "0.8.1" +description = "Resolve abstract dependencies into concrete ones" +optional = false +python-versions = "*" +files = [ + {file = "resolvelib-0.8.1-py2.py3-none-any.whl", hash = "sha256:d9b7907f055c3b3a2cfc56c914ffd940122915826ff5fb5b1de0c99778f4de98"}, + {file = "resolvelib-0.8.1.tar.gz", hash = "sha256:c6ea56732e9fb6fca1b2acc2ccc68a0b6b8c566d8f3e78e0443310ede61dbd37"}, +] + +[package.extras] +examples = ["html5lib", "packaging", "pygraphviz", "requests"] +lint = ["black", "flake8", "isort", "mypy", "types-requests"] +release = ["build", "towncrier", "twine"] +test = ["commentjson", "packaging", "pytest"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "rest-condition" +version = "1.0.3" +description = "Complex permissions flow for django-rest-framework" +optional = false +python-versions = "*" +files = [ + {file = "rest_condition-1.0.3.tar.gz", hash = "sha256:7f47ed11a6519260b3f6248a2b6d6bf127ea7d9e04155fcf1e546bb561295af8"}, +] + +[package.dependencies] +django = ">=1.3" +djangorestframework = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "rfc3986" +version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[package.extras] +idna2008 = ["idna"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "s3transfer" +version = "0.6.1" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.6.1-py3-none-any.whl", hash = "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346"}, + {file = "s3transfer-0.6.1.tar.gz", hash = "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "service-identity" +version = "23.1.0" +description = "Service identity verification for pyOpenSSL & cryptography." +optional = false +python-versions = ">=3.8" +files = [ + {file = "service_identity-23.1.0-py3-none-any.whl", hash = "sha256:87415a691d52fcad954a500cb81f424d0273f8e7e3ee7d766128f4575080f383"}, + {file = "service_identity-23.1.0.tar.gz", hash = "sha256:ecb33cd96307755041e978ab14f8b14e13b40f1fbd525a4dc78f46d2b986431d"}, +] + +[package.dependencies] +attrs = ">=19.1.0" +cryptography = "*" +pyasn1 = "*" +pyasn1-modules = "*" + +[package.extras] +dev = ["pyopenssl", "service-identity[docs,idna,mypy,tests]"] +docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"] +idna = ["idna"] +mypy = ["idna", "mypy", "types-pyopenssl"] +tests = ["coverage[toml] (>=5.0.2)", "pytest"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "setuptools" +version = "68.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"}, + {file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "simplejson" +version = "3.19.1" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "simplejson-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:081ea6305b3b5e84ae7417e7f45956db5ea3872ec497a584ec86c3260cda049e"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f253edf694ce836631b350d758d00a8c4011243d58318fbfbe0dd54a6a839ab4"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:5db86bb82034e055257c8e45228ca3dbce85e38d7bfa84fa7b2838e032a3219c"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:69a8b10a4f81548bc1e06ded0c4a6c9042c0be0d947c53c1ed89703f7e613950"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:58ee5e24d6863b22194020eb62673cf8cc69945fcad6b283919490f6e359f7c5"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:73d0904c2471f317386d4ae5c665b16b5c50ab4f3ee7fd3d3b7651e564ad74b1"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:66d780047c31ff316ee305c3f7550f352d87257c756413632303fc59fef19eac"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd4d50a27b065447c9c399f0bf0a993bd0e6308db8bbbfbc3ea03b41c145775a"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c16ec6a67a5f66ab004190829eeede01c633936375edcad7cbf06d3241e5865"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a963e8dd4d81061cc05b627677c1f6a12e81345111fbdc5708c9f088d752c9"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e78d79b10aa92f40f54178ada2b635c960d24fc6141856b926d82f67e56d169"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad071cd84a636195f35fa71de2186d717db775f94f985232775794d09f8d9061"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e7c70f19405e5f99168077b785fe15fcb5f9b3c0b70b0b5c2757ce294922c8c"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54fca2b26bcd1c403146fd9461d1da76199442297160721b1d63def2a1b17799"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48600a6e0032bed17c20319d91775f1797d39953ccfd68c27f83c8d7fc3b32cb"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:93f5ac30607157a0b2579af59a065bcfaa7fadeb4875bf927a8f8b6739c8d910"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b79642a599740603ca86cf9df54f57a2013c47e1dd4dd2ae4769af0a6816900"}, + {file = "simplejson-3.19.1-cp310-cp310-win32.whl", hash = "sha256:d9f2c27f18a0b94107d57294aab3d06d6046ea843ed4a45cae8bd45756749f3a"}, + {file = "simplejson-3.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:5673d27806085d2a413b3be5f85fad6fca4b7ffd31cfe510bbe65eea52fff571"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:79c748aa61fd8098d0472e776743de20fae2686edb80a24f0f6593a77f74fe86"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:390f4a8ca61d90bcf806c3ad644e05fa5890f5b9a72abdd4ca8430cdc1e386fa"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d61482b5d18181e6bb4810b4a6a24c63a490c3a20e9fbd7876639653e2b30a1a"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2541fdb7467ef9bfad1f55b6c52e8ea52b3ce4a0027d37aff094190a955daa9d"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46133bc7dd45c9953e6ee4852e3de3d5a9a4a03b068bd238935a5c72f0a1ce34"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f96def94576f857abf58e031ce881b5a3fc25cbec64b2bc4824824a8a4367af9"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f14ecca970d825df0d29d5c6736ff27999ee7bdf5510e807f7ad8845f7760ce"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:66389b6b6ee46a94a493a933a26008a1bae0cfadeca176933e7ff6556c0ce998"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22b867205cd258050c2625325fdd9a65f917a5aff22a23387e245ecae4098e78"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c39fa911e4302eb79c804b221ddec775c3da08833c0a9120041dd322789824de"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:65dafe413b15e8895ad42e49210b74a955c9ae65564952b0243a18fb35b986cc"}, + {file = "simplejson-3.19.1-cp311-cp311-win32.whl", hash = "sha256:f05d05d99fce5537d8f7a0af6417a9afa9af3a6c4bb1ba7359c53b6257625fcb"}, + {file = "simplejson-3.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:b46aaf0332a8a9c965310058cf3487d705bf672641d2c43a835625b326689cf4"}, + {file = "simplejson-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b438e5eaa474365f4faaeeef1ec3e8d5b4e7030706e3e3d6b5bee6049732e0e6"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9d614a612ad02492f704fbac636f666fa89295a5d22b4facf2d665fc3b5ea9"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46e89f58e4bed107626edce1cf098da3664a336d01fc78fddcfb1f397f553d44"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ade243fb6f3b57e7bd3b71e90c190cd0f93ec5dce6bf38734a73a2e5fa274f"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed18728b90758d171f0c66c475c24a443ede815cf3f1a91e907b0db0ebc6e508"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6a561320485017ddfc21bd2ed5de2d70184f754f1c9b1947c55f8e2b0163a268"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2098811cd241429c08b7fc5c9e41fcc3f59f27c2e8d1da2ccdcf6c8e340ab507"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8f8d179393e6f0cf6c7c950576892ea6acbcea0a320838c61968ac7046f59228"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:eff87c68058374e45225089e4538c26329a13499bc0104b52b77f8428eed36b2"}, + {file = "simplejson-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:d300773b93eed82f6da138fd1d081dc96fbe53d96000a85e41460fe07c8d8b33"}, + {file = "simplejson-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:37724c634f93e5caaca04458f267836eb9505d897ab3947b52f33b191bf344f3"}, + {file = "simplejson-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74bf802debe68627227ddb665c067eb8c73aa68b2476369237adf55c1161b728"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70128fb92932524c89f373e17221cf9535d7d0c63794955cc3cd5868e19f5d38"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8090e75653ea7db75bc21fa5f7bcf5f7bdf64ea258cbbac45c7065f6324f1b50"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a755f7bfc8adcb94887710dc70cc12a69a454120c6adcc6f251c3f7b46ee6aac"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ccb2c1877bc9b25bc4f4687169caa925ffda605d7569c40e8e95186e9a5e58b"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:919bc5aa4d8094cf8f1371ea9119e5d952f741dc4162810ab714aec948a23fe5"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e333c5b62e93949f5ac27e6758ba53ef6ee4f93e36cc977fe2e3df85c02f6dc4"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3a4480e348000d89cf501b5606415f4d328484bbb431146c2971123d49fd8430"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cb502cde018e93e75dc8fc7bb2d93477ce4f3ac10369f48866c61b5e031db1fd"}, + {file = "simplejson-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:f41915a4e1f059dfad614b187bc06021fefb5fc5255bfe63abf8247d2f7a646a"}, + {file = "simplejson-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3844305bc33d52c4975da07f75b480e17af3558c0d13085eaa6cc2f32882ccf7"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1cb19eacb77adc5a9720244d8d0b5507421d117c7ed4f2f9461424a1829e0ceb"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:926957b278de22797bfc2f004b15297013843b595b3cd7ecd9e37ccb5fad0b72"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0e9a5e66969f7a47dc500e3dba8edc3b45d4eb31efb855c8647700a3493dd8a"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79d46e7e33c3a4ef853a1307b2032cfb7220e1a079d0c65488fbd7118f44935a"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344a5093b71c1b370968d0fbd14d55c9413cb6f0355fdefeb4a322d602d21776"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23fbb7b46d44ed7cbcda689295862851105c7594ae5875dce2a70eeaa498ff86"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3025e7e9ddb48813aec2974e1a7e68e63eac911dd5e0a9568775de107ac79a"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:87b190e6ceec286219bd6b6f13547ca433f977d4600b4e81739e9ac23b5b9ba9"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc935d8322ba9bc7b84f99f40f111809b0473df167bf5b93b89fb719d2c4892b"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3b652579c21af73879d99c8072c31476788c8c26b5565687fd9db154070d852a"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6aa7ca03f25b23b01629b1c7f78e1cd826a66bfb8809f8977a3635be2ec48f1a"}, + {file = "simplejson-3.19.1-cp38-cp38-win32.whl", hash = "sha256:08be5a241fdf67a8e05ac7edbd49b07b638ebe4846b560673e196b2a25c94b92"}, + {file = "simplejson-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:ca56a6c8c8236d6fe19abb67ef08d76f3c3f46712c49a3b6a5352b6e43e8855f"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6424d8229ba62e5dbbc377908cfee9b2edf25abd63b855c21f12ac596cd18e41"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:547ea86ca408a6735335c881a2e6208851027f5bfd678d8f2c92a0f02c7e7330"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:889328873c35cb0b2b4c83cbb83ec52efee5a05e75002e2c0c46c4e42790e83c"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cdb4e544134f305b033ad79ae5c6b9a32e7c58b46d9f55a64e2a883fbbba01"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2b3f06430cbd4fac0dae5b2974d2bf14f71b415fb6de017f498950da8159b1"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d125e754d26c0298715bdc3f8a03a0658ecbe72330be247f4b328d229d8cf67f"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:476c8033abed7b1fd8db62a7600bf18501ce701c1a71179e4ce04ac92c1c5c3c"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199a0bcd792811c252d71e3eabb3d4a132b3e85e43ebd93bfd053d5b59a7e78b"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a79b439a6a77649bb8e2f2644e6c9cc0adb720fc55bed63546edea86e1d5c6c8"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:203412745fed916fc04566ecef3f2b6c872b52f1e7fb3a6a84451b800fb508c1"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca922c61d87b4c38f37aa706520328ffe22d7ac1553ef1cadc73f053a673553"}, + {file = "simplejson-3.19.1-cp39-cp39-win32.whl", hash = "sha256:3e0902c278243d6f7223ba3e6c5738614c971fd9a887fff8feaa8dcf7249c8d4"}, + {file = "simplejson-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:d396b610e77b0c438846607cd56418bfc194973b9886550a98fd6724e8c6cfec"}, + {file = "simplejson-3.19.1-py3-none-any.whl", hash = "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1"}, + {file = "simplejson-3.19.1.tar.gz", hash = "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "sqlparse" +version = "0.4.4" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "sshpubkeys" +version = "3.3.1" +description = "SSH public key parser" +optional = false +python-versions = ">=3" +files = [ + {file = "sshpubkeys-3.3.1-py2.py3-none-any.whl", hash = "sha256:946f76b8fe86704b0e7c56a00d80294e39bc2305999844f079a217885060b1ac"}, + {file = "sshpubkeys-3.3.1.tar.gz", hash = "sha256:3020ed4f8c846849299370fbe98ff4157b0ccc1accec105e07cfa9ae4bb55064"}, +] + +[package.dependencies] +cryptography = ">=2.1.4" +ecdsa = ">=0.13" + +[package.extras] +dev = ["twine", "wheel", "yapf"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "sshtunnel" +version = "0.4.0" +description = "Pure python SSH tunnels" +optional = false +python-versions = "*" +files = [ + {file = "sshtunnel-0.4.0-py2.py3-none-any.whl", hash = "sha256:98e54c26f726ab8bd42b47a3a21fca5c3e60f58956f0f70de2fb8ab0046d0606"}, + {file = "sshtunnel-0.4.0.tar.gz", hash = "sha256:e7cb0ea774db81bf91844db22de72a40aae8f7b0f9bb9ba0f666d474ef6bf9fc"}, +] + +[package.dependencies] +paramiko = ">=2.7.2" + +[package.extras] +build-sphinx = ["sphinx", "sphinxcontrib-napoleon"] +dev = ["check-manifest"] +test = ["tox (>=1.8.1)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "stevedore" +version = "5.1.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.1.0-py3-none-any.whl", hash = "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d"}, + {file = "stevedore-5.1.0.tar.gz", hash = "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "tencentcloud-sdk-python" +version = "3.0.941" +description = "Tencent Cloud SDK for Python" +optional = false +python-versions = "*" +files = [ + {file = "tencentcloud-sdk-python-3.0.941.tar.gz", hash = "sha256:c0fc86ced60b11ff578c94567daaac841606d2414f53806d372b5284351cfe5b"}, + {file = "tencentcloud_sdk_python-3.0.941-py2.py3-none-any.whl", hash = "sha256:aa8a75ab54dcf04edf934bb2b08533b85316907fad3ebfd06571e5591cb8e98f"}, +] + +[package.dependencies] +requests = ">=2.16.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "termcolor" +version = "2.3.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, + {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "texttable" +version = "1.6.7" +description = "module to create simple ASCII tables" +optional = false +python-versions = "*" +files = [ + {file = "texttable-1.6.7-py2.py3-none-any.whl", hash = "sha256:b7b68139aa8a6339d2c320ca8b1dc42d13a7831a346b446cb9eb385f0c76310c"}, + {file = "texttable-1.6.7.tar.gz", hash = "sha256:290348fb67f7746931bcdfd55ac7584ecd4e5b0846ab164333f0794b121760f2"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "tornado" +version = "6.3.3" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, + {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, + {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, + {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "treelib" +version = "1.6.4" +description = "A Python 2/3 implementation of tree structure." +optional = false +python-versions = "*" +files = [ + {file = "treelib-1.6.4-py3-none-any.whl", hash = "sha256:4218f7dded2448dfa6a335888bf68a28430660163e7faf18c6128ec4477d34c0"}, + {file = "treelib-1.6.4.tar.gz", hash = "sha256:1a2e838f6b99e2690bc3d992d5a1f04cdb4af6564bd7688883c23d17257bbb2a"}, +] + +[package.dependencies] +six = "*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "twisted" +version = "22.10.0" +description = "An asynchronous networking framework written in Python" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "Twisted-22.10.0-py3-none-any.whl", hash = "sha256:86c55f712cc5ab6f6d64e02503352464f0400f66d4f079096d744080afcccbd0"}, + {file = "Twisted-22.10.0.tar.gz", hash = "sha256:32acbd40a94f5f46e7b42c109bfae2b302250945561783a8b7a059048f2d4d31"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +Automat = ">=0.8.0" +constantly = ">=15.1" +hyperlink = ">=17.1.1" +idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""} +incremental = ">=21.3.0" +pyopenssl = {version = ">=21.0.0", optional = true, markers = "extra == \"tls\""} +service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} +twisted-iocpsupport = {version = ">=1.0.2,<2", markers = "platform_system == \"Windows\""} +typing-extensions = ">=3.6.5" +"zope.interface" = ">=4.4.2" + +[package.extras] +all-non-platform = ["PyHamcrest (>=1.9.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)"] +conch = ["appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "cryptography (>=2.6)", "pyasn1"] +conch-nacl = ["PyNaCl", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "cryptography (>=2.6)", "pyasn1"] +contextvars = ["contextvars (>=2.4,<3)"] +dev = ["coverage (>=6b1,<7)", "pydoctor (>=22.9.0,<22.10.0)", "pyflakes (>=2.2,<3.0)", "python-subunit (>=1.4,<2.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=5.0,<6)", "sphinx-rtd-theme (>=1.0,<2.0)", "towncrier (>=22.8,<23.0)", "twistedchecker (>=0.7,<1.0)"] +dev-release = ["pydoctor (>=22.9.0,<22.10.0)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "sphinx (>=5.0,<6)", "sphinx-rtd-theme (>=1.0,<2.0)", "towncrier (>=22.8,<23.0)"] +gtk-platform = ["PyHamcrest (>=1.9.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pygobject", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)"] +http2 = ["h2 (>=3.0,<5.0)", "priority (>=1.1.0,<2.0)"] +macos-platform = ["PyHamcrest (>=1.9.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pyobjc-core", "pyobjc-framework-CFNetwork", "pyobjc-framework-Cocoa", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)"] +mypy = ["PyHamcrest (>=1.9.0)", "PyNaCl", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "coverage (>=6b1,<7)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "mypy (==0.930)", "mypy-zope (==0.3.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pydoctor (>=22.9.0,<22.10.0)", "pyflakes (>=2.2,<3.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "python-subunit (>=1.4,<2.0)", "pywin32 (!=226)", "readthedocs-sphinx-ext (>=2.1,<3.0)", "service-identity (>=18.1.0)", "sphinx (>=5.0,<6)", "sphinx-rtd-theme (>=1.0,<2.0)", "towncrier (>=22.8,<23.0)", "twistedchecker (>=0.7,<1.0)", "types-pyOpenSSL", "types-setuptools"] +osx-platform = ["PyHamcrest (>=1.9.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pyobjc-core", "pyobjc-framework-CFNetwork", "pyobjc-framework-Cocoa", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)"] +serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] +test = ["PyHamcrest (>=1.9.0)", "cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.0,<7.0)"] +tls = ["idna (>=2.4)", "pyopenssl (>=21.0.0)", "service-identity (>=18.1.0)"] +windows-platform = ["PyHamcrest (>=1.9.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "contextvars (>=2.4,<3)", "cryptography (>=2.6)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.0,<5.0)", "hypothesis (>=6.0,<7.0)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "pyasn1", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "twisted-iocpsupport" +version = "1.0.4" +description = "An extension for use in the twisted I/O Completion Ports reactor." +optional = false +python-versions = "*" +files = [ + {file = "twisted-iocpsupport-1.0.4.tar.gz", hash = "sha256:858096c0d15e33f15ac157f455d8f86f2f2cdd223963e58c0f682a3af8362d89"}, + {file = "twisted_iocpsupport-1.0.4-cp310-cp310-win32.whl", hash = "sha256:afa2b630797f9ed2f27f3d9f55e3f72b4244911e45a8c82756f44babbf0b243e"}, + {file = "twisted_iocpsupport-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:0058c963c8957bcd3deda62122e89953c9de1e867a274facc9b15dde1a9f31e8"}, + {file = "twisted_iocpsupport-1.0.4-cp311-cp311-win32.whl", hash = "sha256:196f7c7ccad4ba4d1783b1c4e1d1b22d93c04275cd780bf7498d16c77319ad6e"}, + {file = "twisted_iocpsupport-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:4e5f97bcbabdd79cbaa969b63439b89801ea560f11d42b0a387634275c633623"}, + {file = "twisted_iocpsupport-1.0.4-cp312-cp312-win32.whl", hash = "sha256:6081bd7c2f4fcf9b383dcdb3b3385d75a26a7c9d2be25b6950c3d8ea652d2d2d"}, + {file = "twisted_iocpsupport-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:76f7e67cec1f1d097d1f4ed7de41be3d74546e1a4ede0c7d56e775c4dce5dfb0"}, + {file = "twisted_iocpsupport-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:3d306fc4d88a6bcf61ce9d572c738b918578121bfd72891625fab314549024b5"}, + {file = "twisted_iocpsupport-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:391ac4d6002a80e15f35adc4ad6056f4fe1c17ceb0d1f98ba01b0f4f917adfd7"}, + {file = "twisted_iocpsupport-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:0c1b5cf37f0b2d96cc3c9bc86fff16613b9f5d0ca565c96cf1f1fb8cfca4b81c"}, + {file = "twisted_iocpsupport-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:3c5dc11d72519e55f727320e3cee535feedfaee09c0f0765ed1ca7badff1ab3c"}, + {file = "twisted_iocpsupport-1.0.4-cp38-cp38-win32.whl", hash = "sha256:cc86c2ef598c15d824a243c2541c29459881c67fc3c0adb6efe2242f8f0ec3af"}, + {file = "twisted_iocpsupport-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c27985e949b9b1a1fb4c20c71d315c10ea0f93fdf3ccdd4a8c158b5926edd8c8"}, + {file = "twisted_iocpsupport-1.0.4-cp39-cp39-win32.whl", hash = "sha256:e311dfcb470696e3c077249615893cada598e62fa7c4e4ca090167bd2b7d331f"}, + {file = "twisted_iocpsupport-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4574eef1f3bb81501fb02f911298af3c02fe8179c31a33b361dd49180c3e644d"}, + {file = "twisted_iocpsupport-1.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:872747a3b64e2909aee59c803ccd0bceb9b75bf27915520ebd32d69687040fa2"}, + {file = "twisted_iocpsupport-1.0.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c2712b778bacf1db434e3e065adfed3db300754186a29aecac1efae9ef4bcaff"}, + {file = "twisted_iocpsupport-1.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7c66fa0aa4236b27b3c61cb488662d85dae746a6d1c7b0d91cf7aae118445adf"}, + {file = "twisted_iocpsupport-1.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:300437af17396a945a58dcfffd77863303a8b6d9e65c6e81f1d2eed55b50d444"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "txaio" +version = "23.1.1" +description = "Compatibility API between asyncio/Twisted/Trollius" +optional = false +python-versions = ">=3.7" +files = [ + {file = "txaio-23.1.1-py2.py3-none-any.whl", hash = "sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490"}, + {file = "txaio-23.1.1.tar.gz", hash = "sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704"}, +] + +[package.extras] +all = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"] +dev = ["pep8 (>=1.6.2)", "pyenchant (>=1.6.6)", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "sphinx (>=1.2.3)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-spelling (>=2.1.2)", "tox (>=2.1.1)", "tox-gh-actions (>=2.2.0)", "twine (>=1.6.5)", "wheel"] +twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ucloud-sdk-python3" +version = "0.11.50" +description = "UCloud Service Development Kit - Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "ucloud-sdk-python3-0.11.50.tar.gz", hash = "sha256:6e0ed984d6a07489889597c0db5f2d05b3605057c2667dde4f55686f13682866"}, + {file = "ucloud_sdk_python3-0.11.50-py3-none-any.whl", hash = "sha256:e759ebc11e8f782d79dcea10a2597a3a3a7bdf258c6bcf2571ea962bf9c6b7a8"}, +] + +[package.dependencies] +requests = "*" + +[package.extras] +ci = ["flake8 (>=3.6.0)", "pytest (>=4.6)", "pytest-cov", "requests", "requests-mock", "sphinx"] +dev = ["black", "flake8 (>=3.6.0)", "pytest (>=4.6)", "pytest-cov", "requests", "requests-mock", "sphinx"] +doc = ["requests", "sphinx"] +test = ["flake8 (>=3.6.0)", "pytest (>=4.6)", "pytest-cov", "requests", "requests-mock"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "unicodecsv" +version = "0.14.1" +description = "Python2's stdlib csv module is nice, but it doesn't support unicode. This module is a drop-in replacement which *does*." +optional = false +python-versions = "*" +files = [ + {file = "unicodecsv-0.14.1.tar.gz", hash = "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "uvicorn" +version = "0.22.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "vine" +version = "5.0.0" +description = "Promises, promises, promises." +optional = false +python-versions = ">=3.6" +files = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "websocket-client" +version = "1.6.1" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"}, + {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"}, +] + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "xmlsec" +version = "1.3.13" +description = "Python bindings for the XML Security Library" +optional = false +python-versions = ">=3.5" +files = [ + {file = "xmlsec-1.3.13-cp310-cp310-win32.whl", hash = "sha256:2174e8c88555383322d8b7d3927490a92ef72ad72a6ddaf4fa1b96a3f27c3e90"}, + {file = "xmlsec-1.3.13-cp310-cp310-win_amd64.whl", hash = "sha256:46d1daf16a8f4430efca5bb9c6a15776f2671f69f48a1941d6bb335e6f8cb29d"}, + {file = "xmlsec-1.3.13-cp35-cp35m-win32.whl", hash = "sha256:d47062c42775a025aa94fb8b15de97c1db86e301e549d3168157e0b1223d51b1"}, + {file = "xmlsec-1.3.13-cp35-cp35m-win_amd64.whl", hash = "sha256:7c7e8ef52688ddaf5b66750cc8d901f61716f46727014ff012f41d8858cedeb0"}, + {file = "xmlsec-1.3.13-cp36-cp36m-win32.whl", hash = "sha256:1725d70ee2bb2cd8dd66c7a7451be02bb59dc8280103db4f68e731f00135b1e0"}, + {file = "xmlsec-1.3.13-cp36-cp36m-win_amd64.whl", hash = "sha256:1f8c41162152d7086fd459926e61bc7cb2d52ffc829e760bf8b2c221a645d568"}, + {file = "xmlsec-1.3.13-cp37-cp37m-win32.whl", hash = "sha256:ff1c61f296e75cba5bac802d0000bfde09143eed946ced1a5162211867c335f8"}, + {file = "xmlsec-1.3.13-cp37-cp37m-win_amd64.whl", hash = "sha256:d249c0a2bf3ff13a231bca6a588e7d276b3f1e2cf09316b542f470a63855799e"}, + {file = "xmlsec-1.3.13-cp38-cp38-win32.whl", hash = "sha256:56cfcf3487b6ad269eb1fb543c04dee2c101f1bc91e06d6cf7bfab9ac486efd8"}, + {file = "xmlsec-1.3.13-cp38-cp38-win_amd64.whl", hash = "sha256:e6626bece0e97a8598b5df28c27bc6f2ae1e97d29dca3c1a4910a7598a4d1d0f"}, + {file = "xmlsec-1.3.13-cp39-cp39-win32.whl", hash = "sha256:091f23765729df6f3b3a55c8a6a96f9c713fa86e76b86a19cdb756aaa6dc0646"}, + {file = "xmlsec-1.3.13-cp39-cp39-win_amd64.whl", hash = "sha256:5162f416179350587c4ff64737af68a846a9b86f95fd465df4e68b589ce56618"}, + {file = "xmlsec-1.3.13.tar.gz", hash = "sha256:916f5d78e8041f6cd9391abba659da8c94a4fef7196d126d40af1ff417f2cf86"}, +] + +[package.dependencies] +lxml = ">=3.8" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.4" +files = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "zope-interface" +version = "6.0" +description = "Interfaces for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.interface-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990"}, + {file = "zope.interface-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f"}, + {file = "zope.interface-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518"}, + {file = "zope.interface-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb"}, + {file = "zope.interface-6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc"}, + {file = "zope.interface-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f"}, + {file = "zope.interface-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2"}, + {file = "zope.interface-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5"}, + {file = "zope.interface-6.0.tar.gz", hash = "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "0bc2878f163c8b2f48d1103d96314c56f722f10d8f8adf6dfaba0452e6eac368" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..00d18bf29 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,181 @@ +[tool.poetry] +name = "jumpserver" +version = "v3.5" +description = "广受欢迎的开源堡垒机" +authors = ["ibuler "] +license = "GPLv3" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +cython = "3.0.0" +aiofiles = "23.1.0" +amqp = "5.1.1" +ansible-core = { url = "https://github.com/jumpserver/ansible/releases/download/v2.14.1.2/ansible-2.14.1.2.zip" } +ansible = "7.1.0" +ansible-runner = "2.3.3" +asn1crypto = "1.5.1" +bcrypt = "4.0.1" +billiard = "4.1.0" +certifi = "2023.7.22" +cffi = "1.15.1" +chardet = "5.1.0" +configparser = "6.0.0" +decorator = "5.1.1" +docutils = "0.20.1" +ecdsa = "0.18.0" +enum-compat = "0.0.3" +ephem = "4.1.4" +future = "0.18.3" +idna = "3.4" +itypes = "1.2.0" +jinja2 = "3.1.2" +markupsafe = "2.1.3" +olefile = "0.46" +paramiko = "3.2.0" +passlib = "1.7.4" +pyasn1 = "0.5.0" +pycparser = "2.21" +cryptography = "41.0.2" +pycryptodome = "3.18.0" +pycryptodomex = "3.18.0" +phonenumbers = "8.13.17" +gmssl = "3.2.2" +itsdangerous = "1.1.0" +pyotp = "2.8.0" +pynacl = "1.5.0" +python-dateutil = "2.8.2" +pyyaml = "6.0.1" +requests = "2.31.0" +jms-storage = "0.0.51" +simplejson = "3.19.1" +six = "1.16.0" +sshtunnel = "0.4.0" +sshpubkeys = "3.3.1" +uritemplate = "4.1.1" +urllib3 = "1.26.16" +vine = "5.0.0" +werkzeug = "2.3.6" +unicodecsv = "0.14.1" +httpsig = "1.3.0" +treelib = "1.6.4" +psutil = "5.9.5" +msrestazure = "0.6.4" +adal = "1.2.7" +openpyxl = "3.0.10" +pyexcel = "0.7.0" +pyexcel-xlsx = "0.6.0" +data-tree = "0.0.1" +pyvmomi = "8.0.1.0.2" +termcolor = "2.3.0" +html2text = "2020.1.16" +pyzipper = "0.3.6" +python3-saml = "1.15.0" +websocket-client = "1.6.1" +pyjwkest = "1.4.2" +jsonfield2 = "4.0.0.post0" +geoip2 = "4.7.0" +ipip-ipdb = "1.6.1" +pywinrm = "0.4.3" +python-nmap = "0.7.1" +django = "4.1.10" +django-bootstrap3 = "23.4" +django-filter = "23.2" +django-formtools = "2.4.1" +django-ranged-response = "0.2.0" +django-rest-swagger = "2.2.0" +django-simple-captcha = "0.5.18" +django-timezone-field = "5.1" +djangorestframework = "3.14.0" +djangorestframework-bulk = "0.2.1" +django-simple-history = "3.3.0" +django-private-storage = "3.1" +drf-nested-routers = "0.93.4" +drf-writable-nested = "0.7.0" +rest-condition = "1.0.3" +drf-yasg = "1.21.7" +coreapi = "2.3.3" +coreschema = "0.0.4" +openapi-codec = "1.3.2" +pillow = "10.0.0" +pytz = "2023.3" +django-proxy = "1.2.2" +channels-redis = "4.1.0" +python-daemon = "3.0.1" +eventlet = "0.33.3" +greenlet = "2.0.2" +gunicorn = "21.2.0" +celery = "5.3.1" +flower = "2.0.0" +django-celery-beat = "2.5.0" +kombu = "5.3.1" +uvicorn = "0.22.0" +websockets = "11.0.3" +python-ldap = "3.4.3" +ldap3 = "2.9.1" +django-radius = { url = "https://github.com/ibuler/django-radius/archive/refs/tags/1.5.0.zip" } +django-cas-ng = { url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.1/django-cas-ng-4.3.1.zip" } +python-cas = "1.6.0" +django-auth-ldap = "4.4.0" +boto3 = "1.28.9" +botocore = "1.31.9" +s3transfer = "0.6.1" +kubernetes = "27.2.0" +mysqlclient = "2.2.0" +pymysql = "1.1.0" +django-redis = "5.3.0" +python-redis-lock = "4.0.0" +pyopenssl = "23.2.0" +redis = "4.6.0" +pymongo = "4.4.1" +pyfreerdp = "0.0.1" +ipython = "8.14.0" +forgerypy3 = "0.3.1" +django-debug-toolbar = "4.1.0" +pympler = "1.0.1" +hvac = "1.1.1" +pyhcl = "0.4.4" +ipy = "1.1" +netifaces = "^0.11.0" + +[tool.poetry.group.dev.dependencies] +daphne = "4.0.0" +channels = "^4.0.0" +channels-redis = "^4.1.0" + +[tool.poetry.group.xpack.dependencies] +qingcloud-sdk = "1.2.15" +azure-mgmt-subscription = "3.1.1" +azure-identity = "1.13.0" +azure-mgmt-compute = "30.0.0" +azure-mgmt-network = "23.1.0" +google-cloud-compute = "1.13.0" +grpcio = "1.56.2" +alibabacloud-dysmsapi20170525 = "2.0.24" +python-novaclient = "18.3.0" +python-keystoneclient = "5.1.0" +bce-python-sdk = "0.8.87" +tencentcloud-sdk-python = "3.0.941" +aliyun-python-sdk-core-v3 = "2.13.33" +aliyun-python-sdk-ecs = "4.24.64" +keystoneauth1 = "5.2.1" +oracledb = "1.4.0" +psycopg2-binary = "2.9.6" +pymssql = "2.2.8" +psycopg2 = "2.9.6" +ucloud-sdk-python3 = "0.11.50" +huaweicloudsdkecs = "3.1.52" +huaweicloudsdkcore = "3.1.52" + +[[tool.poetry.source]] +name = "tsinghua" +url = "https://pypi.tuna.tsinghua.edu.cn/simple/" +priority = "primary" + +[[tool.poetry.source]] +name = "PyPI" +priority = "primary" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements/mac_pkg.sh b/requirements/mac_pkg.sh index 909282cf0..4caf8dd1a 100644 --- a/requirements/mac_pkg.sh +++ b/requirements/mac_pkg.sh @@ -4,8 +4,9 @@ PROJECT_DIR=$(dirname "$BASE_DIR") echo "1. 安装依赖" brew install libtiff libjpeg webp little-cms2 openssl gettext git \ - git-lfs mysql libxml2 libxmlsec1 pkg-config postgresql freetds openssl \ + git-lfs libxml2 libxmlsec1 pkg-config postgresql freetds openssl \ libffi freerdp +pip install daphne==4.0.0 channels channels-redis echo "2. 下载 IP 数据库" ip_db_path="${PROJECT_DIR}/apps/common/utils/geoip/GeoLite2-City.mmdb" diff --git a/requirements/requirements.txt b/requirements/requirements.txt deleted file mode 100644 index 65004e605..000000000 --- a/requirements/requirements.txt +++ /dev/null @@ -1,134 +0,0 @@ -aiofiles==22.1.0 -amqp==5.0.9 -git+https://github.com/jumpserver/ansible@master#egg=ansible-core -ansible==7.1.0 -ansible-runner==2.2.1 -asn1crypto==0.24.0 -bcrypt==3.1.4 -billiard==3.6.4.0 -certifi==2022.12.7 -cffi==1.15.1 -chardet==3.0.4 -configparser==3.5.0 -decorator==4.1.2 -docutils==0.14 -ecdsa==0.13.3 -enum-compat==0.0.2 -ephem==3.7.6.0 -future==0.16.0 -idna==2.8 -itypes==1.2.0 -Jinja2==3.1.2 -jmespath==1.0.1 -MarkupSafe==2.1.1 -olefile==0.46 -paramiko==2.11.0 -passlib==1.7.4 -pyasn1==0.4.8 -pycparser==2.21 -cryptography==38.0.4 -pycryptodome==3.15.0 -pycryptodomex==3.15.0 -phonenumbers==8.13.8 -gmssl==3.2.1 -itsdangerous==1.1.0 -pyotp==2.6.0 -PyNaCl==1.5.0 -python-dateutil==2.8.2 -PyYAML==6.0 -requests==2.31.0 -jms-storage==0.0.47 -simplejson==3.17.6 -six==1.16.0 -sshtunnel==0.4.0 -sshpubkeys==3.3.1 -uritemplate==4.1.1 -urllib3==1.26.9 -vine==5.0.0 -Werkzeug==2.1.2 -unicodecsv==0.14.1 -httpsig==1.3.0 -treelib==1.6.1 -psutil==5.9.1 -msrestazure==0.6.4 -adal==1.2.5 -openpyxl==3.0.10 -pyexcel==0.7.0 -pyexcel-xlsx==0.6.0 -data-tree==0.0.1 -pyvmomi==7.0.1 -termcolor==1.1.0 -html2text==2020.1.16 -pyzipper==0.3.5 -python3-saml==1.12.0 -websocket-client==1.2.3 -pyjwkest==1.4.2 -jsonfield2==4.0.0.post0 -geoip2==4.5.0 -ipip-ipdb==1.6.1 -pywinrm==0.4.3 -# Django environment -Django==3.2.20 -django-bootstrap3==14.2.0 -django-filter==2.4.0 -django-formtools==2.2 -django-ranged-response==0.2.0 -django-rest-swagger==2.2.0 -django-simple-captcha==0.5.17 -django-timezone-field==5.0 -djangorestframework==3.13.1 -djangorestframework-bulk==0.2.1 -django-simple-history==3.1.1 -django-private-storage==3.0 -drf-nested-routers==0.93.4 -drf-writable-nested==0.6.4 -rest_condition==1.0.3 -drf-yasg==1.20.0 -coreapi==2.3.3 -coreschema==0.0.4 -openapi-codec==1.3.2 -Pillow==9.3.0 -pytz==2022.1 -# Runtime -django-proxy==1.2.1 -channels-redis==4.0.0 -python-daemon==2.3.0 -eventlet==0.33.1 -greenlet==1.1.2 -gunicorn==20.1.0 -celery==5.2.7 -flower==1.2.0 -django-celery-beat==2.3.0 -kombu==5.2.4 -uvicorn==0.20.0 -websockets==10.4 -# Auth -python-ldap==3.4.0 -ldap3==2.9.1 -django-radius==1.5.0 -jumpserver-django-oidc-rp==0.3.7.8 -django-cas-ng==4.0.1 -python-cas==1.5.0 -django-auth-ldap==2.2.0 -# Cloud req -boto3==1.24.12 -botocore==1.27.12 -s3transfer==0.6.0 -kubernetes==21.7.0 -# DB requirements -mysqlclient==2.1.0 -PyMySQL==1.0.2 -pymssql==2.2.5 -django-mysql==3.9.0 -django-redis==5.2.0 -python-redis-lock==3.7.0 -pyOpenSSL==22.0.0 -redis==4.5.4 -pyOpenSSL==22.0.0 -pymongo==4.2.0 -pyfreerdp==0.0.1 -# Debug -ipython==8.10.0 -ForgeryPy3==0.3.1 -django-debug-toolbar==3.5 -Pympler==1.0.1 diff --git a/requirements/requirements_xpack.txt b/requirements/requirements_xpack.txt deleted file mode 100644 index 8f1930fad..000000000 --- a/requirements/requirements_xpack.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Cloud req -qingcloud-sdk==1.2.12 -azure-mgmt-subscription==1.0.0 -azure-identity==1.5.0 -azure-mgmt-compute==4.6.2 -azure-mgmt-network==2.7.0 -google-cloud-compute==0.5.0 -grpcio==1.54.2 -alibabacloud_dysmsapi20170525==2.0.2 -python-novaclient==11.0.1 -python-keystoneclient==4.3.0 -bce-python-sdk==0.8.64 -tencentcloud-sdk-python==3.0.662 -aliyun-python-sdk-core-v3==2.9.1 -aliyun-python-sdk-ecs==4.10.1 -huaweicloud-sdk-python==1.0.21 -# python-keystoneclient need keystoneauth1>=3.4.0 -# huaweicloud-sdk-python need keystoneauth1<=3.4.0 -keystoneauth1==3.4.0 -# DB requirements -oracledb==1.0.1 -psycopg2-binary==2.9.1 -pymssql==2.2.5 -IPy==1.1 -psycopg2==2.9.4 -ucloud-sdk-python3==0.11.47