mirror of https://github.com/jumpserver/jumpserver
parent
c0de0b0d8e
commit
54b89f6fee
|
@ -1,9 +1,14 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import GatheredAccount
|
from accounts.models import GatheredAccount
|
||||||
|
from assets.models import Asset
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
|
from users.models import User
|
||||||
from .filter import GatherAccountsFilter
|
from .filter import GatherAccountsFilter
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
|
from ...notifications import GatherAccountChangeMsg
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -12,6 +17,7 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.host_asset_mapper = {}
|
self.host_asset_mapper = {}
|
||||||
|
self.asset_username_mapper = defaultdict(set)
|
||||||
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -26,10 +32,11 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
||||||
def filter_success_result(self, tp, result):
|
def filter_success_result(self, tp, result):
|
||||||
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
||||||
return result
|
return result
|
||||||
@staticmethod
|
|
||||||
def generate_data(asset, result):
|
def generate_data(self, asset, result):
|
||||||
data = []
|
data = []
|
||||||
for username, info in result.items():
|
for username, info in result.items():
|
||||||
|
self.asset_username_mapper[str(asset.id)].add(username)
|
||||||
d = {'asset': asset, 'username': username, 'present': True}
|
d = {'asset': asset, 'username': username, 'present': True}
|
||||||
if info.get('date'):
|
if info.get('date'):
|
||||||
d['date_last_login'] = info['date']
|
d['date_last_login'] = info['date']
|
||||||
|
@ -60,4 +67,48 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
||||||
result = self.filter_success_result(asset.type, info)
|
result = self.filter_success_result(asset.type, info)
|
||||||
self.update_or_create_accounts(asset, result)
|
self.update_or_create_accounts(asset, result)
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info".format(host))
|
logger.error(f'Not found {host} info')
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
super().run(*args, **kwargs)
|
||||||
|
self.send_email_if_need()
|
||||||
|
|
||||||
|
def send_email_if_need(self):
|
||||||
|
recipients = self.execution.recipients
|
||||||
|
if not self.asset_username_mapper or not recipients:
|
||||||
|
return
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=recipients)
|
||||||
|
if not users:
|
||||||
|
return
|
||||||
|
|
||||||
|
asset_ids = self.asset_username_mapper.keys()
|
||||||
|
assets = Asset.objects.filter(id__in=asset_ids)
|
||||||
|
asset_id_map = {str(asset.id): asset for asset in assets}
|
||||||
|
asset_qs = assets.values_list('id', 'accounts__username')
|
||||||
|
system_asset_username_mapper = defaultdict(set)
|
||||||
|
|
||||||
|
for asset_id, username in asset_qs:
|
||||||
|
system_asset_username_mapper[str(asset_id)].add(username)
|
||||||
|
|
||||||
|
change_info = {}
|
||||||
|
for asset_id, usernames in self.asset_username_mapper.items():
|
||||||
|
system_usernames = system_asset_username_mapper.get(asset_id)
|
||||||
|
|
||||||
|
if not system_usernames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
add_usernames = usernames - system_usernames
|
||||||
|
remove_usernames = system_usernames - usernames
|
||||||
|
k = f'{asset_id_map[asset_id]}[{asset_id}]'
|
||||||
|
|
||||||
|
change_info[k] = {
|
||||||
|
'add_usernames': ', '.join(add_usernames),
|
||||||
|
'remove_usernames': ', '.join(remove_usernames),
|
||||||
|
}
|
||||||
|
|
||||||
|
if not change_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
GatherAccountChangeMsg(user, change_info).publish_async()
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.1.10 on 2023-10-31 06:12
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('accounts', '0017_alter_automationexecution_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='gatheraccountsautomation',
|
||||||
|
name='recipients',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -55,11 +55,15 @@ class GatherAccountsAutomation(AccountBaseAutomation):
|
||||||
is_sync_account = models.BooleanField(
|
is_sync_account = models.BooleanField(
|
||||||
default=False, blank=True, verbose_name=_("Is sync account")
|
default=False, blank=True, verbose_name=_("Is sync account")
|
||||||
)
|
)
|
||||||
|
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
||||||
|
|
||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
attr_json = super().to_attr_json()
|
attr_json = super().to_attr_json()
|
||||||
attr_json.update({
|
attr_json.update({
|
||||||
'is_sync_account': self.is_sync_account,
|
'is_sync_account': self.is_sync_account,
|
||||||
|
'recipients': [
|
||||||
|
str(recipient.id) for recipient in self.recipients.all()
|
||||||
|
]
|
||||||
})
|
})
|
||||||
return attr_json
|
return attr_json
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.tasks import send_mail_attachment_async
|
from common.tasks import send_mail_attachment_async
|
||||||
|
from notifications.notifications import UserMessage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,3 +53,25 @@ class ChangeSecretExecutionTaskMsg(object):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
self.subject, self.message, [self.user.email], attachments
|
self.subject, self.message, [self.user.email], attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GatherAccountChangeMsg(UserMessage):
|
||||||
|
subject = _('Gather account change information')
|
||||||
|
|
||||||
|
def __init__(self, user, change_info: dict):
|
||||||
|
self.change_info = change_info
|
||||||
|
super().__init__(user)
|
||||||
|
|
||||||
|
def get_html_msg(self) -> dict:
|
||||||
|
context = {'change_info': self.change_info}
|
||||||
|
message = render_to_string('accounts/asset_account_change_info.html', context)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'subject': str(self.subject),
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def gen_test_msg(cls):
|
||||||
|
user = User.objects.first()
|
||||||
|
return cls(user, {})
|
||||||
|
|
|
@ -18,7 +18,7 @@ class GatherAccountAutomationSerializer(BaseAutomationSerializer):
|
||||||
model = GatherAccountsAutomation
|
model = GatherAccountsAutomation
|
||||||
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||||
fields = BaseAutomationSerializer.Meta.fields \
|
fields = BaseAutomationSerializer.Meta.fields \
|
||||||
+ ['is_sync_account'] + read_only_fields
|
+ ['is_sync_account', 'recipients'] + read_only_fields
|
||||||
|
|
||||||
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs
|
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
|
||||||
|
<caption></caption>
|
||||||
|
<tr style="background-color: #f2f2f2;">
|
||||||
|
<th style="border: 1px solid #ddd; padding: 10px; font-weight: bold;">{% trans 'Asset name' %}</th>
|
||||||
|
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Added account' %}</th>
|
||||||
|
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Deleted account' %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for name, change in change_info.items %}
|
||||||
|
<tr style="{% cycle 'background-color: #ebf5ff;' 'background-color: #fff;' %}">
|
||||||
|
<td style="border: 1px solid #ddd; padding: 10px;">{{ name }}</td>
|
||||||
|
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.add_usernames }}</td>
|
||||||
|
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.remove_usernames }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ class UserOtpEnableBindView(AuthMixin, TemplateView, FormView):
|
||||||
|
|
||||||
|
|
||||||
class UserOtpDisableView(PermissionsMixin, FormView):
|
class UserOtpDisableView(PermissionsMixin, FormView):
|
||||||
template_name = 'users/user_verify_mfa.html'
|
template_name = 'users/asset_account_change_info.html'
|
||||||
form_class = forms.UserCheckOtpCodeForm
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
permission_classes = [IsValidUser]
|
permission_classes = [IsValidUser]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue