Compare commits

...

12 Commits
v3.0.0 ... v2.2

14 changed files with 77 additions and 34 deletions

View File

@@ -21,7 +21,7 @@ RUN useradd jumpserver
RUN yum -y install epel-release && \ RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo 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 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} pip config set global.index-url ${PIP_MIRROR}
RUN pip install -r requirements/requirements.txt || pip install -r requirements/requirements.txt RUN pip install -r requirements/requirements.txt || pip install -r requirements/requirements.txt

View File

@@ -158,9 +158,11 @@ class AuthMixin:
if update_fields: if update_fields:
self.save(update_fields=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 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: if asset:
queryset = queryset.filter(asset=asset) queryset = queryset.filter(asset=asset)
return queryset.exists() return queryset.exists()

View File

@@ -160,6 +160,11 @@ class SystemUser(BaseUser):
def is_need_test_asset_connective(self): def is_need_test_asset_connective(self):
return self.protocol not in [self.PROTOCOL_MYSQL] 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 @property
def can_perm_to_asset(self): def can_perm_to_asset(self):
return self.protocol not in [self.PROTOCOL_MYSQL] return self.protocol not in [self.PROTOCOL_MYSQL]

View File

@@ -149,6 +149,7 @@ class SystemUserListSerializer(SystemUserSerializer):
class Meta(SystemUserSerializer.Meta): class Meta(SystemUserSerializer.Meta):
fields = [ fields = [
'id', 'name', 'username', 'protocol', 'id', 'name', 'username', 'protocol',
'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'login_mode', 'login_mode_display',
'priority', "username_same_with_user", 'priority', "username_same_with_user",
'auto_push', 'sudo', 'shell', 'comment', 'auto_push', 'sudo', 'shell', 'comment',
@@ -157,6 +158,12 @@ class SystemUserListSerializer(SystemUserSerializer):
'sftp_root', 'sftp_root',
] ]
extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
}
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """

View File

@@ -132,6 +132,7 @@ def get_push_windows_system_user_tasks(system_user, username=None):
tasks = [] tasks = []
if not password: if not password:
logger.error("Error: no password found")
return tasks return tasks
task = { task = {
'name': 'Add user {}'.format(username), '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(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts))) 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") logger.debug("System user not has special auth")
tasks = get_push_system_user_tasks(system_user, platform, username=username) tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, _hosts) run_task(tasks, _hosts)
continue continue
for _host in _hosts: 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) tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, [_host]) run_task(tasks, [_host])

View File

@@ -125,6 +125,8 @@ class TreeService(Tree):
def assets(self, nid): def assets(self, nid):
node = self.get_node(nid) node = self.get_node(nid)
if not node:
return set()
return node.data.get("assets", set()) return node.data.get("assets", set())
def valid_assets(self, nid): def valid_assets(self, nid):
@@ -132,6 +134,8 @@ class TreeService(Tree):
def all_assets(self, nid): def all_assets(self, nid):
node = self.get_node(nid) node = self.get_node(nid)
if not node:
return set()
if node.data is None: if node.data is None:
node.data = {} node.data = {}
all_assets = node.data.get("all_assets") all_assets = node.data.get("all_assets")

View File

@@ -24,7 +24,7 @@ def send_mail_async(*args, **kwargs):
""" """
if len(args) == 3: if len(args) == 3:
args = list(args) 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 email_from = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
args.insert(2, email_from) args.insert(2, email_from)
args = tuple(args) args = tuple(args)

View File

@@ -65,6 +65,8 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
""" """
Task result Callback Task result Callback
""" """
context = None
def clean_result(self, t, host, task_name, task_result): def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"] contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"] dark = self.results_summary["dark"]
@@ -133,7 +135,11 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
pass pass
def set_play_context(self, context): 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): class CommandResultCallback(AdHocResultCallback):

View File

@@ -182,6 +182,13 @@ class AdHocRunner:
_options.update(options) _options.update(options)
return _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'): def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
""" """
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ] :param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
@@ -193,6 +200,7 @@ class AdHocRunner:
self.check_pattern(pattern) self.check_pattern(pattern)
self.results_callback = self.get_result_callback() self.results_callback = self.get_result_callback()
cleaned_tasks = self.clean_tasks(tasks) cleaned_tasks = self.clean_tasks(tasks)
self.set_control_master_if_need(cleaned_tasks)
context.CLIARGS = ImmutableDict(self.options) context.CLIARGS = ImmutableDict(self.options)
play_source = dict( play_source = dict(

View File

@@ -1,5 +1,6 @@
import uuid import uuid
from functools import partial from functools import partial
from itertools import chain
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
@@ -229,6 +230,16 @@ def _none2list(*args):
return ([] if v is None else v for v in 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): class UserRoleMapper(dict):
def __init__(self, container=set): def __init__(self, container=set):
super().__init__() super().__init__()
@@ -266,7 +277,7 @@ class OrgMemeberManager(models.Manager):
users, admins, auditors = _none2list(users, admins, auditors) users, admins, auditors = _none2list(users, admins, auditors)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, 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") send(action="pre_remove")
self.filter(org_id=org.id).filter( self.filter(org_id=org.id).filter(
@@ -290,14 +301,14 @@ class OrgMemeberManager(models.Manager):
) )
oms_add = [] oms_add = []
for users, role in add_mapper: for _users, _role in add_mapper:
for user in users: for _user in _users:
if isinstance(user, models.Model): if isinstance(_user, models.Model):
user = user.id _user = _user.id
oms_add.append(self.model(org_id=org.id, user_id=user, role=role)) 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, 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') send(action='pre_add')
self.bulk_create(oms_add) self.bulk_create(oms_add)

View File

@@ -52,9 +52,9 @@ class OrgReadSerializer(OrgSerializer):
class OrgMemberSerializer(BulkModelSerializer): class OrgMemberSerializer(BulkModelSerializer):
org_display = serializers.CharField() org_display = serializers.CharField(read_only=True)
user_display = serializers.CharField() user_display = serializers.CharField(read_only=True)
role_display = serializers.CharField(source='get_role_display') role_display = serializers.CharField(source='get_role_display', read_only=True)
class Meta: class Meta:
model = OrganizationMember model = OrganizationMember

View File

@@ -44,9 +44,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().annotate( return super().get_queryset().annotate(
gc_m2m_org_members__role=GroupConcat('m2m_org_members__role'), gc_m2m_org_members__role=GroupConcat('m2m_org_members__role'),
gc_groups__name=GroupConcat('groups__name'), ).prefetch_related('groups')
gc_groups=GroupConcat('groups__id', output_field=CharField())
)
def send_created_signal(self, users): def send_created_signal(self, users):
if not isinstance(users, list): if not isinstance(users, list):
@@ -54,6 +52,19 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
for user in users: for user in users:
post_user_create.send(self.__class__, user=user) 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): def perform_create(self, serializer):
validated_data = serializer.validated_data validated_data = serializer.validated_data
@@ -106,11 +117,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
users = serializer.save() users = serializer.save()
if isinstance(users, User): if isinstance(users, User):
users = [users] users = [users]
if current_org and current_org.is_real(): self.set_users_to_org(users, org_roles, update=True)
for user, roles in zip(users, org_roles):
if roles is not None:
# roles 是 `Node` 表明不需要更新
OrganizationMember.objects.set_user_roles(current_org, user, roles)
def perform_bulk_update(self, serializer): def perform_bulk_update(self, serializer):
# TODO: 需要测试 # TODO: 需要测试

View File

@@ -560,12 +560,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
@property @property
def groups_display(self): 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()]) return ' '.join([group.name for group in self.groups.all()])
@property @property

View File

@@ -44,9 +44,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
label=_('Password strategy'), write_only=True label=_('Password strategy'), write_only=True
) )
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display') 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() login_blocked = serializers.SerializerMethodField()
can_update = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField()
can_delete = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField()