diff --git a/apps/accounts/automations/gather_accounts/manager.py b/apps/accounts/automations/gather_accounts/manager.py index c4ba6b5a0..3bc085850 100644 --- a/apps/accounts/automations/gather_accounts/manager.py +++ b/apps/accounts/automations/gather_accounts/manager.py @@ -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() diff --git a/apps/accounts/migrations/0018_gatheraccountsautomation_recipients.py b/apps/accounts/migrations/0018_gatheraccountsautomation_recipients.py new file mode 100644 index 000000000..ffa201d4d --- /dev/null +++ b/apps/accounts/migrations/0018_gatheraccountsautomation_recipients.py @@ -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'), + ), + ] diff --git a/apps/accounts/models/automations/gather_account.py b/apps/accounts/models/automations/gather_account.py index 9ea5dcc3b..bc56fa4ec 100644 --- a/apps/accounts/models/automations/gather_account.py +++ b/apps/accounts/models/automations/gather_account.py @@ -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 diff --git a/apps/accounts/notifications.py b/apps/accounts/notifications.py index 190eb8fb6..eb716a27a 100644 --- a/apps/accounts/notifications.py +++ b/apps/accounts/notifications.py @@ -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, {}) diff --git a/apps/accounts/serializers/automations/gather_accounts.py b/apps/accounts/serializers/automations/gather_accounts.py index fd09773de..cbef21307 100644 --- a/apps/accounts/serializers/automations/gather_accounts.py +++ b/apps/accounts/serializers/automations/gather_accounts.py @@ -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 diff --git a/apps/accounts/templates/accounts/asset_account_change_info.html b/apps/accounts/templates/accounts/asset_account_change_info.html new file mode 100644 index 000000000..9b03acd42 --- /dev/null +++ b/apps/accounts/templates/accounts/asset_account_change_info.html @@ -0,0 +1,20 @@ +{% load i18n %} + + +
{% trans 'Asset name' %} | +{% trans 'Added account' %} | +{% trans 'Deleted account' %} | +
---|---|---|
{{ name }} | +{{ change.add_usernames }} | +{{ change.remove_usernames }} | +