Compare commits

...

40 Commits

Author SHA1 Message Date
fit2bot
b5cb3399f5 feat: Update v3.6.6 2023-09-26 17:47:44 +08:00
ibuler
e104db4187 fix: pubkey auth require svc sign 2023-09-25 23:30:51 +08:00
ibuler
a99635d982 fix: 修复暴力校验验证码 2023-09-25 23:05:19 +08:00
Bai
f2e2c91856 fix: 修复系统用户同步同时包含pwd/ssh-key导致创建账号id冲突报错的问题 2023-09-25 16:23:47 +08:00
吴小白
e4a14917fa perf: 添加 patch 命令 2023-09-22 15:20:00 +08:00
Bai
67343cb257 fix: 解决节点资产数量方法计算不准确的问题 2023-09-22 15:19:32 +08:00
Aaron3S
d0321a74f1 perf: 优化 Playbook 文件创建逻辑 2023-09-19 18:47:25 +08:00
ibuler
ce645b1710 fix: 修复 random error 2023-09-19 18:19:36 +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
40 changed files with 879 additions and 144 deletions

View File

@@ -39,6 +39,7 @@ ARG TOOLS=" \
locales \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
e104db4187ab7ffb877c95d3a025af178cace3df

View File

@@ -113,7 +113,7 @@ class Migration(migrations.Migration):
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('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')),
('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_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16)),

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ def migrate_asset_accounts(apps, schema_editor):
count += len(auth_books)
# auth book 和 account 相同的属性
same_attrs = [
'id', 'username', 'comment', 'date_created', 'date_updated',
'username', 'comment', 'date_created', 'date_updated',
'created_by', 'asset_id', 'org_id',
]
# 认证的属性,可能是 auth_book 的,可能是 system_user 的

View File

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

View File

@@ -402,12 +402,7 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin):
return Asset.objects.filter(q).distinct()
def get_assets_amount(self):
q = Q(node__key__startswith=f'{self.key}:') | Q(node__key=self.key)
return self.assets.through.objects.filter(q).count()
def get_assets_account_by_children(self):
children = self.get_all_children().values_list()
return self.assets.through.objects.filter(node_id__in=children).count()
return self.get_all_assets().count()
@classmethod
def get_node_all_assets_by_key_v2(cls, key):

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
from django.contrib.auth import get_user_model
from django.conf import settings
from django.contrib.auth import get_user_model
from common.permissions import ServiceAccountSignaturePermission
from .base import JMSBaseAuthBackend
UserModel = get_user_model()
@@ -18,6 +19,10 @@ class PublicKeyAuthBackend(JMSBaseAuthBackend):
def authenticate(self, request, username=None, public_key=None, **kwargs):
if not public_key:
return None
permission = ServiceAccountSignaturePermission()
if not permission.has_permission(request, None):
return None
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
@@ -26,7 +31,7 @@ class PublicKeyAuthBackend(JMSBaseAuthBackend):
return None
else:
if user.check_public_key(public_key) and \
self.user_can_authenticate(user):
self.user_can_authenticate(user):
return user
def get_user(self, user_id):

View File

