Compare commits

...

39 Commits

Author SHA1 Message Date
Jiangjie.Bai
18c388f3a5 Merge pull request #7629 from jumpserver/dev
v2.19.0-rc3
2022-02-17 11:04:33 +08:00
feng626
b088362ae3 perf: 账号备份性能优化 2022-02-17 11:02:49 +08:00
xinwen
c7f8ebb613 fix: 站内信 ServerPerformanceCheck redis 订阅报错 2022-02-17 10:53:36 +08:00
feng626
de2f9ae687 Merge pull request #7626 from jumpserver/pr@dev@fix_i18n
fix: 翻译
2022-02-16 19:19:55 +08:00
xinwen
a806a2d3e6 fix: 翻译 2022-02-16 19:16:45 +08:00
Jiangjie.Bai
7be76feeb0 Merge pull request #7622 from jumpserver/dev
v2.19.0-rc3
2022-02-16 16:42:19 +08:00
ibuler
f548abcb87 perf: 用户临时密码支持加密传输 2022-02-16 16:31:57 +08:00
xinwen
35c6b581e2 feat: 远程应用支持磁盘挂载 2022-02-16 16:29:06 +08:00
feng626
40b119786b Merge pull request #7621 from jumpserver/pr@dev@ticket_flow
fix: 工单流需要超级管理员配置
2022-02-16 14:47:56 +08:00
feng626
ff9347e344 fix: 工单流需要超级管理员配置 2022-02-16 14:37:26 +08:00
feng626
da0bbcee57 fix: 修复资产账号过滤重复问题 2022-02-16 11:35:53 +08:00
ibuler
824d10ce93 pref: connection token 添加 secret 2022-02-16 11:33:25 +08:00
feng626
1d6dcf9fb5 fix: 修复命令报警翻译 2022-02-15 18:33:52 +08:00
Jiangjie.Bai
3325867f8f fix: 修改LDAP SYNC相关参数设置 2022-02-15 17:57:56 +08:00
xinwen
2f43aeee5d fix: Authbook 修改秘钥不生效 2022-02-15 11:14:51 +08:00
Jiangjie.Bai
764d70ea4f fix: 修改用户组名称翻译 2022-02-14 19:27:41 +08:00
Jiangjie.Bai
ff6dbe67a6 Merge pull request #7610 from jumpserver/dev
v2.19.0-rc2
2022-02-14 18:31:52 +08:00
feng626
7f36958683 perf: 工单流只需组织管理员权限 2022-02-14 18:15:11 +08:00
xinwen
fa5921cd86 fix: 去掉 can_replay 兼容代码 2022-02-14 18:14:37 +08:00
feng626
540679df11 fix: 修复operatelog add remove 翻译问题 2022-02-14 14:04:59 +08:00
fit2bot
597b022905 fix: 修复历史会话文件不能被清理的bug (#7604)
* fix: 修复历史会话文件不能被清理的bug

* perf: 删除调试日志

Co-authored-by: halo <wuyihuangw@gmail.com>
2022-02-14 10:58:30 +08:00
jiangweidong
ab34b9906e perf: 切割一次就可以了 2022-02-10 18:34:09 +08:00
jiangweidong
755fa8efa8 perf: 兼容不同版本间JumpServer获取的SAML2协议用户属性 2022-02-10 18:34:09 +08:00
feng626
889713f00e fix: 修复工单流程设置显示其它组织问题 2022-02-10 14:07:32 +08:00
Jiangjie.Bai
c10436de47 Merge pull request #7589 from jumpserver/dev
v2.19.0-rc1
2022-02-10 11:24:28 +08:00
xinwen
333bb64b8b fix: Authbook 取认证信息bug 2022-02-10 11:20:40 +08:00
Michael Bai
67a49dc5e9 feat: syslog 记录的数据来源于序列类 2022-02-08 19:22:08 +08:00
Michael Bai
8085db7acc feat: 增加系统设置(安全)控制第三方认证用户是否进行MFA认证 2022-02-08 17:48:40 +08:00
Michael Bai
6adeafd1d2 fix: 修复创建命令过滤规则失败的问题 2022-02-08 14:21:43 +08:00
xinwen
1faba95a48 fix: 修复 xrdp 连接资产时会生成用户登录日志 2022-02-08 12:38:27 +08:00
吴小白
ab07091eb8 perf: 优化 quick_start.sh 2022-02-08 12:36:43 +08:00
xinwen
f994f5d776 fix: redis 订阅 bug 2022-02-08 12:35:16 +08:00
ibuler
55fae1667d fix: 修复 redis 连接过多的问题 2022-02-08 12:35:16 +08:00
Michael Bai
b3397c6aeb feat: 命令过滤规则增加忽略大小写选项 2022-02-08 12:31:15 +08:00
老广
8ab4f6f004 docs: 添加 pr 提示说明 2022-02-07 15:39:50 +08:00
老广
5a37540eda docs: 修改贡献提示 2022-02-07 15:39:50 +08:00
老广
1c1d507e34 Create CONTRIBUTING.md 2022-02-07 15:39:50 +08:00
老广
9c40314edc Create CODE_OF_CONDUCT.md 2022-02-07 15:39:22 +08:00
feng626
acdde5a236 fix: 密钥密码翻译 2022-01-27 18:19:44 +08:00
40 changed files with 633 additions and 364 deletions

9
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,9 @@
#### What this PR does / why we need it?
#### Summary of your change
#### Please indicate you've done the following:
- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.

View File

@@ -20,7 +20,7 @@ jobs:
run: |
TAG=$(basename ${GITHUB_REF})
VERSION=${TAG/v/}
wget https://raw.githubusercontent.com/jumpserver/installer/v${VERSION}/quick_start.sh
wget https://raw.githubusercontent.com/jumpserver/installer/master/quick_start.sh
sed -i "s@Version=.*@Version=v${VERSION}@g" quick_start.sh
echo "::set-output name=TAG::$TAG"
echo "::set-output name=VERSION::$VERSION"

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

25
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,25 @@
# Contributing
## Create pull request
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
This [development guideline](https://docs.jumpserver.org/zh/master/dev/rest_api/) contains information about repository structure, how to setup development environment, how to run it, and more.
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
## Report issues
It is a great way to contribute by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue and follow the template to fill in required information.
Before opening any issue, please look up the existing issues to avoid submitting a duplication.
If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment.
When reporting issues, always include:
* Which version you are using.
* Steps to reproduce the issue.
* Snapshots or log files if needed
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
replace those parts with "REDACTED" or other strings like "****".

View File

@@ -26,6 +26,7 @@ class AccountFilterSet(BaseFilterSet):
qs = super().qs
qs = self.filter_username(qs)
qs = self.filter_node(qs)
qs = qs.distinct()
return qs
def filter_username(self, qs):

View File

@@ -1,18 +1,17 @@
# ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from django.middleware import csrf
from rest_framework.response import Response
from django.db.models import Q
from common.utils import get_logger, get_object_or_none
from common.utils.crypto import get_aes_crypto
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.mixins.api import SuggestionMixin
from orgs.utils import tmp_to_root_org
from rest_framework.decorators import action
from users.models import User, UserGroup
from applications.models import Application
from ..models import SystemUser, Asset, CommandFilter, CommandFilterRule
from ..models import SystemUser, CommandFilterRule
from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer
from ..tasks import (
@@ -95,17 +94,27 @@ class SystemUserTempAuthInfoApi(generics.CreateAPIView):
permission_classes = (IsValidUser,)
serializer_class = SystemUserTempAuthSerializer
def decrypt_data_if_need(self, data):
csrf_token = self.request.META.get('CSRF_COOKIE')
aes = get_aes_crypto(csrf_token, 'ECB')
password = data.get('password', '')
try:
data['password'] = aes.decrypt(password)
except:
pass
return data
def create(self, request, *args, **kwargs):
serializer = super().get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
pk = kwargs.get('pk')
user = self.request.user
data = serializer.validated_data
data = self.decrypt_data_if_need(serializer.validated_data)
instance_id = data.get('instance_id')
with tmp_to_root_org():
instance = get_object_or_404(SystemUser, pk=pk)
instance.set_temp_auth(instance_id, user.id, data)
instance.set_temp_auth(instance_id, self.request.user, data)
return Response(serializer.data, status=201)

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.13 on 2022-02-08 02:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0084_auto_20220112_1959'),
]
operations = [
migrations.AddField(
model_name='commandfilterrule',
name='ignore_case',
field=models.BooleanField(default=True, verbose_name='Ignore case'),
),
]

View File

@@ -85,6 +85,7 @@ class CommandFilterRule(OrgModelMixin):
validators=[MinValueValidator(1), MaxValueValidator(100)]
)
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case'))
action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action"))
# 动作: 附加字段
# - confirm: 命令复核人
@@ -129,13 +130,16 @@ class CommandFilterRule(OrgModelMixin):
regex.append(r'\b{0}\b'.format(cmd))
else:
regex.append(r'\b{0}'.format(cmd))
s = r'(?i){}'.format('|'.join(regex))
s = r'{}'.format('|'.join(regex))
return s
@staticmethod
def compile_regex(regex):
def compile_regex(regex, ignore_case):
try:
pattern = re.compile(regex)
if ignore_case:
pattern = re.compile(regex, re.IGNORECASE)
else:
pattern = re.compile(regex)
except Exception as e:
error = _('The generated regular expression is incorrect: {}').format(str(e))
logger.error(error)
@@ -143,7 +147,7 @@ class CommandFilterRule(OrgModelMixin):
return True, '', pattern
def match(self, data):
succeed, error, pattern = self.compile_regex(regex=self.pattern)
succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case)
if not succeed:
return self.ACTION_UNKNOWN, ''

