feat: 账号收集添加资产账号信息变化通知 (#12009)

Co-authored-by: feng <1304903146@qq.com>
pull/12032/head^2
fit2bot 2023-11-07 13:00:09 +08:00 committed by GitHub
parent c0de0b0d8e
commit 54b89f6fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 124 additions and 5 deletions

View File

@ -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()

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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, {})

View File

@ -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

View File

@ -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>

View File

@ -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]