@@ -146,7 +146,9 @@ class PrepareRequestMixin:
},
'singleLogoutService': {
'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)

View File

@@ -223,10 +223,22 @@
height: 13px;
cursor: pointer;
}
.error-info {
font-size: 16px;
text-align: center;
}
</style>
</head>
<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="right-image-box">
<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 os
from typing import Callable
from urllib.parse import urlparse
from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY
@@ -40,6 +41,7 @@ __all__ = [
class UserLoginContextMixin:
get_user_mfa_context: Callable
request: HttpRequest
error_origin: str
def get_support_auth_methods(self):
auth_methods = [
@@ -134,8 +136,27 @@ class UserLoginContextMixin:
count += 1
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):
context = super().get_context_data(**kwargs)
self.set_csrf_error_if_need(context)
context.update({
'demo_mode': os.environ.get("DEMO_MODE"),
'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
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
class IsValidUser(permissions.IsAuthenticated):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):
@@ -86,3 +86,38 @@ class UserConfirmation(permissions.BasePermission):
min_level = ConfirmType.values.index(confirm_type) + 1
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
class ServiceAccountSignaturePermission(permissions.BasePermission):
def has_permission(self, request, view):
from authentication.models import AccessKey
from common.utils.crypto import get_aes_crypto
signature = request.META.get('HTTP_X_JMS_SVC', '')
if not signature or not signature.startswith('Sign'):
return False
data = signature[4:].strip()
if not data or ':' not in data:
return False
ak_id, time_sign = data.split(':', 1)
if not ak_id or not time_sign:
return False
ak = AccessKey.objects.filter(id=ak_id).first()
if not ak or not ak.is_active:
return False
if not ak.user or not ak.user.is_active or not ak.user.is_service_account:
return False
aes = get_aes_crypto(str(ak.secret).replace('-', ''), mode='ECB')
try:
timestamp = aes.decrypt(time_sign)
if not timestamp or not timestamp.isdigit():
return False
timestamp = int(timestamp)
interval = abs(int(time.time()) - timestamp)
if interval > 30:
return False
return True
except Exception:
return False
def has_object_permission(self, request, view, obj):
return False

View File

@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
#
import struct
import random
import socket
import string
import struct
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
@@ -19,6 +18,7 @@ def random_ip():
def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
random.seed(None)
args_names = ['lower', 'upper', 'digit', 'special_char']
args_values = [lower, upper, digit, special_char]
args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]

View File

@@ -26,6 +26,7 @@ class SendAndVerifyCodeUtil(object):
self.target = target
self.backend = backend
self.key = key or self.KEY_TMPL.format(target)
self.verify_key = self.key + '_verify'
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
self.other_args = kwargs
@@ -47,6 +48,11 @@ class SendAndVerifyCodeUtil(object):
raise
def verify(self, code):
times = cache.get(self.verify_key, 0)
if times >= 3:
self.__clear()
raise CodeExpired
cache.set(self.verify_key, times + 1, timeout=self.timeout)
right = cache.get(self.key)
if not right:
raise CodeExpired
@@ -59,6 +65,7 @@ class SendAndVerifyCodeUtil(object):
def __clear(self):
cache.delete(self.key)
cache.delete(self.verify_key)
def __ttl(self):
return cache.ttl(self.key)

View File

@@ -1,4 +1,5 @@
import time
from collections import defaultdict
from django.core.cache import cache
from django.db.models import Count, Max, F
@@ -141,40 +142,35 @@ class DatesLoginMetricMixin:
operate_logs_queryset: OperateLog.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):
queryset = UserLoginLog.objects \
.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]
return self.get_date_metrics(UserLoginLog.objects, 'datetime', 'id')
def get_dates_metrics_total_count_active_users(self):
queryset = Session.objects \
.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]
return self.get_date_metrics(Session.objects, 'date_start', 'user_id')
def get_dates_metrics_total_count_active_assets(self):
queryset = Session.objects \
.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]
return self.get_date_metrics(Session.objects, 'date_start', 'asset_id')
def get_dates_metrics_total_count_sessions(self):
queryset = Session.objects \
.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]
return self.get_date_metrics(Session.objects, 'date_start', 'id')
@lazyproperty
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_perm = path_perms_map.get(path_base, None)
if ".." in request_path:
return False
if not path_perm:
return False
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]
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:
for host in ALLOWED_DOMAINS:
print(' - ' + host.lstrip('.'))
ALLOWED_HOSTS = ['*']
# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS
CSRF_TRUSTED_ORIGINS = []
for host_port in ALLOWED_DOMAINS:
@@ -106,6 +107,7 @@ for host_port in ALLOWED_DOMAINS:
continue
CSRF_TRUSTED_ORIGINS.append('{}://*.{}'.format(schema, origin))
CSRF_FAILURE_VIEW = 'jumpserver.views.other.csrf_failure'
# print("CSRF_TRUSTED_ORIGINS: ")
# for origin in CSRF_TRUSTED_ORIGINS:
# print(' - ' + origin)

View File