View File

@@ -150,17 +150,37 @@ class AuthMixin:
def load_asset_special_auth(self, asset, username=''):
"""
AuthBook 的数据状态
| asset | systemuser | username |
1 | * | * | x |
2 | * | x | * |
当前 AuthBook 只有以上两种状态systemuser 与 username 不会并存。
正常的资产与系统用户关联产生的是第1种状态改密则产生第2种状态。改密之后
只有 username 而没有 systemuser 。
Freq: 关联同一资产的多个系统用户指定同一用户名时,修改用户密码会影响所有系统用户
这里有一个不对称的行为,同名系统用户密码覆盖
当有相同 username 的多个系统用户时,有改密动作之后,所有的同名系统用户都使用最后
一次改动,但如果没有发生过改密,同名系统用户使用的密码还是各自的。
"""
authbooks = list(AuthBook.objects.filter(asset=asset).filter(
Q(username=username) | Q(systemuser=self)
))
if len(authbooks) == 0:
if username == '':
username = self.username
authbook = AuthBook.objects.filter(
asset=asset, username=username, systemuser__isnull=True
).order_by('-date_created').first()
if not authbook:
authbook = AuthBook.objects.filter(
asset=asset, systemuser=self
).order_by('-date_created').first()
if not authbook:
return None
elif len(authbooks) == 1:
authbook = authbooks[0]
else:
authbooks.sort(key=lambda x: 1 if x.username == username else 0, reverse=True)
authbook = authbooks[0]
authbook.load_auth()
self.password = authbook.password
self.private_key = authbook.private_key

View File

@@ -6,6 +6,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import AuthSerializerMixin
from .utils import validate_password_contains_left_double_curly_bracket
from common.utils.encode import ssh_pubkey_gen
class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
@@ -40,6 +41,21 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
}
ref_name = 'AssetAccountSerializer'
def _validate_gen_key(self, attrs):
private_key = attrs.get('private_key')
if not private_key:
return attrs
password = attrs.get('passphrase')
username = attrs.get('username')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
attrs['public_key'] = public_key
return attrs
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
return attrs
def get_protocols(self, v):
return v.protocols.replace(' ', ', ')

View File

@@ -2,7 +2,7 @@
#
from io import StringIO
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key

View File

@@ -38,7 +38,7 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
model = CommandFilterRule
fields_mini = ['id']
fields_small = fields_mini + [
'type', 'type_display', 'content', 'pattern', 'priority',
'type', 'type_display', 'content', 'ignore_case', 'pattern', 'priority',
'action', 'action_display', 'reviewers',
'date_created', 'date_updated',
'comment', 'created_by',
@@ -66,7 +66,8 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
regex = CommandFilterRule.construct_command_regex(content)
else:
regex = content
succeed, error, pattern = CommandFilterRule.compile_regex(regex)
ignore_case = self.initial_data.get('ignore_case')
succeed, error, pattern = CommandFilterRule.compile_regex(regex, ignore_case)
if not succeed:
raise serializers.ValidationError(error)
return content

View File

@@ -79,7 +79,7 @@ def subscribe_node_assets_mapping_expire(sender, **kwargs):
Node.expire_node_all_asset_ids_mapping_from_memory(root_org_id)
def keep_subscribe_node_assets_relation():
node_assets_mapping_for_memory_pub_sub.keep_handle_msg(handle_node_relation_change)
node_assets_mapping_for_memory_pub_sub.subscribe(handle_node_relation_change)
t = threading.Thread(target=keep_subscribe_node_assets_relation)
t.daemon = True

View File

@@ -52,10 +52,11 @@ class BaseAccountHandler:
return header_fields
@classmethod
def create_row(cls, account, serializer_cls):
def create_row(cls, account, serializer_cls, header_fields=None):
serializer = serializer_cls(account)
if not header_fields:
header_fields = cls.get_header_fields(serializer)
data = cls.unpack_data(serializer.data)
header_fields = cls.get_header_fields(serializer)
row_dict = {}
for field, header_name in header_fields.items():
row_dict[header_name] = data[field]
@@ -74,12 +75,16 @@ class AssetAccountHandler(BaseAccountHandler):
def create_df(cls):
df_dict = defaultdict(list)
sheet_name = AuthBook._meta.verbose_name
accounts = AuthBook.get_queryset()
accounts = AuthBook.get_queryset().select_related('systemuser')
if not accounts.first():
return df_dict
header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first()))
for account in accounts:
account.load_auth()
row = cls.create_row(account, AccountSecretSerializer)
row = cls.create_row(account, AccountSecretSerializer, header_fields)
df_dict[sheet_name].append(row)
for k, v in df_dict.items():
df_dict[k] = pd.DataFrame(v)
@@ -98,7 +103,7 @@ class AppAccountHandler(BaseAccountHandler):
@classmethod
def create_df(cls):
df_dict = defaultdict(list)
accounts = Account.get_queryset()
accounts = Account.get_queryset().select_related('systemuser')
for account in accounts:
account.load_auth()
app_type = account.type

View File

@@ -4,10 +4,10 @@ from itertools import groupby
from celery import shared_task
from common.db.utils import get_object_if_need, get_objects
from django.utils.translation import ugettext as _, gettext_noop
from django.db.models import Empty, Q
from django.db.models import Empty
from common.utils import encrypt_password, get_logger
from assets.models import SystemUser, Asset, AuthBook
from assets.models import SystemUser, Asset
from orgs.utils import org_aware_func, tmp_to_root_org
from . import const
from .utils import clean_ansible_task_hosts, group_asset_by_platform
@@ -178,6 +178,7 @@ def get_push_windows_system_user_tasks(system_user: SystemUser, username=None):
def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
"""
获取推送系统用户的 ansible 命令,跟资产无关
:param system_user:
:param platform:
:param username: 当动态时,近推送某个
@@ -209,18 +210,10 @@ def push_system_user_util(system_user, assets, task_name, username=None):
if not assets:
return {}
# 资产按平台分类
assets_sorted = sorted(assets, key=group_asset_by_platform)
platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
def run_task(_tasks, _hosts):
if not _tasks:
return
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
)
task.run()
if system_user.username_same_with_user:
if username is None:
# 动态系统用户,但是没有指定 username
@@ -232,6 +225,15 @@ def push_system_user_util(system_user, assets, task_name, username=None):
assert username is None, 'Only Dynamic user can assign `username`'
usernames = [system_user.username]
def run_task(_tasks, _hosts):
if not _tasks:
return
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
)
task.run()
for platform, _assets in platform_hosts:
_assets = list(_assets)
if not _assets:
@@ -239,36 +241,11 @@ def push_system_user_util(system_user, assets, task_name, username=None):
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_assets)))
id_asset_map = {_asset.id: _asset for _asset in _assets}
asset_ids = id_asset_map.keys()
no_special_auth = []
special_auth_set = set()
auth_books = AuthBook.objects.filter(asset_id__in=asset_ids).filter(
Q(username__in=usernames) | Q(systemuser=system_user)
).prefetch_related('systemuser')
for auth_book in auth_books:
auth_book.load_auth()
special_auth_set.add((auth_book.username, auth_book.asset_id))
for _username in usernames:
no_special_assets = []
for asset_id in asset_ids:
if (_username, asset_id) not in special_auth_set:
no_special_assets.append(id_asset_map[asset_id])
if no_special_assets:
no_special_auth.append((_username, no_special_assets))
for _username, no_special_assets in no_special_auth:
tasks = get_push_system_user_tasks(system_user, platform, username=_username)
run_task(tasks, no_special_assets)
for auth_book in auth_books:
system_user._merge_auth(auth_book)
tasks = get_push_system_user_tasks(system_user, platform, username=auth_book.username)
asset = id_asset_map[auth_book.asset_id]
run_task(tasks, [asset])
for u in usernames:
for a in _assets:
system_user.load_asset_special_auth(a, u)
tasks = get_push_system_user_tasks(system_user, platform, username=u)
run_task(tasks, [a])
@shared_task(queue="ansible")

View File

@@ -22,13 +22,15 @@ from users.models import User
from users.signals import post_user_change_password
from terminal.models import Session, Command
from .utils import write_login_log
from . import models
from . import models, serializers
from .models import OperateLog
from orgs.utils import current_org
from perms.models import AssetPermission, ApplicationPermission
from terminal.backends.command.serializers import SessionCommandSerializer
from terminal.serializers import SessionSerializer
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR
from common.utils import get_request_ip, get_logger, get_syslogger
from common.utils.encode import model_to_json
from common.utils.encode import data_to_json
logger = get_logger(__name__)
sys_logger = get_syslogger(__name__)
@@ -168,9 +170,9 @@ M2M_NEED_RECORD = {
}
M2M_ACTION = {
POST_ADD: 'add',
POST_REMOVE: 'remove',
POST_CLEAR: 'remove',
POST_ADD: OperateLog.ACTION_CREATE,
POST_REMOVE: OperateLog.ACTION_DELETE,
POST_CLEAR: OperateLog.ACTION_DELETE,
}
@@ -185,14 +187,14 @@ def on_m2m_changed(sender, action, instance, reverse, model, pk_set, **kwargs):
sender_name = sender._meta.object_name
if sender_name in M2M_NEED_RECORD:
action = M2M_ACTION[action]
org_id = current_org.id
remote_addr = get_request_ip(current_request)
user = str(user)
resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name]
if action == 'add':
action = M2M_ACTION[action]
if action == OperateLog.ACTION_CREATE:
resource_tmpl = resource_tmpl_add
elif action == 'remove':
elif action == OperateLog.ACTION_DELETE:
resource_tmpl = resource_tmpl_remove
to_create = []
@@ -255,20 +257,27 @@ def on_user_change_password(sender, user=None, **kwargs):
def on_audits_log_create(sender, instance=None, **kwargs):
if sender == models.UserLoginLog:
category = "login_log"
serializer_cls = serializers.UserLoginLogSerializer
elif sender == models.FTPLog:
category = "ftp_log"
serializer_cls = serializers.FTPLogSerializer
elif sender == models.OperateLog:
category = "operation_log"
serializer_cls = serializers.OperateLogSerializer
elif sender == models.PasswordChangeLog:
category = "password_change_log"
serializer_cls = serializers.PasswordChangeLogSerializer
elif sender == Session:
category = "host_session_log"
serializer_cls = SessionSerializer
elif sender == Command:
category = "session_command_log"
serializer_cls = SessionCommandSerializer
else:
return
data = model_to_json(instance, indent=None)
serializer = serializer_cls(instance)
data = data_to_json(serializer.data, indent=None)
msg = "{} - {}".format(category, data)
sys_logger.info(msg)

View File

@@ -11,6 +11,7 @@ from django.conf import settings
from django.core.cache import cache
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.utils import timezone
from django.utils.translation import ugettext as _
from rest_framework.response import Response
from rest_framework.request import Request
@@ -20,15 +21,17 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework import serializers
from applications.models import Application
from authentication.signals import post_auth_failed, post_auth_success
from authentication.signals import post_auth_failed
from common.utils import get_logger, random_string
from common.mixins.api import SerializerMixin
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
from common.utils.common import get_file_by_arch
from orgs.mixins.api import RootOrgViewMixin
from common.http import is_true
from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user
from perms.models.base import Action
from perms.utils.application.permission import validate_permission as app_validate_permission
from perms.utils.application.permission import get_application_actions
from perms.utils.asset.permission import get_asset_actions
from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
@@ -99,10 +102,14 @@ class ClientProtocolMixin:
token = self.create_token(user, asset, application, system_user)
# 设置磁盘挂载
if drives_redirect and asset:
systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset)
actions = systemuser_actions_mapper.get(system_user.id, 0)
if actions & Action.UPDOWNLOAD:
if drives_redirect:
actions = 0
if asset:
actions = get_asset_actions(user, asset, system_user)
elif application:
actions = get_application_actions(user, application, system_user)
if actions & Action.UPDOWNLOAD == Action.UPDOWNLOAD:
options['drivestoredirect:s'] = '*'
# 全屏
@@ -148,7 +155,6 @@ class ClientProtocolMixin:
return name, content
def get_encrypt_cmdline(self, app: Application):
parameters = app.get_rdp_remote_app_setting()['parameters']
parameters = parameters.encode('ascii')
@@ -231,19 +237,16 @@ class SecretDetailMixin:
@staticmethod
def _get_application_secret_detail(application):
from perms.models.base import Action
gateway = None
remote_app = None
asset = None
if not application.category_remote_app:
actions = Action.NONE
remote_app = {}
asset = None
domain = application.domain
else:
if application.category_remote_app:
remote_app = application.get_rdp_remote_app_setting()
actions = Action.CONNECT
asset = application.get_remote_app_asset()
domain = asset.domain
else:
domain = application.domain
if domain and domain.has_gateway():
gateway = domain.random_gateway()
@@ -253,15 +256,10 @@ class SecretDetailMixin:
'application': application,
'gateway': gateway,
'remote_app': remote_app,
'actions': actions
}
@staticmethod
def _get_asset_secret_detail(asset, user, system_user):
from perms.utils.asset import get_asset_system_user_ids_with_actions_by_user
systemuserid_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset)
actions = systemuserid_actions_mapper.get(system_user.id, [])
def _get_asset_secret_detail(asset):
gateway = None
if asset and asset.domain and asset.domain.has_gateway():
gateway = asset.domain.random_gateway()
@@ -271,14 +269,13 @@ class SecretDetailMixin:
'application': None,
'gateway': gateway,
'remote_app': None,
'actions': actions,
}
@action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail')
def get_secret_detail(self, request, *args, **kwargs):
token = request.data.get('token', '')
try:
value, user, system_user, asset, app, expired_at = self.valid_token(token)
value, user, system_user, asset, app, expired_at, actions = self.valid_token(token)
except serializers.ValidationError as e:
post_auth_failed.send(
sender=self.__class__, username='', request=self.request,
@@ -286,9 +283,13 @@ class SecretDetailMixin:
)
raise e
data = dict(user=user, system_user=system_user, expired_at=expired_at)
data = dict(
id=token, secret=value.get('secret', ''),
user=user, system_user=system_user,
expired_at=expired_at, actions=actions
)
if asset:
asset_detail = self._get_asset_secret_detail(asset, user=user, system_user=system_user)
asset_detail = self._get_asset_secret_detail(asset)
system_user.load_asset_more_auth(asset.id, user.username, user.id)
data['type'] = 'asset'
data.update(asset_detail)
@@ -298,9 +299,6 @@ class SecretDetailMixin:
data['type'] = 'application'
data.update(app_detail)
self.request.session['auth_backend'] = settings.AUTH_BACKEND_AUTH_TOKEN
post_auth_success.send(sender=self.__class__, user=user, request=self.request, login_type='T')
serializer = self.get_serializer(data)
return Response(data=serializer.data, status=200)
@@ -336,11 +334,16 @@ class UserConnectionTokenViewSet(
raise PermissionDenied('Only super user can create user token')
self.check_resource_permission(user, asset, application, system_user)
token = random_string(36)
secret = random_string(16)
value = {
'id': token,
'secret': secret,
'user': str(user.id),
'username': user.username,
'system_user': str(system_user.id),
'system_user_name': system_user.name
'system_user_name': system_user.name,
'created_by': str(self.request.user),
'date_created': str(timezone.now())
}
if asset:
@@ -398,7 +401,7 @@ class UserConnectionTokenViewSet(
if not has_perm:
raise serializers.ValidationError('Permission expired or invalid')
return value, user, system_user, asset, app, expired_at
return value, user, system_user, asset, app, expired_at, actions
def get_permissions(self):
if self.action in ["create", "get_rdp_file"]:

View File

@@ -171,6 +171,7 @@ class PrepareRequestMixin:
valid_attrs = ['username', 'name', 'email', 'comment', 'phone']
for attr, value in attrs.items():
attr = attr.rsplit('/', 1)[-1]
if attr not in valid_attrs:
continue
user_attrs[attr] = self.value_to_str(value)

View File

@@ -191,6 +191,8 @@ class ConnectionTokenApplicationSerializer(serializers.ModelSerializer):
class ConnectionTokenSecretSerializer(serializers.Serializer):
id = serializers.CharField(read_only=True)
secret = serializers.CharField(read_only=True)
type = serializers.ChoiceField(choices=[('application', 'Application'), ('asset', 'Asset')])
user = ConnectionTokenUserSerializer(read_only=True)
asset = ConnectionTokenAssetSerializer(read_only=True)

View File

@@ -17,7 +17,9 @@ from .signals import post_auth_success, post_auth_failed
@receiver(user_logged_in)
def on_user_auth_login_success(sender, user, request, **kwargs):
# 开启了 MFA且没有校验过, 可以全局校验, middleware 中可以全局管理 oidc 等第三方认证的 MFA
if user.mfa_enabled and not request.session.get('auth_mfa'):
if settings.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY \
and user.mfa_enabled \
and not request.session.get('auth_mfa'):
request.session['auth_mfa_required'] = 1
# 单点登录,超过了自动退出

View File

@@ -1,4 +1,5 @@
import json
import threading
import redis
from django.conf import settings
@@ -19,49 +20,84 @@ def get_redis_client(db):
return rc
class Subscription:
def __init__(self, ch, sub, ):
self.ch = ch
self.sub = sub
def _handle_msg(self, _next, error, complete):
"""
handle arg is the pub published
:param _next: next msg handler
:param error: error msg handler
:param complete: complete msg handler
:return:
"""
msgs = self.sub.listen()
if error is None:
error = lambda m, i: None
if complete is None:
complete = lambda: None
try:
for msg in msgs:
if msg["type"] != "message":
continue
item = None
try:
item_json = msg['data'].decode()
item = json.loads(item_json)
with safe_db_connection():
_next(item)
except Exception as e:
error(msg, item)
logger.error('Subscribe handler handle msg error: ', e)
except Exception as e:
logger.error('Consume msg error: ', e)
try:
complete()
except Exception as e:
logger.error('Complete subscribe error: {}'.format(e))
pass
try:
self.unsubscribe()
except Exception as e:
logger.error("Redis observer close error: {}".format(e))
def keep_handle_msg(self, _next, error, complete):
t = threading.Thread(target=self._handle_msg, args=(_next, error, complete))
t.daemon = True
t.start()
return t
def unsubscribe(self):
try:
self.sub.close()
except Exception as e:
logger.error('Unsubscribe msg error: {}'.format(e))
class RedisPubSub:
def __init__(self, ch, db=10):
self.ch = ch
self.redis = get_redis_client(db)
def subscribe(self):
def subscribe(self, _next, error=None, complete=None):
ps = self.redis.pubsub()
ps.subscribe(self.ch)
return ps
sub = Subscription(self.ch, ps)
sub.keep_handle_msg(_next, error, complete)
return sub
def publish(self, data):
data_json = json.dumps(data)
self.redis.publish(self.ch, data_json)
return True
def keep_handle_msg(self, handle):
"""
handle arg is the pub published
:param handle: lambda item: do_something
:return:
"""
sub = self.subscribe()
msgs = sub.listen()
try:
for msg in msgs:
if msg["type"] != "message":
continue
try:
item_json = msg['data'].decode()
item = json.loads(item_json)
with safe_db_connection():
handle(item)
except Exception as e:
logger.error('Subscribe handler handle msg error: ', e)
except Exception as e:
logger.error('Consume msg error: ', e)
try:
sub.close()
except Exception as e:
logger.error("Redis observer close error: ", e)

View File

@@ -208,30 +208,7 @@ def ensure_last_char_is_ascii(data):
secret_pattern = re.compile(r'password|secret|key', re.IGNORECASE)
def model_to_dict_pro(instance, fields=None, exclude=None):
from ..fields.model import EncryptMixin
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields):
if not getattr(f, 'editable', False):
continue
if fields and f.name not in fields:
continue
if exclude and f.name in exclude:
continue
if isinstance(f, FileField):
continue
if isinstance(f, EncryptMixin):
continue
if secret_pattern.search(f.name):
continue
value = f.value_from_object(instance)
data[f.name] = value
return data
def model_to_json(instance, sort_keys=True, indent=2, cls=None):
data = model_to_dict_pro(instance)
def data_to_json(data, sort_keys=True, indent=2, cls=None):
if cls is None:
cls = DjangoJSONEncoder
return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls)

View File

@@ -312,6 +312,7 @@ class Config(dict):
# 安全配置
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY': True,
'SECURITY_COMMAND_EXECUTION': True,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,

View File

@@ -32,6 +32,7 @@ TERMINAL_REPLAY_STORAGE = CONFIG.TERMINAL_REPLAY_STORAGE
# Security settings
SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH
SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = CONFIG.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY
SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute
SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:07244b630278a5574b97c46218ae453de71d01a1ea6682b88baa741a99cf8c22
size 97436
oid sha256:67a04954f2a6c1da8c2f40307166a87ba1088e858422378eb638a1d1aafc5a89
size 97595

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-20 10:38+0800\n"
"POT-Creation-Date: 2022-02-15 17:52+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"
@@ -35,12 +35,12 @@ msgid "Name"
msgstr "名称"
#: acls/models/base.py:27 assets/models/cmd_filter.py:84
#: assets/models/user.py:214
#: assets/models/user.py:234
msgid "Priority"
msgstr "优先级"
#: acls/models/base.py:28 assets/models/cmd_filter.py:84
#: assets/models/user.py:214
#: assets/models/user.py:234
msgid "1-100, the lower the value will be match first"
msgstr "优先级可选范围为 1-100 (数值越小越优先)"
@@ -55,7 +55,7 @@ msgstr "激活中"
#: assets/models/asset.py:144 assets/models/asset.py:232
#: assets/models/backup.py:54 assets/models/base.py:180
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48
#: assets/models/cmd_filter.py:95 assets/models/domain.py:25
#: assets/models/cmd_filter.py:96 assets/models/domain.py:25
#: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:27
#: perms/models/base.py:93 settings/models.py:34 terminal/models/storage.py:26
@@ -104,7 +104,7 @@ msgstr "规则"
#: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26
#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75
#: assets/models/cmd_filter.py:88 audits/models.py:58 audits/serializers.py:51
#: assets/models/cmd_filter.py:89 audits/models.py:58 audits/serializers.py:51
#: authentication/templates/authentication/_access_key_modal.html:34
#: users/templates/users/_granted_assets.html:29
#: users/templates/users/user_asset_permission.html:44
@@ -114,7 +114,7 @@ msgid "Action"
msgstr "动作"
#: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32
#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:93
#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:94
msgid "Reviewers"
msgstr "审批人"
@@ -124,7 +124,7 @@ msgstr "登录访问控制"
#: acls/models/login_asset_acl.py:21
#: applications/serializers/application.py:122
#: applications/serializers/application.py:165
#: applications/serializers/application.py:166
msgid "System User"
msgstr "系统用户"
@@ -208,7 +208,7 @@ msgid ""
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}"
#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:214
#: assets/models/domain.py:63 assets/models/user.py:215
#: assets/models/domain.py:63 assets/models/user.py:235
#: terminal/serializers/session.py:30 terminal/serializers/storage.py:69
msgid "Protocol"
msgstr "协议"
@@ -270,7 +270,7 @@ msgid "Application"
msgstr "应用程序"
#: applications/models/account.py:15 assets/models/authbook.py:20
#: assets/models/cmd_filter.py:42 assets/models/user.py:305 audits/models.py:40
#: assets/models/cmd_filter.py:42 assets/models/user.py:325 audits/models.py:40
#: perms/models/application_permission.py:32
#: perms/models/asset_permission.py:26 templates/_nav.html:45
#: terminal/backends/command/models.py:21
@@ -311,11 +311,11 @@ msgstr "类别"
#: applications/models/application.py:207
#: applications/serializers/application.py:101 assets/models/backup.py:49
#: assets/models/cmd_filter.py:82 assets/models/user.py:213
#: assets/models/cmd_filter.py:82 assets/models/user.py:233
#: perms/models/application_permission.py:23
#: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:55 terminal/models/storage.py:116
#: tickets/models/flow.py:55 tickets/models/ticket.py:52
#: tickets/models/flow.py:56 tickets/models/ticket.py:52
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
#: xpack/plugins/change_auth_plan/models/app.py:28
#: xpack/plugins/change_auth_plan/models/app.py:153
@@ -368,7 +368,7 @@ msgid "Date updated"
msgstr "更新日期"
#: applications/serializers/application.py:121
#: applications/serializers/application.py:164
#: applications/serializers/application.py:165
msgid "Application display"
msgstr "应用名称"
@@ -564,7 +564,7 @@ msgstr "主机名原始"
msgid "Protocols"
msgstr "协议组"
#: assets/models/asset.py:219 assets/models/user.py:205
#: assets/models/asset.py:219 assets/models/user.py:225
#: perms/models/asset_permission.py:25
#: xpack/plugins/change_auth_plan/models/asset.py:43
#: xpack/plugins/gathered_user/models.py:24
@@ -577,7 +577,7 @@ msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:223 assets/models/cluster.py:19
#: assets/models/user.py:202 assets/models/user.py:354 templates/_nav.html:44
#: assets/models/user.py:222 assets/models/user.py:374 templates/_nav.html:44
msgid "Admin user"
msgstr "特权用户"
@@ -595,7 +595,7 @@ msgstr "标签管理"
#: assets/models/asset.py:230 assets/models/base.py:183
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:98 assets/models/group.py:21
#: assets/models/cmd_filter.py:99 assets/models/group.py:21
#: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:25
#: orgs/models.py:437 perms/models/base.py:91 users/models/user.py:593
#: users/serializers/group.py:33
@@ -703,7 +703,7 @@ msgstr "可连接性"
msgid "Date verified"
msgstr "校验日期"
#: assets/models/base.py:177 audits/signals_handler.py:66
#: assets/models/base.py:177 audits/signals_handler.py:68
#: authentication/forms.py:22
#: authentication/templates/authentication/login.html:151
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
@@ -817,15 +817,19 @@ msgstr "内容"
msgid "One line one command"
msgstr "每行一个命令"
#: assets/models/cmd_filter.py:102
#: assets/models/cmd_filter.py:88
msgid "Ignore case"
msgstr "忽略大小写"
#: assets/models/cmd_filter.py:103
msgid "Command filter rule"
msgstr "命令过滤规则"
#: assets/models/cmd_filter.py:140
#: assets/models/cmd_filter.py:144
msgid "The generated regular expression is incorrect: {}"
msgstr "生成的正则表达式有误"
#: assets/models/cmd_filter.py:166 tickets/const.py:13
#: assets/models/cmd_filter.py:170 tickets/const.py:13
msgid "Command confirm"
msgstr "命令复核"
@@ -902,75 +906,75 @@ msgstr "ssh私钥"
msgid "Node"
msgstr "节点"
#: assets/models/user.py:196
#: assets/models/user.py:216
msgid "Automatic managed"
msgstr "托管密码"
#: assets/models/user.py:197
#: assets/models/user.py:217
msgid "Manually input"
msgstr "手动输入"
#: assets/models/user.py:201
#: assets/models/user.py:221
msgid "Common user"
msgstr "普通用户"
#: assets/models/user.py:204
#: assets/models/user.py:224
msgid "Username same with user"
msgstr "用户名与用户相同"
#: assets/models/user.py:207 assets/serializers/domain.py:29
#: assets/models/user.py:227 assets/serializers/domain.py:29
#: templates/_nav.html:39
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
#: xpack/plugins/change_auth_plan/models/asset.py:39
msgid "Assets"
msgstr "资产"
#: assets/models/user.py:211 templates/_nav.html:17
#: assets/models/user.py:231 templates/_nav.html:17
#: users/views/profile/pubkey.py:37
msgid "Users"
msgstr "用户管理"
#: assets/models/user.py:212
#: assets/models/user.py:232
msgid "User groups"
msgstr "用户组"
#: assets/models/user.py:216
#: assets/models/user.py:236
msgid "Auto push"
msgstr "自动推送"
#: assets/models/user.py:217
#: assets/models/user.py:237
msgid "Sudo"
msgstr "Sudo"
#: assets/models/user.py:218
#: assets/models/user.py:238
msgid "Shell"
msgstr "Shell"
#: assets/models/user.py:219
#: assets/models/user.py:239
msgid "Login mode"
msgstr "认证方式"
#: assets/models/user.py:220
#: assets/models/user.py:240
msgid "SFTP Root"
msgstr "SFTP根路径"
#: assets/models/user.py:221 authentication/models.py:45
#: assets/models/user.py:241 authentication/models.py:45
msgid "Token"
msgstr ""
#: assets/models/user.py:222
#: assets/models/user.py:242
msgid "Home"
msgstr "家目录"
#: assets/models/user.py:223
#: assets/models/user.py:243
msgid "System groups"
msgstr "用户组"
#: assets/models/user.py:226
#: assets/models/user.py:246
msgid "User switch"
msgstr "用户切换"
#: assets/models/user.py:227
#: assets/models/user.py:247
msgid "Switch from"
msgstr "切换自"
@@ -1043,6 +1047,7 @@ msgid "Actions"
msgstr "动作"
#: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147
#: settings/serializers/auth/ldap.py:59
#: xpack/plugins/change_auth_plan/serializers/base.py:42
msgid "Periodic perform"
msgstr "定时执行"
@@ -1221,24 +1226,24 @@ msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr "自检程序已经在运行,不能重复启动"
#: assets/tasks/push_system_user.py:199
#: assets/tasks/push_system_user.py:200
msgid "System user is dynamic: {}"
msgstr "系统用户是动态的: {}"
#: assets/tasks/push_system_user.py:239
#: assets/tasks/push_system_user.py:241
msgid "Start push system user for platform: [{}]"
msgstr "推送系统用户到平台: [{}]"
#: assets/tasks/push_system_user.py:240
#: assets/tasks/push_system_user.py:242
#: assets/tasks/system_user_connectivity.py:106
msgid "Hosts count: {}"
msgstr "主机数量: {}"
#: assets/tasks/push_system_user.py:282 assets/tasks/push_system_user.py:315
#: assets/tasks/push_system_user.py:259 assets/tasks/push_system_user.py:292
msgid "Push system users to assets: "
msgstr "推送系统用户到入资产: "
#: assets/tasks/push_system_user.py:294
#: assets/tasks/push_system_user.py:271
msgid "Push system users to asset: "
msgstr "推送系统用户到入资产: "
@@ -1444,209 +1449,209 @@ msgstr "运行用户名称"
msgid "User display"
msgstr "用户名称"
#: audits/signals_handler.py:65
#: audits/signals_handler.py:67
msgid "SSH Key"
msgstr "SSH 密钥"
#: audits/signals_handler.py:67
#: audits/signals_handler.py:69
msgid "SSO"
msgstr ""
#: audits/signals_handler.py:68
#: audits/signals_handler.py:70
msgid "Auth Token"
msgstr "认证令牌"
#: audits/signals_handler.py:69 authentication/notifications.py:73
#: audits/signals_handler.py:71 authentication/notifications.py:73
#: authentication/views/login.py:164 authentication/views/wecom.py:158
#: notifications/backends/__init__.py:11 users/models/user.py:607
msgid "WeCom"
msgstr "企业微信"
#: audits/signals_handler.py:70 authentication/views/dingtalk.py:160
#: audits/signals_handler.py:72 authentication/views/dingtalk.py:160
#: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:608
msgid "DingTalk"
msgstr "钉钉"
#: audits/signals_handler.py:104
#: audits/signals_handler.py:106
msgid "User and Organization"
msgstr "用户与组织"
#: audits/signals_handler.py:105
#: audits/signals_handler.py:107
#, python-brace-format
msgid "{User} JOINED {Organization}"
msgstr "{User} 加入 {Organization}"
#: audits/signals_handler.py:106
#: audits/signals_handler.py:108
#, python-brace-format
msgid "{User} LEFT {Organization}"
msgstr "{User} 离开 {Organization}"
#: audits/signals_handler.py:109
#: audits/signals_handler.py:111
msgid "User and Group"
msgstr "用户与用户组"
#: audits/signals_handler.py:110
#: audits/signals_handler.py:112
#, python-brace-format
msgid "{User} JOINED {UserGroup}"
msgstr "{User} 加入 {UserGroup}"
#: audits/signals_handler.py:111
#: audits/signals_handler.py:113
#, python-brace-format
msgid "{User} LEFT {UserGroup}"
msgstr "{User} 离开 {UserGroup}"
#: audits/signals_handler.py:114
#: audits/signals_handler.py:116
msgid "Asset and SystemUser"
msgstr "资产与系统用户"
#: audits/signals_handler.py:115
#: audits/signals_handler.py:117
#, python-brace-format
msgid "{Asset} ADD {SystemUser}"
msgstr "{Asset} 添加 {SystemUser}"
#: audits/signals_handler.py:116
#: audits/signals_handler.py:118
#, python-brace-format
msgid "{Asset} REMOVE {SystemUser}"
msgstr "{Asset} 移除 {SystemUser}"
#: audits/signals_handler.py:119
#: audits/signals_handler.py:121
msgid "Node and Asset"
msgstr "节点与资产"
#: audits/signals_handler.py:120
#: audits/signals_handler.py:122
#, python-brace-format
msgid "{Node} ADD {Asset}"
msgstr "{Node} 添加 {Asset}"
#: audits/signals_handler.py:121
#: audits/signals_handler.py:123
#, python-brace-format
msgid "{Node} REMOVE {Asset}"
msgstr "{Node} 移除 {Asset}"
#: audits/signals_handler.py:124
#: audits/signals_handler.py:126
msgid "User asset permissions"
msgstr "用户资产授权"
#: audits/signals_handler.py:125
#: audits/signals_handler.py:127
#, python-brace-format
msgid "{AssetPermission} ADD {User}"
msgstr "{AssetPermission} 添加 {User}"
#: audits/signals_handler.py:126
#: audits/signals_handler.py:128
#, python-brace-format
msgid "{AssetPermission} REMOVE {User}"
msgstr "{AssetPermission} 移除 {User}"
#: audits/signals_handler.py:129
#: audits/signals_handler.py:131
msgid "User group asset permissions"
msgstr "用户组资产授权"
#: audits/signals_handler.py:130
#: audits/signals_handler.py:132
#, python-brace-format
msgid "{AssetPermission} ADD {UserGroup}"
msgstr "{AssetPermission} 添加 {UserGroup}"
#: audits/signals_handler.py:131
#: audits/signals_handler.py:133
#, python-brace-format
msgid "{AssetPermission} REMOVE {UserGroup}"
msgstr "{AssetPermission} 移除 {UserGroup}"
#: audits/signals_handler.py:134 perms/models/asset_permission.py:30
#: audits/signals_handler.py:136 perms/models/asset_permission.py:30
#: templates/_nav.html:78 users/templates/users/_user_detail_nav_header.html:31
msgid "Asset permission"
msgstr "资产授权"
#: audits/signals_handler.py:135
#: audits/signals_handler.py:137
#, python-brace-format
msgid "{AssetPermission} ADD {Asset}"
msgstr "{AssetPermission} 添加 {Asset}"
#: audits/signals_handler.py:136
#: audits/signals_handler.py:138
#, python-brace-format
msgid "{AssetPermission} REMOVE {Asset}"
msgstr "{AssetPermission} 移除 {Asset}"
#: audits/signals_handler.py:139
#: audits/signals_handler.py:141
msgid "Node permission"
msgstr "节点授权"
#: audits/signals_handler.py:140
#: audits/signals_handler.py:142
#, python-brace-format
msgid "{AssetPermission} ADD {Node}"
msgstr "{AssetPermission} 添加 {Node}"
#: audits/signals_handler.py:141
#: audits/signals_handler.py:143
#, python-brace-format
msgid "{AssetPermission} REMOVE {Node}"
msgstr "{AssetPermission} 移除 {Node}"
#: audits/signals_handler.py:144
#: audits/signals_handler.py:146
msgid "Asset permission and SystemUser"
msgstr "资产授权与系统用户"
#: audits/signals_handler.py:145
#: audits/signals_handler.py:147
#, python-brace-format
msgid "{AssetPermission} ADD {SystemUser}"
msgstr "{AssetPermission} 添加 {SystemUser}"
#: audits/signals_handler.py:146
#: audits/signals_handler.py:148
#, python-brace-format
msgid "{AssetPermission} REMOVE {SystemUser}"
msgstr "{AssetPermission} 移除 {SystemUser}"
#: audits/signals_handler.py:149
#: audits/signals_handler.py:151
msgid "User application permissions"
msgstr "用户应用授权"
#: audits/signals_handler.py:150
#: audits/signals_handler.py:152
#, python-brace-format
msgid "{ApplicationPermission} ADD {User}"
msgstr "{ApplicationPermission} 添加 {User}"
#: audits/signals_handler.py:151
#: audits/signals_handler.py:153
#, python-brace-format
msgid "{ApplicationPermission} REMOVE {User}"
msgstr "{ApplicationPermission} 移除 {User}"
#: audits/signals_handler.py:154
#: audits/signals_handler.py:156
msgid "User group application permissions"
msgstr "用户组应用授权"
#: audits/signals_handler.py:155
#: audits/signals_handler.py:157
#, python-brace-format
msgid "{ApplicationPermission} ADD {UserGroup}"
msgstr "{ApplicationPermission} 添加 {UserGroup}"
#: audits/signals_handler.py:156
#: audits/signals_handler.py:158
#, python-brace-format
msgid "{ApplicationPermission} REMOVE {UserGroup}"
msgstr "{ApplicationPermission} 移除 {UserGroup}"
#: audits/signals_handler.py:159 perms/models/application_permission.py:37
#: audits/signals_handler.py:161 perms/models/application_permission.py:37
msgid "Application permission"
msgstr "应用授权"
#: audits/signals_handler.py:160
#: audits/signals_handler.py:162
#, python-brace-format
msgid "{ApplicationPermission} ADD {Application}"
msgstr "{ApplicationPermission} 添加 {Application}"
#: audits/signals_handler.py:161
#: audits/signals_handler.py:163
#, python-brace-format
msgid "{ApplicationPermission} REMOVE {Application}"
msgstr "{ApplicationPermission} 移除 {Application}"
#: audits/signals_handler.py:164
#: audits/signals_handler.py:166
msgid "Application permission and SystemUser"
msgstr "应用授权与系统用户"
#: audits/signals_handler.py:165
#: audits/signals_handler.py:167
#, python-brace-format
msgid "{ApplicationPermission} ADD {SystemUser}"
msgstr "{ApplicationPermission} 添加 {SystemUser}"
#: audits/signals_handler.py:166
#: audits/signals_handler.py:168
#, python-brace-format
msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 移除 {SystemUser}"
@@ -1950,7 +1955,7 @@ msgstr "异地登录提醒"
#: authentication/notifications.py:52
msgid "binding reminder"
msgstr "定提醒"
msgstr "定提醒"
#: authentication/templates/authentication/_access_key_modal.html:6
msgid "API key list"
@@ -2553,15 +2558,16 @@ msgid "Operations"
msgstr "运维"
#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162
#: settings/serializers/auth/ldap.py:66
msgid "Cycle perform"
msgstr "周期执行"
#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150
#: settings/serializers/auth/ldap.py:64
#: settings/serializers/auth/ldap.py:63
msgid "Regularly perform"
msgstr "定期执行"
#: ops/mixin.py:112 settings/serializers/auth/ldap.py:61
#: ops/mixin.py:112
msgid "Interval"
msgstr "间隔"
@@ -2588,7 +2594,7 @@ msgstr ""
"分 时 日 月 星期> <a href='https://tool.lu/crontab/' target='_blank'>在线工"
"具</a> <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
#: ops/mixin.py:162 settings/serializers/auth/ldap.py:61
#: ops/mixin.py:162
msgid "Unit: hour"
msgstr "单位: 时"
@@ -2869,7 +2875,7 @@ msgstr "用户名称"
#: perms/serializers/asset/permission.py:22
msgid "User groups display"
msgstr "用户名称"
msgstr "用户名称"
#: perms/serializers/asset/permission.py:23
msgid "Assets display"
@@ -3058,21 +3064,15 @@ msgstr ""
"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上username, name,"
"email 是jumpserver的用户需要属性"
#: settings/serializers/auth/ldap.py:58
#: xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
#: settings/serializers/auth/ldap.py:68
#: settings/serializers/auth/ldap.py:70
msgid "Connect timeout"
msgstr "连接超时时间"
#: settings/serializers/auth/ldap.py:70
#: settings/serializers/auth/ldap.py:72
msgid "Search paged size"
msgstr "搜索分页数量"
#: settings/serializers/auth/ldap.py:72
#: settings/serializers/auth/ldap.py:74
msgid "Enable LDAP auth"
msgstr "启用 LDAP 认证"
@@ -3521,34 +3521,42 @@ msgid "Global MFA auth"
msgstr "全局启用 MFA 认证"
#: settings/serializers/security.py:47
msgid "Third-party login users perform MFA authentication"
msgstr "第三方登录用户进行MFA认证"
#: settings/serializers/security.py:48
msgid "The third-party login modes include OIDC, CAS, and SAML2"
msgstr "第三方登录方式包括: OIDC、CAS、SAML2"
#: settings/serializers/security.py:52
msgid "Limit the number of user login failures"
msgstr "限制用户登录失败次数"
#: settings/serializers/security.py:51
#: settings/serializers/security.py:56
msgid "Block user login interval"
msgstr "禁止用户登录时间间隔"
#: settings/serializers/security.py:56
#: settings/serializers/security.py:61
msgid "Limit the number of IP login failures"
msgstr "限制 IP 登录失败次数"
#: settings/serializers/security.py:60
#: settings/serializers/security.py:65
msgid "Block IP login interval"
msgstr "禁止 IP 登录时间间隔"
#: settings/serializers/security.py:64
#: settings/serializers/security.py:69
msgid "Login IP White List"
msgstr "IP 登录白名单"
#: settings/serializers/security.py:69
#: settings/serializers/security.py:74
msgid "Login IP Black List"
msgstr "IP 登录黑名单"
#: settings/serializers/security.py:75
#: settings/serializers/security.py:80
msgid "User password expiration"
msgstr "用户密码过期时间"
#: settings/serializers/security.py:77
#: settings/serializers/security.py:82
msgid ""
"Unit: day, If the user does not update the password during the time, the "
"user password will expire failure;The password expiration reminder mail will "
@@ -3558,55 +3566,55 @@ msgstr ""
"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件"
"将在密码过期前5天内由系统每天自动发送给用户"
#: settings/serializers/security.py:84
#: settings/serializers/security.py:89
msgid "Number of repeated historical passwords"
msgstr "不能设置近几次密码"
#: settings/serializers/security.py:86
#: settings/serializers/security.py:91
msgid ""
"Tip: When the user resets the password, it cannot be the previous n "
"historical passwords of the user"
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
#: settings/serializers/security.py:91
#: settings/serializers/security.py:96
msgid "Only single device login"
msgstr "仅一台设备登录"
#: settings/serializers/security.py:92
#: settings/serializers/security.py:97
msgid "Next device login, pre login will be logout"
msgstr "下个设备登录,上次登录会被顶掉"
#: settings/serializers/security.py:95
#: settings/serializers/security.py:100
msgid "Only exist user login"
msgstr "仅已存在用户登录"
#: settings/serializers/security.py:96
#: settings/serializers/security.py:101
msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:99
#: settings/serializers/security.py:104
msgid "Only from source login"
msgstr "仅从用户来源登录"
#: settings/serializers/security.py:100
#: settings/serializers/security.py:105
msgid "Only log in from the user source property"
msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:104
#: settings/serializers/security.py:109
msgid "MFA verify TTL"
msgstr "MFA 校验有效期"
#: settings/serializers/security.py:106
#: settings/serializers/security.py:111
msgid ""
"Unit: second, The verification MFA takes effect only when you view the "
"account password"
msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效"
#: settings/serializers/security.py:111
#: settings/serializers/security.py:116
msgid "Enable Login dynamic code"
msgstr "启用登录附加码"
#: settings/serializers/security.py:112
#: settings/serializers/security.py:117
msgid ""
"The password and additional code are sent to a third party authentication "
"system for verification"
@@ -3614,89 +3622,89 @@ msgstr ""
"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密"
"码+6位数字 完成认证"
#: settings/serializers/security.py:117
#: settings/serializers/security.py:122
msgid "MFA in login page"
msgstr "MFA 在登录页面输入"
#: settings/serializers/security.py:118
#: settings/serializers/security.py:123
msgid "Eu security regulations(GDPR) require MFA to be on the login page"
msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全"
#: settings/serializers/security.py:121
#: settings/serializers/security.py:126
msgid "Enable Login captcha"
msgstr "启用登录验证码"
#: settings/serializers/security.py:122
#: settings/serializers/security.py:127
msgid "Enable captcha to prevent robot authentication"
msgstr "开启验证码,防止机器人登录"
#: settings/serializers/security.py:142
#: settings/serializers/security.py:147
msgid "Enable terminal register"
msgstr "终端注册"
#: settings/serializers/security.py:144
#: settings/serializers/security.py:149
msgid ""
"Allow terminal register, after all terminal setup, you should disable this "
"for security"
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
#: settings/serializers/security.py:148
#: settings/serializers/security.py:153
msgid "Enable watermark"
msgstr "开启水印"
#: settings/serializers/security.py:149
#: settings/serializers/security.py:154
msgid "Enabled, the web session and replay contains watermark information"
msgstr "启用后Web 会话和录像将包含水印信息"
#: settings/serializers/security.py:153
#: settings/serializers/security.py:158
msgid "Connection max idle time"
msgstr "连接最大空闲时间"
#: settings/serializers/security.py:154
#: settings/serializers/security.py:159
msgid "If idle time more than it, disconnect connection Unit: minute"
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
#: settings/serializers/security.py:157
#: settings/serializers/security.py:162
msgid "Remember manual auth"
msgstr "保存手动输入密码"
#: settings/serializers/security.py:160
#: settings/serializers/security.py:165
msgid "Enable change auth secure mode"
msgstr "启用改密安全模式"
#: settings/serializers/security.py:163
#: settings/serializers/security.py:168
msgid "Insecure command alert"
msgstr "危险命令告警"
#: settings/serializers/security.py:166
#: settings/serializers/security.py:171
msgid "Email recipient"
msgstr "邮件收件人"
#: settings/serializers/security.py:167
#: settings/serializers/security.py:172
msgid "Multiple user using , split"
msgstr "多个用户,使用 , 分割"
#: settings/serializers/security.py:170
#: settings/serializers/security.py:175
msgid "Batch command execution"
msgstr "批量命令执行"
#: settings/serializers/security.py:171
#: settings/serializers/security.py:176
msgid "Allow user run batch command or not using ansible"
msgstr "是否允许用户使用 ansible 执行批量命令"
#: settings/serializers/security.py:174
#: settings/serializers/security.py:179
msgid "Session share"
msgstr "会话分享"
#: settings/serializers/security.py:175
#: settings/serializers/security.py:180
msgid "Enabled, Allows user active session to be shared with other users"
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
#: settings/serializers/security.py:178
#: settings/serializers/security.py:183
msgid "Remote Login Protection"
msgstr "异地登录保护"
#: settings/serializers/security.py:180
#: settings/serializers/security.py:185
msgid ""
"The system determines whether the login IP address belongs to a common login "
"city. If the account is logged in from a common login city, the system sends "
@@ -4354,19 +4362,19 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远
msgid "Filters"
msgstr "过滤"
#: terminal/api/session.py:190
#: terminal/api/session.py:192
msgid "Session does not exist: {}"
msgstr "会话不存在: {}"
#: terminal/api/session.py:193
#: terminal/api/session.py:195
msgid "Session is finished or the protocol not supported"
msgstr "会话已经完成或协议不支持"
#: terminal/api/session.py:198
#: terminal/api/session.py:200
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
#: terminal/api/session.py:205
#: terminal/api/session.py:207
msgid "User does not have permission"
msgstr "用户没有权限"
@@ -4923,24 +4931,24 @@ msgstr "用户显示名称"
msgid "Body"
msgstr "内容"
#: tickets/models/flow.py:18 tickets/models/flow.py:60
#: tickets/models/flow.py:19 tickets/models/flow.py:61
#: tickets/models/ticket.py:29
msgid "Approve level"
msgstr "审批级别"
#: tickets/models/flow.py:23 tickets/serializers/ticket/ticket.py:141
#: tickets/models/flow.py:24 tickets/serializers/ticket/ticket.py:141
msgid "Approve strategy"
msgstr "审批策略"
#: tickets/models/flow.py:28 tickets/serializers/ticket/ticket.py:142
#: tickets/models/flow.py:29 tickets/serializers/ticket/ticket.py:142
msgid "Assignees"
msgstr "受理人"
#: tickets/models/flow.py:32
#: tickets/models/flow.py:33
msgid "Ticket flow approval rule"
msgstr "工单批准信息"
#: tickets/models/flow.py:65
#: tickets/models/flow.py:66
msgid "Ticket flow"
msgstr "工单流程"
@@ -6275,6 +6283,11 @@ msgstr "实例个数"
msgid "Linux admin user"
msgstr "Linux 管理员"
#: xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
#: xpack/plugins/cloud/utils.py:68
msgid "Account unavailable"
msgstr "账户无效"

View File

@@ -12,14 +12,13 @@ logger = get_logger(__name__)
class SiteMsgWebsocket(JsonWebsocketConsumer):
refresh_every_seconds = 10
sub = None
def connect(self):
user = self.scope["user"]
if user.is_authenticated:
self.accept()
thread = threading.Thread(target=self.watch_recv_new_site_msg)
thread.start()
self.sub = self.watch_recv_new_site_msg()
else:
self.close()
@@ -56,4 +55,9 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
if user_id in users:
ws.send_unread_msg_count()
new_site_msg_chan.keep_handle_msg(handle_new_site_msg_recv)
return new_site_msg_chan.subscribe(handle_new_site_msg_recv)
def disconnect(self, code):
if self.sub:
self.sub.unsubscribe()

View File

@@ -1,4 +1,5 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from django.template.loader import render_to_string
from notifications.notifications import SystemMessage
@@ -19,7 +20,7 @@ class ServerPerformanceMessage(SystemMessage):
self.terms_with_errors = terms_with_errors
def get_html_msg(self) -> dict:
subject = _("Terminal health check warning")
subject = gettext("Terminal health check warning")
context = {
'terms_with_errors': self.terms_with_errors
}

View File

@@ -46,7 +46,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
logger.debug("Start subscribe for expire orgs mapping from memory")
def keep_subscribe_org_mapping():
orgs_mapping_for_memory_pub_sub.keep_handle_msg(
orgs_mapping_for_memory_pub_sub.subscribe(
lambda org_id: Organization.expire_orgs_mapping()
)

View File

@@ -1,4 +1,5 @@
import time
from functools import reduce
from django.db.models import Q
@@ -79,3 +80,14 @@ def get_application_system_user_ids(user, application):
def has_application_system_permission(user, application, system_user):
system_user_ids = get_application_system_user_ids(user, application)
return system_user.id in system_user_ids
def get_application_actions(user, application, system_user):
perm_ids = get_user_all_app_perm_ids(user)
actions = ApplicationPermission.objects.filter(
applications=application, system_users=system_user,
id__in=list(perm_ids)
).values_list('actions', flat=True)
actions = reduce(lambda x, y: x | y, actions, 0)
return actions

View File

@@ -109,3 +109,9 @@ def get_asset_system_user_ids_with_actions_by_group(group: UserGroup, asset: Ass
user_groups=group
).valid().values_list('id', flat=True).distinct()
return get_asset_system_user_ids_with_actions(asset_perm_ids, asset)
def get_asset_actions(user, asset, system_user):
systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset)
actions = systemuser_actions_mapper.get(system_user.id, 0)
return actions

View File

@@ -55,13 +55,15 @@ class LDAPSettingSerializer(serializers.Serializer):
help_text=_('User attr map present how to map LDAP user attr to '
'jumpserver, username,name,email is jumpserver attr')
)
AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField(required=False, label=_('Periodic display'))
AUTH_LDAP_SYNC_INTERVAL = serializers.CharField(
required=False, max_length=1024, allow_null=True,
label=_('Interval'), help_text=_('Unit: hour')
AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField(
required=False, label=_('Periodic perform')
)
AUTH_LDAP_SYNC_CRONTAB = serializers.CharField(
required=False, max_length=1024, allow_null=True, label=_('Regularly perform')
required=False, max_length=128, allow_null=True, allow_blank=True,
label=_('Regularly perform')
)
AUTH_LDAP_SYNC_INTERVAL = serializers.IntegerField(
required=False, default=24, allow_null=True, label=_('Cycle perform')
)
AUTH_LDAP_CONNECT_TIMEOUT = serializers.IntegerField(
min_value=1, max_value=300,

View File

@@ -42,6 +42,11 @@ class SecurityAuthSerializer(serializers.Serializer):
),
required=False, label=_("Global MFA auth")
)
SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField(
required=False, default=True,
label=_('Third-party login users perform MFA authentication'),
help_text=_('The third-party login modes include OIDC, CAS, and SAML2'),
)
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999,
label=_('Limit the number of user login failures')

View File

@@ -80,9 +80,7 @@ def subscribe_settings_change(sender, **kwargs):
logger.debug("Start subscribe setting change")
def keep_subscribe_settings_change():
setting_pub_sub.keep_handle_msg(
lambda name: Setting.refresh_item(name)
)
setting_pub_sub.subscribe(lambda name: Setting.refresh_item(name))
t = threading.Thread(target=keep_subscribe_settings_change)
t.daemon = True

View File

@@ -12,7 +12,7 @@ from rest_framework import viewsets, views
from rest_framework.response import Response
from rest_framework.decorators import action
from common.utils import model_to_json
from common.utils import data_to_json
from .. import utils
from common.const.http import GET
from common.utils import get_logger, get_object_or_none
@@ -62,7 +62,9 @@ class SessionViewSet(OrgBulkModelViewSet):
os.chdir(dir_path)
with open(meta_filename, 'wt') as f:
f.write(model_to_json(session))
serializer = serializers.SessionDisplaySerializer(session)
data = data_to_json(serializer.data)
f.write(data)
with tarfile.open(offline_filename, 'w') as f:
f.add(replay_filename)

View File

@@ -56,7 +56,6 @@ class Session(OrgModelMixin):
upload_to = 'replay'
ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}'
_DATE_START_FIRST_HAS_REPLAY_RDP_SESSION = None
SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz'}
DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz']
@@ -125,25 +124,8 @@ class Session(OrgModelMixin):
def user_obj(self):
return User.objects.get(id=self.user_id)
@property
def _date_start_first_has_replay_rdp_session(self):
if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None:
instance = self.__class__.objects.filter(
protocol='rdp', has_replay=True
).order_by('date_start').first()
if not instance:
date_start = timezone.now() - timezone.timedelta(days=365)
else:
date_start = instance.date_start
self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION = date_start
return self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION
def can_replay(self):
if self.has_replay:
return True
if self.date_start < self._date_start_first_has_replay_rdp_session:
return True
return False
return self.has_replay
@property
def can_join(self):

View File

@@ -1,7 +1,6 @@
from typing import Callable
import textwrap
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.template.loader import render_to_string
@@ -11,7 +10,6 @@ from notifications.notifications import SystemMessage
from terminal.models import Session, Command
from notifications.models import SystemMsgSubscription
from notifications.backends import BACKEND
from orgs.utils import tmp_to_root_org
from common.utils import lazyproperty
from common.utils.timezone import local_now_display

View File

@@ -18,7 +18,6 @@ from .models import Status, Session, Command
from .backends import server_replay_storage
from .utils import find_session_replay_local
CACHE_REFRESH_INTERVAL = 10
RUNNING = False
logger = get_task_logger(__name__)
@@ -64,7 +63,7 @@ def clean_expired_session_period():
logger.info("Clean session item done")
expired_commands.delete()
logger.info("Clean session command done")
command = "find %s -mtime +%s -name '*.gz' -exec rm -f {} \\;" % (
command = "find %s -mtime +%s \\( -name '*.json' -o -name '*.tar' -o -name '*.gz' \\) -exec rm -f {} \\;" % (
replay_dir, days
)
subprocess.call(command, shell=True)

View File

@@ -33,7 +33,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
'title', 'applicant_display', 'status', 'state', 'action_display',
'date_created', 'serial_num',
)
ordering = ('-date_created', )
ordering = ('-date_created',)
def create(self, request, *args, **kwargs):
raise MethodNotAllowed(self.action)
@@ -81,7 +81,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
class TicketFlowViewSet(JMSBulkModelViewSet):
permission_classes = (IsOrgAdmin, IsSuperUser)
permission_classes = (IsSuperUser,)
serializer_class = serializers.TicketFlowSerializer
filterset_fields = ['id', 'type']

View File

@@ -6,7 +6,8 @@ from django.utils.translation import ugettext_lazy as _
from users.models import User
from common.mixins.models import CommonModelMixin
from orgs.mixins.models import OrgModelMixin
from orgs.utils import tmp_to_root_org, tmp_to_org, get_current_org_id
from orgs.models import Organization
from orgs.utils import tmp_to_org, get_current_org_id
from ..const import TicketType, TicketApprovalLevel, TicketApprovalStrategy
__all__ = ['TicketFlow', 'ApprovalRule']
@@ -75,6 +76,7 @@ class TicketFlow(CommonModelMixin, OrgModelMixin):
else:
flows = cls.objects.all()
cur_flow_types = flows.values_list('type', flat=True)
with tmp_to_root_org():
diff_global_flows = cls.objects.exclude(type__in=cur_flow_types)
root_id = Organization.ROOT_ID
with tmp_to_org(root_id):
diff_global_flows = cls.objects.exclude(type__in=cur_flow_types).filter(org_id=root_id)
return flows | diff_global_flows