Compare commits

...

33 Commits

Author SHA1 Message Date
fit2bot
dd652681d3 feat: Update v3.6.4 2023-09-11 13:05:10 +08:00
ibuler
0a58bba59c fix: 修复 private storage permission 2023-09-11 11:19:15 +08:00
fit2bot
c5102e567a fix: 修复工单审计员切换其他资产,原资产未删除问题 (#11512)
Co-authored-by: feng <1304903146@qq.com>
2023-09-06 15:13:23 +08:00
fit2bot
305a426789 perf: dashboard date metrics (#11463)
Co-authored-by: feng <1304903146@qq.com>
2023-08-30 14:11:24 +08:00
老广
946a01f826 Merge pull request #11441 from jumpserver/pr@v3.6@fix_migrate_sftp
fix: 修复迁移的 sftp 数量不对
2023-08-28 19:04:41 +08:00
ibuler
127a5d4157 fix: 修复迁移的 sftp 数量不对 2023-08-28 08:49:03 +00:00
“huailei000”
18fb9a67ac perf: 优化不能生成MFA二维码问题 2023-08-25 11:59:58 +05:00
老广
352b2c2bd4 Merge pull request #11416 from jumpserver/pr@v3.6@fix_new_applet_host_not_schedule
fix: 修复新添加发布机不被调度的问题
2023-08-24 18:12:55 +08:00
ibuler
5b498650cb fix: 修复新添加发布机不被调度的问题 2023-08-24 18:02:56 +08:00
jiangweidong
bd88e0af68 fix: saml2无法登陆问题 2023-08-24 11:05:03 +05:00
ibuler
29fdeef45f fix: 修复 Host name 中包含 [ 导致 ansible 错误的问题 2023-08-23 16:07:30 +05:00
老广
ed5f4a227f Merge pull request #11373 from jumpserver/pr@v3.6@device_add_sftp
perf: 网络设备支持 sftp
2023-08-21 15:40:10 +08:00
ibuler
5cb510a200 perf: 网络设备支持 sftp 2023-08-21 07:21:34 +00:00
老广
180cf354ad Merge pull request #11360 from jumpserver/pr@v3.6@perf_login_csrf
perf: 修改 csrf 登录时判断
2023-08-18 20:44:47 +08:00
ibuler
89a5c970e4 perf: 修改 csrf 登录时判断 2023-08-18 12:37:32 +00:00
老广
1d25cad449 Merge pull request #11359 from jumpserver/pr@v3.6@perf_csrf_token_error
perf: 修改 csrf token 提示
2023-08-18 18:46:02 +08:00
ibuler
c3b0798311 perf: 修改 csrf token 提示 2023-08-18 10:44:23 +00:00
ibuler
ff851b4672 perf: 去掉 migrate 提示 2023-08-18 15:17:20 +05:00
老广
2bcdcce2d3 Merge pull request #11352 from jumpserver/pr@v3.6@perf_login_info
perf: 优化登录页面提示判断,可能没有端口
2023-08-18 18:11:40 +08:00
ibuler
f5ac941eb3 perf: 优化登录页面提示判断,可能没有端口
perf: 修改 login 检测
2023-08-18 09:59:53 +00:00
老广
efcbfe63f9 Merge pull request #11350 from jumpserver/pr@v3.6@perf_info
perf: 修改说明
2023-08-18 17:01:15 +08:00
ibuler
41a2e00406 perf: 修改说明 2023-08-18 08:59:40 +00:00
老广
738b9efe11 Merge pull request #11345 from jumpserver/pr@v3.6@fix_sessionshare
fix: 修复创建会话分享不填写用户报错的问题
2023-08-18 16:52:24 +08:00
ibuler
119c7a8634 perf: 优化登录提示 2023-08-18 13:51:51 +05:00
Bai
77e43c1c5c fix: 修复创建会话分享不填写用户报错的问题 2023-08-18 08:46:14 +00:00
老广
4d0231a9ad Merge pull request #11344 from jumpserver/pr@v3.6@allow_hosts_to_all
perf: 修改 allowed hosts
2023-08-18 16:17:00 +08:00
ibuler
4562f1fbe8 perf: 修改 allowed hosts 2023-08-18 08:16:04 +00:00
Bai
4be70ff3da fix: 修复资产树子节点创建后没有获取到的问题 2023-08-18 13:03:34 +05:00
老广
1a742d65f6 Merge pull request #11338 from jumpserver/pr@v3.6@perf_depends
perf: 优化依赖
2023-08-18 15:26:48 +08:00
ibuler
ba83b64d87 perf: 优化依赖 2023-08-18 15:23:01 +08:00
feng
f46c9f56e8 fix: 修复密钥校验ansible不支持{% 2023-08-17 16:16:15 +05:00
老广
626ec8f25d Merge pull request #11325 from jumpserver/pr@v3.6@perf_django_ca_version
perf: 修改 django cas version
2023-08-17 17:50:15 +08:00
ibuler
526c7de598 perf: 修改 django cas version 2023-08-17 09:48:19 +00:00
32 changed files with 772 additions and 91 deletions

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
0a58bba59cd275bab8e0ae58bf4b359fbc5eb74a

View File

@@ -113,7 +113,7 @@ class Migration(migrations.Migration):
('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='New secret')),
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16)), ('status', models.CharField(default='pending', max_length=16)),

View File

@@ -39,6 +39,8 @@ def validate_password_for_ansible(password):
# Ansible 推送的时候不支持 # Ansible 推送的时候不支持
if '{{' in password: if '{{' in password:
raise serializers.ValidationError(_('Password can not contains `{{` ')) raise serializers.ValidationError(_('Password can not contains `{{` '))
if '{%' in password:
raise serializers.ValidationError(_('Password can not contains `{%` '))
# Ansible Windows 推送的时候不支持 # Ansible Windows 推送的时候不支持
if "'" in password: if "'" in password:
raise serializers.ValidationError(_("Password can not contains `'` ")) raise serializers.ValidationError(_("Password can not contains `'` "))

View File

@@ -42,7 +42,7 @@ class SerializeToTreeNodeMixin:
'name': _name(node), 'name': _name(node),
'title': _name(node), 'title': _name(node),
'pId': node.parent_key, 'pId': node.parent_key,
'isParent': node.assets_amount > 0, 'isParent': True,
'open': _open(node), 'open': _open(node),
'meta': { 'meta': {
'data': { 'data': {

View File

@@ -24,7 +24,7 @@ class DeviceTypes(BaseType):
def _get_protocol_constrains(cls) -> dict: def _get_protocol_constrains(cls) -> dict:
return { return {
'*': { '*': {
'choices': ['ssh', 'telnet'] 'choices': ['ssh', 'telnet', 'sftp']
} }
} }

View File

@@ -49,11 +49,11 @@ def migrate_assets_sftp_protocol(apps, schema_editor):
count = 0 count = 0
print("\nAsset add sftp protocol: ") print("\nAsset add sftp protocol: ")
asset_ids = asset_cls.objects\ asset_ids = list(asset_cls.objects\
.filter(platform__in=sftp_platforms)\ .filter(platform__in=sftp_platforms)\
.exclude(protocols__name='sftp')\ .exclude(protocols__name='sftp')\
.distinct()\ .distinct()\
.values_list('id', flat=True) .values_list('id', flat=True))
while True: while True:
_asset_ids = asset_ids[count:count + 1000] _asset_ids = asset_ids[count:count + 1000]
if not _asset_ids: if not _asset_ids:

View File

@@ -146,7 +146,9 @@ class PrepareRequestMixin:
}, },
'singleLogoutService': { 'singleLogoutService': {
'url': f"{sp_host}{reverse('authentication:saml2:saml2-logout')}" 'url': f"{sp_host}{reverse('authentication:saml2:saml2-logout')}"
} },
'privateKey': getattr(settings, 'SAML2_SP_KEY_CONTENT', ''),
'x509cert': getattr(settings, 'SAML2_SP_CERT_CONTENT', ''),
} }
} }
sp_settings['sp'].update(attrs) sp_settings['sp'].update(attrs)

View File

@@ -223,10 +223,22 @@
height: 13px; height: 13px;
cursor: pointer; cursor: pointer;
} }
.error-info {
font-size: 16px;
text-align: center;
}
</style> </style>
</head> </head>
<body> <body>
{% if error_origin %}
<div class='alert alert-danger error-info'>
配置文件存在问题无法完成登录请联系管理员解决或查看最新更新说明 <br/>
Configuration file has problems and cannot be logged in. Please contact the administrator <br/>
- {{ error_origin }} -
</div>
{% endif %}
<div class="login-content extra-fields-{{ extra_fields_count }}"> <div class="login-content extra-fields-{{ extra_fields_count }}">
<div class="right-image-box"> <div class="right-image-box">
<a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver.git{% endif %}"> <a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver.git{% endif %}">

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import datetime import datetime
import os import os
from typing import Callable from typing import Callable
from urllib.parse import urlparse
from django.conf import settings from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY from django.contrib.auth import BACKEND_SESSION_KEY
@@ -40,6 +41,7 @@ __all__ = [
class UserLoginContextMixin: class UserLoginContextMixin:
get_user_mfa_context: Callable get_user_mfa_context: Callable
request: HttpRequest request: HttpRequest
error_origin: str
def get_support_auth_methods(self): def get_support_auth_methods(self):
auth_methods = [ auth_methods = [
@@ -134,8 +136,27 @@ class UserLoginContextMixin:
count += 1 count += 1
return count return count
def set_csrf_error_if_need(self, context):
if not self.request.GET.get('csrf_failure'):
return context
http_origin = self.request.META.get('HTTP_ORIGIN')
http_referer = self.request.META.get('HTTP_REFERER')
http_origin = http_origin or http_referer
if not http_origin:
return context
try:
origin = urlparse(http_origin)
context['error_origin'] = str(origin.netloc)
except ValueError:
pass
return context
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
self.set_csrf_error_if_need(context)
context.update({ context.update({
'demo_mode': os.environ.get("DEMO_MODE"), 'demo_mode': os.environ.get("DEMO_MODE"),
'auth_methods': self.get_support_auth_methods(), 'auth_methods': self.get_support_auth_methods(),

View File

@@ -12,7 +12,7 @@ from common.utils import get_object_or_none
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): class IsValidUser(permissions.IsAuthenticated):
"""Allows access to valid user, is active and not expired""" """Allows access to valid user, is active and not expired"""
def has_permission(self, request, view): def has_permission(self, request, view):

View File

@@ -1,4 +1,5 @@
import time import time
from collections import defaultdict
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Count, Max, F from django.db.models import Count, Max, F
@@ -141,40 +142,35 @@ class DatesLoginMetricMixin:
operate_logs_queryset: OperateLog.objects operate_logs_queryset: OperateLog.objects
password_change_logs_queryset: PasswordChangeLog.objects password_change_logs_queryset: PasswordChangeLog.objects
def filter_date_start_end(self, queryset, field_name):
query = {f'{field_name}__range': self.date_start_end}
return queryset.filter(**query)
def get_date_metrics(self, queryset, field_name, count_field):
queryset = self.filter_date_start_end(queryset, field_name)
queryset = queryset.values_list(field_name, count_field)
date_group_map = defaultdict(set)
for datetime, count_field in queryset:
date_str = str(datetime.date())
date_group_map[date_str].add(count_field)
return [
len(date_group_map.get(str(d), set()))
for d in self.dates_list
]
def get_dates_metrics_total_count_login(self): def get_dates_metrics_total_count_login(self):
queryset = UserLoginLog.objects \ return self.get_date_metrics(UserLoginLog.objects, 'datetime', 'id')
.filter(datetime__range=(self.date_start_end)) \
.values('datetime__date').annotate(id__count=Count(id)) \
.order_by('datetime__date')
map_date_logincount = {i['datetime__date']: i['id__count'] for i in queryset}
return [map_date_logincount.get(d, 0) for d in self.dates_list]
def get_dates_metrics_total_count_active_users(self): def get_dates_metrics_total_count_active_users(self):
queryset = Session.objects \ return self.get_date_metrics(Session.objects, 'date_start', 'user_id')
.filter(date_start__range=(self.date_start_end)) \
.values('date_start__date') \
.annotate(id__count=Count('user_id', distinct=True)) \
.order_by('date_start__date')
map_date_usercount = {i['date_start__date']: i['id__count'] for i in queryset}
return [map_date_usercount.get(d, 0) for d in self.dates_list]
def get_dates_metrics_total_count_active_assets(self): def get_dates_metrics_total_count_active_assets(self):
queryset = Session.objects \ return self.get_date_metrics(Session.objects, 'date_start', 'asset_id')
.filter(date_start__range=(self.date_start_end)) \
.values('date_start__date') \
.annotate(id__count=Count('asset_id', distinct=True)) \
.order_by('date_start__date')
map_date_assetcount = {i['date_start__date']: i['id__count'] for i in queryset}
return [map_date_assetcount.get(d, 0) for d in self.dates_list]
def get_dates_metrics_total_count_sessions(self): def get_dates_metrics_total_count_sessions(self):
queryset = Session.objects \ return self.get_date_metrics(Session.objects, 'date_start', 'id')
.filter(date_start__range=(self.date_start_end)) \
.values('date_start__date') \
.annotate(id__count=Count(id)) \
.order_by('date_start__date')
map_date_usercount = {i['date_start__date']: i['id__count'] for i in queryset}
return [map_date_usercount.get(d, 0) for d in self.dates_list]
@lazyproperty @lazyproperty
def get_type_to_assets(self): def get_type_to_assets(self):

View File

@@ -16,6 +16,8 @@ def allow_access(private_file):
path_base = path_list[1] if len(path_list) > 1 else None path_base = path_list[1] if len(path_list) > 1 else None
path_perm = path_perms_map.get(path_base, None) path_perm = path_perms_map.get(path_base, None)
if ".." in request_path:
return False
if not path_perm: if not path_perm:
return False return False
if path_perm == '*' or request.user.has_perms([path_perm]): if path_perm == '*' or request.user.has_perms([path_perm]):

View File

@@ -88,11 +88,12 @@ if DEBUG:
DEBUG_HOST_PORTS = ['{}:{}'.format(host, port) for host in DEBUG_HOSTS for port in DEBUG_PORT] DEBUG_HOST_PORTS = ['{}:{}'.format(host, port) for host in DEBUG_HOSTS for port in DEBUG_PORT]
ALLOWED_DOMAINS.extend(DEBUG_HOST_PORTS) ALLOWED_DOMAINS.extend(DEBUG_HOST_PORTS)
ALLOWED_HOSTS = list(set(['.' + host.split(':')[0] for host in ALLOWED_DOMAINS]))
print("ALLOWED_HOSTS: ", ) print("ALLOWED_HOSTS: ", )
for host in ALLOWED_HOSTS: for host in ALLOWED_DOMAINS:
print(' - ' + host.lstrip('.')) print(' - ' + host.lstrip('.'))
ALLOWED_HOSTS = ['*']
# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS # https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS
CSRF_TRUSTED_ORIGINS = [] CSRF_TRUSTED_ORIGINS = []
for host_port in ALLOWED_DOMAINS: for host_port in ALLOWED_DOMAINS:
@@ -106,6 +107,7 @@ for host_port in ALLOWED_DOMAINS:
continue continue
CSRF_TRUSTED_ORIGINS.append('{}://*.{}'.format(schema, origin)) CSRF_TRUSTED_ORIGINS.append('{}://*.{}'.format(schema, origin))
CSRF_FAILURE_VIEW = 'jumpserver.views.other.csrf_failure'
# print("CSRF_TRUSTED_ORIGINS: ") # print("CSRF_TRUSTED_ORIGINS: ")
# for origin in CSRF_TRUSTED_ORIGINS: # for origin in CSRF_TRUSTED_ORIGINS:
# print(' - ' + origin) # print(' - ' + origin)

View File

@@ -88,3 +88,9 @@ class KokoView(View):
class ResourceDownload(TemplateView): class ResourceDownload(TemplateView):
template_name = 'resource_download.html' template_name = 'resource_download.html'
def csrf_failure(request, reason=""):
from django.shortcuts import reverse
login_url = reverse('authentication:login') + '?csrf_failure=1&admin=1'
return redirect(login_url)

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:810d46e14e09a2309a8d898bc391f33082bfc5c164dec246bd95cea8436e33ee oid sha256:f87b3d05a2f665ef21a4876b9af8ba651936220b35633d1dc6a93941000bd253
size 154540 size 154676

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-17 15:30+0800\n" "POT-Creation-Date: 2023-08-17 18:24+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -857,15 +857,19 @@ msgstr "アカウント接続のテスト"
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない" msgstr "パスワードには '{{' を含まない"
#: accounts/utils.py:44 #: accounts/utils.py:43
msgid "Password can not contains `{%` "
msgstr "パスワードには '{%' を含まない"
#: accounts/utils.py:46
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない" msgstr "パスワードには `'` を含まない"
#: accounts/utils.py:46 #: accounts/utils.py:48
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない" msgstr "パスワードには `\"` を含まない"
#: accounts/utils.py:52 #: accounts/utils.py:54
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー" msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1032,7 +1036,7 @@ msgid "None of the reviewers belong to Organization `{}`"
msgstr "いずれのレビューアも組織 '{}' に属していません" msgstr "いずれのレビューアも組織 '{}' に属していません"
#: acls/serializers/rules/rules.py:20 #: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:133 #: xpack/plugins/cloud/serializers/task.py:137
msgid "IP address invalid: `{}`" msgid "IP address invalid: `{}`"
msgstr "IPアドレスが無効: '{}'" msgstr "IPアドレスが無効: '{}'"
@@ -7696,7 +7700,7 @@ msgstr "クラウドアカウント"
msgid "Test cloud account" msgid "Test cloud account"
msgstr "クラウドアカウントのテスト" msgstr "クラウドアカウントのテスト"
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:147 #: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:151
msgid "Regions" msgid "Regions"
msgstr "リージョン" msgstr "リージョン"
@@ -7705,17 +7709,17 @@ msgid "Hostname strategy"
msgstr "ホスト名戦略" msgstr "ホスト名戦略"
#: xpack/plugins/cloud/models.py:100 #: xpack/plugins/cloud/models.py:100
#: xpack/plugins/cloud/serializers/task.py:150 #: xpack/plugins/cloud/serializers/task.py:154
msgid "IP network segment group" msgid "IP network segment group"
msgstr "IPネットワークセグメントグループ" msgstr "IPネットワークセグメントグループ"
#: xpack/plugins/cloud/models.py:103 #: xpack/plugins/cloud/models.py:103
#: xpack/plugins/cloud/serializers/task.py:155 #: xpack/plugins/cloud/serializers/task.py:159
msgid "Sync IP type" msgid "Sync IP type"
msgstr "同期IPタイプ" msgstr "同期IPタイプ"
#: xpack/plugins/cloud/models.py:106 #: xpack/plugins/cloud/models.py:106
#: xpack/plugins/cloud/serializers/task.py:173 #: xpack/plugins/cloud/serializers/task.py:177
msgid "Always update" msgid "Always update"
msgstr "常に更新" msgstr "常に更新"
@@ -7808,7 +7812,7 @@ msgstr "ルール一致"
msgid "Rule value" msgid "Rule value"
msgstr "ルール値" msgstr "ルール値"
#: xpack/plugins/cloud/models.py:317 #: xpack/plugins/cloud/models.py:317 xpack/plugins/cloud/serializers/task.py:70
msgid "Strategy rule" msgid "Strategy rule"
msgstr "戦略ルール" msgstr "戦略ルール"
@@ -7820,7 +7824,7 @@ msgstr "アクション属性"
msgid "Action value" msgid "Action value"
msgstr "アクション値" msgstr "アクション値"
#: xpack/plugins/cloud/models.py:341 #: xpack/plugins/cloud/models.py:341 xpack/plugins/cloud/serializers/task.py:73
msgid "Strategy action" msgid "Strategy action"
msgstr "戦略アクション" msgstr "戦略アクション"
@@ -8108,7 +8112,7 @@ msgstr "テストタイムアウト"
msgid "Project" msgid "Project"
msgstr "project" msgstr "project"
#: xpack/plugins/cloud/serializers/task.py:139 #: xpack/plugins/cloud/serializers/task.py:143
msgid "" msgid ""
"Only instances matching the IP range will be synced. <br>If the instance " "Only instances matching the IP range will be synced. <br>If the instance "
"contains multiple IP addresses, the first IP address that matches will be " "contains multiple IP addresses, the first IP address that matches will be "
@@ -8122,11 +8126,11 @@ msgstr ""
"ドレスをランダムに一致させることを意味します。 <br> 例: " "ドレスをランダムに一致させることを意味します。 <br> 例: "
"192.168.1.0/24,10.1.1.1-10.1.1.20。" "192.168.1.0/24,10.1.1.1-10.1.1.20。"
#: xpack/plugins/cloud/serializers/task.py:145 #: xpack/plugins/cloud/serializers/task.py:149
msgid "History count" msgid "History count"
msgstr "実行回数" msgstr "実行回数"
#: xpack/plugins/cloud/serializers/task.py:146 #: xpack/plugins/cloud/serializers/task.py:150
msgid "Instance count" msgid "Instance count"
msgstr "インスタンス数" msgstr "インスタンス数"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:da4f312ed86d27fa8b6bde8da3bc70b0f32fe40811ee855f9fe81d89a68a646f oid sha256:f2f7fa427404ca2b2f5651cb1be3e84876dad9d72f14e488321a2dc84d4f4051
size 126402 size 126472

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-17 15:30+0800\n" "POT-Creation-Date: 2023-08-17 18:24+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -857,15 +857,19 @@ msgstr "测试账号可连接性"
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "密码不能包含 `{{` 字符" msgstr "密码不能包含 `{{` 字符"
#: accounts/utils.py:44 #: accounts/utils.py:43
msgid "Password can not contains `{%` "
msgstr "密码不能包含 `{%` 字符"
#: accounts/utils.py:46
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "密码不能包含 `'` 字符" msgstr "密码不能包含 `'` 字符"
#: accounts/utils.py:46 #: accounts/utils.py:48
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "密码不能包含 `\"` 字符" msgstr "密码不能包含 `\"` 字符"
#: accounts/utils.py:52 #: accounts/utils.py:54
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误" msgstr "密钥不合法或密钥密码错误"
@@ -1031,7 +1035,7 @@ msgid "None of the reviewers belong to Organization `{}`"
msgstr "所有复核人都不属于组织 `{}`" msgstr "所有复核人都不属于组织 `{}`"
#: acls/serializers/rules/rules.py:20 #: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:133 #: xpack/plugins/cloud/serializers/task.py:137
msgid "IP address invalid: `{}`" msgid "IP address invalid: `{}`"
msgstr "IP 地址无效: `{}`" msgstr "IP 地址无效: `{}`"
@@ -7588,7 +7592,7 @@ msgstr "云账号"
msgid "Test cloud account" msgid "Test cloud account"
msgstr "测试云账号" msgstr "测试云账号"
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:147 #: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers/task.py:151
msgid "Regions" msgid "Regions"
msgstr "地域" msgstr "地域"
@@ -7597,17 +7601,17 @@ msgid "Hostname strategy"
msgstr "主机名策略" msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:100 #: xpack/plugins/cloud/models.py:100
#: xpack/plugins/cloud/serializers/task.py:150 #: xpack/plugins/cloud/serializers/task.py:154
msgid "IP network segment group" msgid "IP network segment group"
msgstr "IP网段组" msgstr "IP网段组"
#: xpack/plugins/cloud/models.py:103 #: xpack/plugins/cloud/models.py:103
#: xpack/plugins/cloud/serializers/task.py:155 #: xpack/plugins/cloud/serializers/task.py:159
msgid "Sync IP type" msgid "Sync IP type"
msgstr "同步IP类型" msgstr "同步IP类型"
#: xpack/plugins/cloud/models.py:106 #: xpack/plugins/cloud/models.py:106
#: xpack/plugins/cloud/serializers/task.py:173 #: xpack/plugins/cloud/serializers/task.py:177
msgid "Always update" msgid "Always update"
msgstr "总是更新" msgstr "总是更新"
@@ -7700,7 +7704,7 @@ msgstr "规则匹配"
msgid "Rule value" msgid "Rule value"
msgstr "规则值" msgstr "规则值"
#: xpack/plugins/cloud/models.py:317 #: xpack/plugins/cloud/models.py:317 xpack/plugins/cloud/serializers/task.py:70
msgid "Strategy rule" msgid "Strategy rule"
msgstr "策略规则" msgstr "策略规则"
@@ -7712,7 +7716,7 @@ msgstr "动作属性"
msgid "Action value" msgid "Action value"
msgstr "动作值" msgstr "动作值"
#: xpack/plugins/cloud/models.py:341 #: xpack/plugins/cloud/models.py:341 xpack/plugins/cloud/serializers/task.py:73
msgid "Strategy action" msgid "Strategy action"
msgstr "策略动作" msgstr "策略动作"
@@ -7999,7 +8003,7 @@ msgstr "测试超时时间"
msgid "Project" msgid "Project"
msgstr "project" msgstr "project"
#: xpack/plugins/cloud/serializers/task.py:139 #: xpack/plugins/cloud/serializers/task.py:143
msgid "" msgid ""
"Only instances matching the IP range will be synced. <br>If the instance " "Only instances matching the IP range will be synced. <br>If the instance "
"contains multiple IP addresses, the first IP address that matches will be " "contains multiple IP addresses, the first IP address that matches will be "
@@ -8011,11 +8015,11 @@ msgstr ""
"到的 IP 地址将被用作创建的资产的 IP。<br>默认值 * 表示同步所有实例和随机匹配 " "到的 IP 地址将被用作创建的资产的 IP。<br>默认值 * 表示同步所有实例和随机匹配 "
"IP 地址。<br> 例如: 192.168.1.0/24,10.1.1.1-10.1.1.20。" "IP 地址。<br> 例如: 192.168.1.0/24,10.1.1.1-10.1.1.20。"
#: xpack/plugins/cloud/serializers/task.py:145 #: xpack/plugins/cloud/serializers/task.py:149
msgid "History count" msgid "History count"
msgstr "执行次数" msgstr "执行次数"
#: xpack/plugins/cloud/serializers/task.py:146 #: xpack/plugins/cloud/serializers/task.py:150
msgid "Instance count" msgid "Instance count"
msgstr "实例个数" msgstr "实例个数"

View File

@@ -163,8 +163,9 @@ class JMSInventory:
protocol = self.get_primary_protocol(ansible_config, protocols) protocol = self.get_primary_protocol(ansible_config, protocols)
name = asset.name.replace(' ', '_').replace('[', '_').replace(']', '_')
host = { host = {
'name': '{}'.format(asset.name.replace(' ', '_')), 'name': name,
'jms_asset': { 'jms_asset': {
'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'id': str(asset.id), 'name': asset.name, 'address': asset.address,
'type': asset.type, 'category': asset.category, 'type': asset.type, 'category': asset.category,
@@ -281,7 +282,6 @@ class JMSInventory:
data = {'all': {'hosts': {}}} data = {'all': {'hosts': {}}}
for host in hosts: for host in hosts:
name = host.pop('name') name = host.pop('name')
name = name.replace('[', '_').replace(']', '_')
data['all']['hosts'][name] = host data['all']['hosts'][name] = host
if not self.exclude_localhost: if not self.exclude_localhost:
data['all']['hosts'].update({ data['all']['hosts'].update({

View File

@@ -0,0 +1,618 @@
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();
if (typeof module != "undefined") {
module.exports = QRCode;
}

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@
<link href="{% static 'css/jumpserver.css' %}" rel="stylesheet"> <link href="{% static 'css/jumpserver.css' %}" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet"> <link href="{% static 'css/style.css' %}" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/otp.css' %}" /> <link rel="stylesheet" href="{% static 'css/otp.css' %}" />
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script> <script src="{% static "js/plugins/qrcode/qrcode.js" %}"></script>
</head> </head>
<body style="background-color: #f3f3f4"> <body style="background-color: #f3f3f4">

View File

@@ -148,6 +148,11 @@ class Applet(JMSBaseModel):
shutil.copytree(path, pkg_path) shutil.copytree(path, pkg_path)
return instance, serializer return instance, serializer
@classmethod
def clear_host_prefer(cls):
prefer_key = 'applet_host_prefer_{}'.format("*")
cache.delete_pattern(prefer_key)
def select_host(self, user, asset): def select_host(self, user, asset):
hosts = self.hosts.filter(is_active=True) hosts = self.hosts.filter(is_active=True)
hosts = [host for host in hosts if host.load != 'offline'] hosts = [host for host in hosts if host.load != 'offline']

View File

@@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from django.utils.functional import cached_property from django.utils.functional import cached_property
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
from common.utils import is_uuid
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from users.models import User from users.models import User
@@ -62,6 +63,9 @@ class SessionSharing(JMSBaseModel, OrgModelMixin):
@cached_property @cached_property
def users_queryset(self): def users_queryset(self):
user_ids = self.users.split(',') user_ids = self.users.split(',')
user_ids = [user_id for user_id in user_ids if is_uuid(user_id)]
if not user_ids:
return User.objects.none()
return User.objects.filter(id__in=user_ids) return User.objects.filter(id__in=user_ids)
@property @property

View File

@@ -1,13 +1,15 @@
from rest_framework import permissions from rest_framework import permissions
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['IsSessionAssignee'] __all__ = ['IsSessionAssignee']
class IsSessionAssignee(permissions.BasePermission): class IsSessionAssignee(permissions.IsAuthenticated):
def has_permission(self, request, view):
return False
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
try: try:

View File

@@ -20,6 +20,8 @@ logger = get_logger(__file__)
def on_applet_host_create(sender, instance, created=False, **kwargs): def on_applet_host_create(sender, instance, created=False, **kwargs):
if not created: if not created:
return return
Applet.clear_host_prefer()
applets = Applet.objects.all() applets = Applet.objects.all()
instance.applets.set(applets) instance.applets.set(applets)

View File

@@ -86,7 +86,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer = self.get_serializer(instance, data=request.data, partial=partial)
with tmp_to_root_org(): with tmp_to_root_org():
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
instance = serializer.save() instance = serializer.save()
instance.approve(processor=request.user) instance.approve(processor=request.user)
return Response('ok') return Response('ok')

View File

@@ -1,12 +1,12 @@
from rest_framework import permissions from rest_framework import permissions
class IsAssignee(permissions.BasePermission): class IsAssignee(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return obj.has_current_assignee(request.user) return obj.has_current_assignee(request.user)
class IsApplicant(permissions.BasePermission): class IsApplicant(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return obj.applicant == request.user return obj.applicant == request.user

View File

@@ -1,6 +1,5 @@
from rest_framework import permissions from rest_framework import permissions
from rbac.builtin import BuiltinRole
from .utils import is_auth_password_time_valid from .utils import is_auth_password_time_valid
@@ -11,7 +10,7 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
and is_auth_password_time_valid(request.session) and is_auth_password_time_valid(request.session)
class UserObjectPermission(permissions.BasePermission): class UserObjectPermission(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if view.action not in ['update', 'partial_update', 'destroy']: if view.action not in ['update', 'partial_update', 'destroy']:

View File

@@ -41,8 +41,8 @@
// 生成用户绑定otp的二维码 // 生成用户绑定otp的二维码
var qrcode = new QRCode(document.getElementById('qr_code'), { var qrcode = new QRCode(document.getElementById('qr_code'), {
text: "{{ otp_uri|safe}}", text: "{{ otp_uri|safe}}",
width: 180 , width: 190 ,
height: 180, height: 190,
colorDark: '#000000', colorDark: '#000000',
colorLight: '#ffffff', colorLight: '#ffffff',
correctlevel: QRCode.CorrectLevel.H correctlevel: QRCode.CorrectLevel.H

12
poetry.lock generated
View File

@@ -1348,13 +1348,13 @@ reference = "tsinghua"
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.6" version = "8.1.7"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
] ]
[package.dependencies] [package.dependencies]
@@ -1824,7 +1824,7 @@ description = ""
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "django-cas-ng-4.3.1.zip", hash = "sha256:aeea96ad7958e3cb40d9bb5ef6a1add66f720835dfe87cc1dfe163f92d084690"}, {file = "django-cas-ng-4.3.2.zip", hash = "sha256:3539eae7ca857017cefa962782c99cbd35cc15ea741655b10c3417fa7e86807a"},
] ]
[package.dependencies] [package.dependencies]
@@ -1833,7 +1833,7 @@ python-cas = ">=1.6.0"
[package.source] [package.source]
type = "url" type = "url"
url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.1/django-cas-ng-4.3.1.zip" url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.2/django-cas-ng-4.3.2.zip"
[[package]] [[package]]
name = "django-celery-beat" name = "django-celery-beat"
@@ -7223,4 +7223,4 @@ reference = "tsinghua"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "0bc2878f163c8b2f48d1103d96314c56f722f10d8f8adf6dfaba0452e6eac368" content-hash = "121cb018467440ab938aa169dcb8de096018855f0034f1cbc980d484ede3c8f1"

View File

@@ -100,7 +100,6 @@ openapi-codec = "1.3.2"
pillow = "10.0.0" pillow = "10.0.0"
pytz = "2023.3" pytz = "2023.3"
django-proxy = "1.2.2" django-proxy = "1.2.2"
channels-redis = "4.1.0"
python-daemon = "3.0.1" python-daemon = "3.0.1"
eventlet = "0.33.3" eventlet = "0.33.3"
greenlet = "2.0.2" greenlet = "2.0.2"
@@ -114,7 +113,7 @@ websockets = "11.0.3"
python-ldap = "3.4.3" python-ldap = "3.4.3"
ldap3 = "2.9.1" ldap3 = "2.9.1"
django-radius = { url = "https://github.com/ibuler/django-radius/archive/refs/tags/1.5.0.zip" } 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" } django-cas-ng = { url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.2/django-cas-ng-4.3.2.zip" }
python-cas = "1.6.0" python-cas = "1.6.0"
django-auth-ldap = "4.4.0" django-auth-ldap = "4.4.0"
boto3 = "1.28.9" boto3 = "1.28.9"
@@ -137,10 +136,11 @@ hvac = "1.1.1"
pyhcl = "0.4.4" pyhcl = "0.4.4"
ipy = "1.1" ipy = "1.1"
netifaces = "^0.11.0" netifaces = "^0.11.0"
channels-redis = "4.1.0"
[tool.poetry.group.dev.dependencies]
daphne = "4.0.0" daphne = "4.0.0"
channels = "^4.0.0" channels = "^4.0.0"
[tool.poetry.group.dev.dependencies]
channels-redis = "^4.1.0" channels-redis = "^4.1.0"
[tool.poetry.group.xpack.dependencies] [tool.poetry.group.xpack.dependencies]