@@ -88,3 +88,9 @@ class KokoView(View):
class ResourceDownload(TemplateView):
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
oid sha256:810d46e14e09a2309a8d898bc391f33082bfc5c164dec246bd95cea8436e33ee
size 154540
oid sha256:f87b3d05a2f665ef21a4876b9af8ba651936220b35633d1dc6a93941000bd253
size 154676

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -857,15 +857,19 @@ msgstr "アカウント接続のテスト"
msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない"
#: accounts/utils.py:44
#: accounts/utils.py:43
msgid "Password can not contains `{%` "
msgstr "パスワードには '{%' を含まない"
#: accounts/utils.py:46
msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない"
#: accounts/utils.py:46
#: accounts/utils.py:48
msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない"
#: accounts/utils.py:52
#: accounts/utils.py:54
msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1032,7 +1036,7 @@ msgid "None of the reviewers belong to Organization `{}`"
msgstr "いずれのレビューアも組織 '{}' に属していません"
#: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:133
#: xpack/plugins/cloud/serializers/task.py:137
msgid "IP address invalid: `{}`"
msgstr "IPアドレスが無効: '{}'"
@@ -7696,7 +7700,7 @@ msgstr "クラウドアカウント"
msgid "Test cloud account"
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"
msgstr "リージョン"
@@ -7705,17 +7709,17 @@ msgid "Hostname strategy"
msgstr "ホスト名戦略"
#: 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"
msgstr "IPネットワークセグメントグループ"
#: xpack/plugins/cloud/models.py:103
#: xpack/plugins/cloud/serializers/task.py:155
#: xpack/plugins/cloud/serializers/task.py:159
msgid "Sync IP type"
msgstr "同期IPタイプ"
#: xpack/plugins/cloud/models.py:106
#: xpack/plugins/cloud/serializers/task.py:173
#: xpack/plugins/cloud/serializers/task.py:177
msgid "Always update"
msgstr "常に更新"
@@ -7808,7 +7812,7 @@ msgstr "ルール一致"
msgid "Rule value"
msgstr "ルール値"
#: xpack/plugins/cloud/models.py:317
#: xpack/plugins/cloud/models.py:317 xpack/plugins/cloud/serializers/task.py:70
msgid "Strategy rule"
msgstr "戦略ルール"
@@ -7820,7 +7824,7 @@ msgstr "アクション属性"
msgid "Action value"
msgstr "アクション値"
#: xpack/plugins/cloud/models.py:341
#: xpack/plugins/cloud/models.py:341 xpack/plugins/cloud/serializers/task.py:73
msgid "Strategy action"
msgstr "戦略アクション"
@@ -8108,7 +8112,7 @@ msgstr "テストタイムアウト"
msgid "Project"
msgstr "project"
#: xpack/plugins/cloud/serializers/task.py:139
#: xpack/plugins/cloud/serializers/task.py:143
msgid ""
"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 "
@@ -8122,11 +8126,11 @@ msgstr ""
"ドレスをランダムに一致させることを意味します。 <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"
msgstr "実行回数"
#: xpack/plugins/cloud/serializers/task.py:146
#: xpack/plugins/cloud/serializers/task.py:150
msgid "Instance count"
msgstr "インスタンス数"

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import shutil
import zipfile
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from rest_framework import status
@@ -18,6 +19,7 @@ __all__ = ["PlaybookViewSet", "PlaybookFileBrowserAPIView"]
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils._os import safe_join
def unzip_playbook(src, dist):
@@ -37,7 +39,7 @@ class PlaybookViewSet(OrgBulkModelViewSet):
raise JMSException(code='playbook_has_job', detail={"msg": _("Currently playbook is being used in a job")})
instance_id = instance.id
super().perform_destroy(instance)
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__())
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__())
shutil.rmtree(dest_path)
def get_queryset(self):
@@ -48,8 +50,8 @@ class PlaybookViewSet(OrgBulkModelViewSet):
def perform_create(self, serializer):
instance = serializer.save()
if 'multipart/form-data' in self.request.headers['Content-Type']:
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
src_path = safe_join(settings.MEDIA_ROOT, instance.path.name)
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
try:
unzip_playbook(src_path, dest_path)
except RuntimeError as e:
@@ -60,9 +62,9 @@ class PlaybookViewSet(OrgBulkModelViewSet):
else:
if instance.create_method == 'blank':
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
os.makedirs(dest_path)
with open(os.path.join(dest_path, 'main.yml'), 'w') as f:
with open(safe_join(dest_path, 'main.yml'), 'w') as f:
f.write('## write your playbook here')
@@ -83,13 +85,15 @@ class PlaybookFileBrowserAPIView(APIView):
work_path = playbook.work_dir
file_key = request.query_params.get('key', '')
if file_key:
file_path = os.path.join(work_path, file_key)
with open(file_path, 'r') as f:
try:
try:
file_path = safe_join(work_path, file_key)
with open(file_path, 'r') as f:
content = f.read()
except UnicodeDecodeError:
content = _('Unsupported file content')
return Response({'content': content})
except UnicodeDecodeError:
content = _('Unsupported file content')
except SuspiciousFileOperation:
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
return Response({'content': content})
else:
expand_key = request.query_params.get('expand', '')
nodes = self.generate_tree(playbook, work_path, expand_key)
@@ -105,7 +109,8 @@ class PlaybookFileBrowserAPIView(APIView):
parent_key = ''
if os.path.dirname(parent_key) == 'root':
parent_key = os.path.basename(parent_key)
full_path = os.path.join(work_path, parent_key)
full_path = safe_join(work_path, parent_key)
is_directory = request.data.get('is_directory', False)
content = request.data.get('content', '')
@@ -117,27 +122,30 @@ class PlaybookFileBrowserAPIView(APIView):
p = 'new_file.yml'
else:
p = 'new_dir'
np = os.path.join(full_path, p)
np = safe_join(full_path, p)
n = 0
while os.path.exists(np):
n += 1
np = os.path.join(full_path, '{}({})'.format(p, n))
np = safe_join(full_path, '{}({})'.format(p, n))
return np
if is_directory:
new_file_path = find_new_name(name)
os.makedirs(new_file_path)
else:
new_file_path = find_new_name(name, True)
with open(new_file_path, 'w') as f:
f.write(content)
try:
if is_directory:
new_file_path = find_new_name(name)
os.makedirs(new_file_path)
else:
new_file_path = find_new_name(name, True)
with open(new_file_path, 'w') as f:
f.write(content)
except SuspiciousFileOperation:
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
relative_path = os.path.relpath(os.path.dirname(new_file_path), work_path)
new_node = {
"name": os.path.basename(new_file_path),
"title": os.path.basename(new_file_path),
"id": os.path.join(relative_path, os.path.basename(new_file_path))
if not os.path.join(relative_path, os.path.basename(new_file_path)).startswith('.')
"id": safe_join(relative_path, os.path.basename(new_file_path))
if not safe_join(relative_path, os.path.basename(new_file_path)).startswith('.')
else os.path.basename(new_file_path),
"isParent": is_directory,
"pId": relative_path if not relative_path.startswith('.') else 'root',
@@ -156,7 +164,7 @@ class PlaybookFileBrowserAPIView(APIView):
new_name = request.data.get('new_name', '')
if file_key in self.protected_files and new_name:
return Response({'msg': '{} can not be rename'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST)
raise JMSException(code='this_file_can_not_be_rename', detail={"msg": _("This file can not be rename")})
if os.path.dirname(file_key) == 'root':
file_key = os.path.basename(file_key)
@@ -166,16 +174,20 @@ class PlaybookFileBrowserAPIView(APIView):
if not file_key or file_key == 'root':
return Response(status=status.HTTP_400_BAD_REQUEST)
file_path = os.path.join(work_path, file_key)
file_path = safe_join(work_path, file_key)
# rename
if new_name:
new_file_path = os.path.join(os.path.dirname(file_path), new_name)
if new_file_path == file_path:
return Response(status=status.HTTP_200_OK)
if os.path.exists(new_file_path):
return Response({'msg': '{} already exists'.format(new_name)}, status=status.HTTP_400_BAD_REQUEST)
os.rename(file_path, new_file_path)
try:
new_file_path = safe_join(os.path.dirname(file_path), new_name)
if new_file_path == file_path:
return Response(status=status.HTTP_200_OK)
if os.path.exists(new_file_path):
raise JMSException(code='file_already exists', detail={"msg": _("File already exists")})
os.rename(file_path, new_file_path)
except SuspiciousFileOperation:
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
# edit content
else:
if not is_directory:
@@ -189,10 +201,11 @@ class PlaybookFileBrowserAPIView(APIView):
work_path = playbook.work_dir
file_key = request.query_params.get('key', '')
if not file_key:
return Response({'msg': 'key is required'}, status=status.HTTP_400_BAD_REQUEST)
raise JMSException(code='file_key_is_required', detail={"msg": _("File key is required")})
if file_key in self.protected_files:
return Response({'msg': ' {} can not be delete'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST)
file_path = os.path.join(work_path, file_key)
raise JMSException(code='This file can not be delete', detail={"msg": _("This file can not be delete")})
file_path = safe_join(work_path, file_key)
if os.path.isdir(file_path):
shutil.rmtree(file_path)
else:
@@ -216,11 +229,12 @@ class PlaybookFileBrowserAPIView(APIView):
relative_path = os.path.relpath(path, root_path)
for d in dirs:
dir_id = os.path.relpath(safe_join(path, d), root_path)
node = {
"name": d,
"title": d,
"id": os.path.join(relative_path, d) if not os.path.join(relative_path, d).startswith(
'.') else d,
"id": dir_id,
"isParent": True,
"open": True,
"pId": relative_path if not relative_path.startswith('.') else 'root',
@@ -230,12 +244,12 @@ class PlaybookFileBrowserAPIView(APIView):
node['open'] = True
nodes.append(node)
for f in files:
file_id = os.path.relpath(safe_join(path, f), root_path)
node = {
"name": f,
"title": f,
"iconSkin": 'file',
"id": os.path.join(relative_path, f) if not os.path.join(relative_path, f).startswith(
'.') else f,
"id": file_id,
"isParent": False,
"open": False,
"pId": relative_path if not relative_path.startswith('.') else 'root',

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/style.css' %}" rel="stylesheet">
<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>
<body style="background-color: #f3f3f4">

View File

@@ -148,6 +148,11 @@ class Applet(JMSBaseModel):
shutil.copytree(path, pkg_path)
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):
hosts = self.hosts.filter(is_active=True)
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 common.db.models import JMSBaseModel
from common.utils import is_uuid
from orgs.mixins.models import OrgModelMixin
from orgs.utils import tmp_to_root_org
from users.models import User
@@ -62,6 +63,9 @@ class SessionSharing(JMSBaseModel, OrgModelMixin):
@cached_property
def users_queryset(self):
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)
@property

View File

@@ -1,13 +1,15 @@
from rest_framework import permissions
from common.utils import get_logger
logger = get_logger(__file__)
__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):
try:

View File

@@ -20,6 +20,8 @@ logger = get_logger(__file__)
def on_applet_host_create(sender, instance, created=False, **kwargs):
if not created:
return
Applet.clear_host_prefer()
applets = Applet.objects.all()
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)
with tmp_to_root_org():
serializer.is_valid(raise_exception=True)
instance = serializer.save()
instance = serializer.save()
instance.approve(processor=request.user)
return Response('ok')

View File

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

View File

@@ -3,8 +3,6 @@
#
import base64
import datetime
import random
import string
import uuid
from typing import Callable
@@ -596,8 +594,7 @@ class TokenMixin:
return self.access_keys.first()
def generate_reset_token(self):
letter = string.ascii_letters + string.digits
token = ''.join([random.choice(letter) for _ in range(50)])
token = random_string(50)
self.set_cache(token)
return token

View File

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

View File

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

12
poetry.lock generated
View File

@@ -1348,13 +1348,13 @@ reference = "tsinghua"
[[package]]
name = "click"
version = "8.1.6"
version = "8.1.7"
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"},
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
@@ -1824,7 +1824,7 @@ description = ""
optional = false
python-versions = ">=3.7"
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]
@@ -1833,7 +1833,7 @@ 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"
url = "https://github.com/ibuler/django-cas-ng/releases/download/v4.3.2/django-cas-ng-4.3.2.zip"
[[package]]
name = "django-celery-beat"
@@ -7223,4 +7223,4 @@ reference = "tsinghua"
[metadata]
lock-version = "2.0"
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"
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"
@@ -114,7 +113,7 @@ 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" }
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"
django-auth-ldap = "4.4.0"
boto3 = "1.28.9"
@@ -137,10 +136,11 @@ hvac = "1.1.1"
pyhcl = "0.4.4"
ipy = "1.1"
netifaces = "^0.11.0"
[tool.poetry.group.dev.dependencies]
channels-redis = "4.1.0"
daphne = "4.0.0"
channels = "^4.0.0"
[tool.poetry.group.dev.dependencies]
channels-redis = "^4.1.0"
[tool.poetry.group.xpack.dependencies]