mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
12 Commits
pr@dev@per
...
v2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b854587a | ||
|
|
5c3f92cd09 | ||
|
|
2529ef2211 | ||
|
|
8c51ff22fe | ||
|
|
cc819f199e | ||
|
|
32ed385498 | ||
|
|
b51eb4267d | ||
|
|
fdcecb022d | ||
|
|
9060b49dcb | ||
|
|
363f0326fd | ||
|
|
502ded6de1 | ||
|
|
1614a81936 |
@@ -21,7 +21,7 @@ RUN useradd jumpserver
|
||||
RUN yum -y install epel-release && \
|
||||
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
|
||||
RUN yum -y install $(cat requirements/rpm_requirements.txt)
|
||||
RUN pip install --upgrade pip setuptools wheel -i ${PIP_MIRROR} && \
|
||||
RUN pip install --upgrade pip setuptools==49.6.0 wheel -i ${PIP_MIRROR} && \
|
||||
pip config set global.index-url ${PIP_MIRROR}
|
||||
RUN pip install -r requirements/requirements.txt || pip install -r requirements/requirements.txt
|
||||
|
||||
|
||||
@@ -158,9 +158,11 @@ class AuthMixin:
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def has_special_auth(self, asset=None):
|
||||
def has_special_auth(self, asset=None, username=None):
|
||||
from .authbook import AuthBook
|
||||
queryset = AuthBook.objects.filter(username=self.username)
|
||||
if username is None:
|
||||
username = self.username
|
||||
queryset = AuthBook.objects.filter(username=username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
return queryset.exists()
|
||||
|
||||
@@ -160,6 +160,11 @@ class SystemUser(BaseUser):
|
||||
def is_need_test_asset_connective(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
|
||||
def has_special_auth(self, asset=None, username=None):
|
||||
if username is None and self.username_same_with_user:
|
||||
raise TypeError('System user is dynamic, username should be pass')
|
||||
return super().has_special_auth(asset=asset, username=username)
|
||||
|
||||
@property
|
||||
def can_perm_to_asset(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
|
||||
@@ -149,6 +149,7 @@ class SystemUserListSerializer(SystemUserSerializer):
|
||||
class Meta(SystemUserSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'name', 'username', 'protocol',
|
||||
'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display',
|
||||
'priority', "username_same_with_user",
|
||||
'auto_push', 'sudo', 'shell', 'comment',
|
||||
@@ -157,6 +158,12 @@ class SystemUserListSerializer(SystemUserSerializer):
|
||||
'sftp_root',
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
|
||||
@@ -132,6 +132,7 @@ def get_push_windows_system_user_tasks(system_user, username=None):
|
||||
|
||||
tasks = []
|
||||
if not password:
|
||||
logger.error("Error: no password found")
|
||||
return tasks
|
||||
task = {
|
||||
'name': 'Add user {}'.format(username),
|
||||
@@ -207,14 +208,15 @@ 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(_hosts)))
|
||||
|
||||
if not system_user.has_special_auth():
|
||||
# 如果没有特殊密码设置,就不需要单独推送某台机器了
|
||||
if not system_user.has_special_auth(username=username):
|
||||
logger.debug("System user not has special auth")
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=username)
|
||||
run_task(tasks, _hosts)
|
||||
continue
|
||||
|
||||
for _host in _hosts:
|
||||
system_user.load_asset_special_auth(_host)
|
||||
system_user.load_asset_special_auth(_host, username=username)
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=username)
|
||||
run_task(tasks, [_host])
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ class TreeService(Tree):
|
||||
|
||||
def assets(self, nid):
|
||||
node = self.get_node(nid)
|
||||
if not node:
|
||||
return set()
|
||||
return node.data.get("assets", set())
|
||||
|
||||
def valid_assets(self, nid):
|
||||
@@ -132,6 +134,8 @@ class TreeService(Tree):
|
||||
|
||||
def all_assets(self, nid):
|
||||
node = self.get_node(nid)
|
||||
if not node:
|
||||
return set()
|
||||
if node.data is None:
|
||||
node.data = {}
|
||||
all_assets = node.data.get("all_assets")
|
||||
|
||||
@@ -24,7 +24,7 @@ def send_mail_async(*args, **kwargs):
|
||||
"""
|
||||
if len(args) == 3:
|
||||
args = list(args)
|
||||
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
||||
args[0] = (settings.EMAIL_SUBJECT_PREFIX or '') + args[0]
|
||||
email_from = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
|
||||
args.insert(2, email_from)
|
||||
args = tuple(args)
|
||||
|
||||
@@ -65,6 +65,8 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
|
||||
"""
|
||||
Task result Callback
|
||||
"""
|
||||
context = None
|
||||
|
||||
def clean_result(self, t, host, task_name, task_result):
|
||||
contacted = self.results_summary["contacted"]
|
||||
dark = self.results_summary["dark"]
|
||||
@@ -133,7 +135,11 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
|
||||
pass
|
||||
|
||||
def set_play_context(self, context):
|
||||
context.ssh_args = '-C -o ControlMaster=no'
|
||||
# for k, v in context._attributes.items():
|
||||
# print("{} ==> {}".format(k, v))
|
||||
if self.context and isinstance(self.context, dict):
|
||||
for k, v in self.context.items():
|
||||
setattr(context, k, v)
|
||||
|
||||
|
||||
class CommandResultCallback(AdHocResultCallback):
|
||||
|
||||
@@ -182,6 +182,13 @@ class AdHocRunner:
|
||||
_options.update(options)
|
||||
return _options
|
||||
|
||||
def set_control_master_if_need(self, cleaned_tasks):
|
||||
modules = [task.get('action', {}).get('module') for task in cleaned_tasks]
|
||||
if {'ping', 'win_ping'} & set(modules):
|
||||
self.results_callback.context = {
|
||||
'ssh_args': '-C -o ControlMaster=no'
|
||||
}
|
||||
|
||||
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
|
||||
"""
|
||||
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
|
||||
@@ -193,6 +200,7 @@ class AdHocRunner:
|
||||
self.check_pattern(pattern)
|
||||
self.results_callback = self.get_result_callback()
|
||||
cleaned_tasks = self.clean_tasks(tasks)
|
||||
self.set_control_master_if_need(cleaned_tasks)
|
||||
context.CLIARGS = ImmutableDict(self.options)
|
||||
|
||||
play_source = dict(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uuid
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
@@ -229,6 +230,16 @@ def _none2list(*args):
|
||||
return ([] if v is None else v for v in args)
|
||||
|
||||
|
||||
def _users2pks_if_need(users, admins, auditors):
|
||||
pks = []
|
||||
for user in chain(users, admins, auditors):
|
||||
if hasattr(user, 'pk'):
|
||||
pks.append(user.pk)
|
||||
else:
|
||||
pks.append(user)
|
||||
return pks
|
||||
|
||||
|
||||
class UserRoleMapper(dict):
|
||||
def __init__(self, container=set):
|
||||
super().__init__()
|
||||
@@ -266,7 +277,7 @@ class OrgMemeberManager(models.Manager):
|
||||
users, admins, auditors = _none2list(users, admins, auditors)
|
||||
|
||||
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
|
||||
model=User, pk_set=[*users, *admins, *auditors], using=self.db)
|
||||
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db)
|
||||
|
||||
send(action="pre_remove")
|
||||
self.filter(org_id=org.id).filter(
|
||||
@@ -290,14 +301,14 @@ class OrgMemeberManager(models.Manager):
|
||||
)
|
||||
|
||||
oms_add = []
|
||||
for users, role in add_mapper:
|
||||
for user in users:
|
||||
if isinstance(user, models.Model):
|
||||
user = user.id
|
||||
oms_add.append(self.model(org_id=org.id, user_id=user, role=role))
|
||||
for _users, _role in add_mapper:
|
||||
for _user in _users:
|
||||
if isinstance(_user, models.Model):
|
||||
_user = _user.id
|
||||
oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role))
|
||||
|
||||
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
|
||||
model=User, pk_set=[*users, *admins, *auditors], using=self.db)
|
||||
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db)
|
||||
|
||||
send(action='pre_add')
|
||||
self.bulk_create(oms_add)
|
||||
|
||||
@@ -52,9 +52,9 @@ class OrgReadSerializer(OrgSerializer):
|
||||
|
||||
|
||||
class OrgMemberSerializer(BulkModelSerializer):
|
||||
org_display = serializers.CharField()
|
||||
user_display = serializers.CharField()
|
||||
role_display = serializers.CharField(source='get_role_display')
|
||||
org_display = serializers.CharField(read_only=True)
|
||||
user_display = serializers.CharField(read_only=True)
|
||||
role_display = serializers.CharField(source='get_role_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = OrganizationMember
|
||||
|
||||
@@ -44,9 +44,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().annotate(
|
||||
gc_m2m_org_members__role=GroupConcat('m2m_org_members__role'),
|
||||
gc_groups__name=GroupConcat('groups__name'),
|
||||
gc_groups=GroupConcat('groups__id', output_field=CharField())
|
||||
)
|
||||
).prefetch_related('groups')
|
||||
|
||||
def send_created_signal(self, users):
|
||||
if not isinstance(users, list):
|
||||
@@ -54,6 +52,19 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||
for user in users:
|
||||
post_user_create.send(self.__class__, user=user)
|
||||
|
||||
@staticmethod
|
||||
def set_users_to_org(users, org_roles, update=False):
|
||||
# 只有真实存在的组织才真正关联用户
|
||||
if not current_org or not current_org.is_real():
|
||||
return
|
||||
for user, roles in zip(users, org_roles):
|
||||
if update and roles is None:
|
||||
continue
|
||||
if not roles:
|
||||
# 当前组织创建的用户,至少是该组织的`User`
|
||||
roles = [ORG_ROLE.USER]
|
||||
OrganizationMember.objects.set_user_roles(current_org, user, roles)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
validated_data = serializer.validated_data
|
||||
|
||||
@@ -106,11 +117,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||
users = serializer.save()
|
||||
if isinstance(users, User):
|
||||
users = [users]
|
||||
if current_org and current_org.is_real():
|
||||
for user, roles in zip(users, org_roles):
|
||||
if roles is not None:
|
||||
# roles 是 `Node` 表明不需要更新
|
||||
OrganizationMember.objects.set_user_roles(current_org, user, roles)
|
||||
self.set_users_to_org(users, org_roles, update=True)
|
||||
|
||||
def perform_bulk_update(self, serializer):
|
||||
# TODO: 需要测试
|
||||
|
||||
@@ -560,12 +560,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||
|
||||
@property
|
||||
def groups_display(self):
|
||||
if hasattr(self, 'gc_groups__name'):
|
||||
names = self.gc_groups__name
|
||||
if isinstance(names, str):
|
||||
return ' '.join(set(self.gc_groups__name.split(',')))
|
||||
else:
|
||||
return ''
|
||||
return ' '.join([group.name for group in self.groups.all()])
|
||||
|
||||
@property
|
||||
|
||||
@@ -44,9 +44,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
||||
label=_('Password strategy'), write_only=True
|
||||
)
|
||||
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display')
|
||||
groups = GroupConcatedPrimaryKeyRelatedField(
|
||||
label=_('User group'), many=True, queryset=UserGroup.objects.all(), required=False
|
||||
)
|
||||
login_blocked = serializers.SerializerMethodField()
|
||||
can_update = serializers.SerializerMethodField()
|
||||
can_delete = serializers.SerializerMethodField()
|
||||
|
||||
Reference in New Issue
Block a user