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.models import GatheredAccount
|
||||
from assets.models import Asset
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import tmp_to_org
|
||||
from users.models import User
|
||||
from .filter import GatherAccountsFilter
|
||||
from ..base.manager import AccountBasePlaybookManager
|
||||
from ...notifications import GatherAccountChangeMsg
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
@ -12,6 +17,7 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_asset_mapper = {}
|
||||
self.asset_username_mapper = defaultdict(set)
|
||||
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
||||
|
||||
@classmethod
|
||||
|
@ -26,10 +32,11 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||
def filter_success_result(self, tp, result):
|
||||
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
||||
return result
|
||||
@staticmethod
|
||||
def generate_data(asset, result):
|
||||
|
||||
def generate_data(self, asset, result):
|
||||
data = []
|
||||
for username, info in result.items():
|
||||
self.asset_username_mapper[str(asset.id)].add(username)
|
||||
d = {'asset': asset, 'username': username, 'present': True}
|
||||
if info.get('date'):
|
||||
d['date_last_login'] = info['date']
|
||||
|
@ -60,4 +67,48 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||
result = self.filter_success_result(asset.type, info)
|
||||
self.update_or_create_accounts(asset, result)
|
||||
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(
|
||||
default=False, blank=True, verbose_name=_("Is sync account")
|
||||
)
|
||||
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
||||
|
||||
def to_attr_json(self):
|
||||
attr_json = super().to_attr_json()
|
||||
attr_json.update({
|
||||
'is_sync_account': self.is_sync_account,
|
||||
'recipients': [
|
||||
str(recipient.id) for recipient in self.recipients.all()
|
||||
]
|
||||
})
|
||||
return attr_json
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.tasks import send_mail_attachment_async
|
||||
from notifications.notifications import UserMessage
|
||||
from users.models import User
|
||||
|
||||
|
||||
|
@ -51,3 +53,25 @@ class ChangeSecretExecutionTaskMsg(object):
|
|||
send_mail_attachment_async(
|
||||
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
|
||||
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||
fields = BaseAutomationSerializer.Meta.fields \
|
||||
+ ['is_sync_account'] + read_only_fields
|
||||
+ ['is_sync_account', 'recipients'] + read_only_fields
|
||||
|
||||
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):
|
||||
template_name = 'users/user_verify_mfa.html'
|
||||
template_name = 'users/asset_account_change_info.html'
|
||||
form_class = forms.UserCheckOtpCodeForm
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
|
|
Loading…
Reference in New Issue