From c2f12e180cedd65c5fcee7e3c3a42855993eca14 Mon Sep 17 00:00:00 2001 From: Carina Antunes <carina.oliveira.antunes@cern.ch> Date: Fri, 11 Feb 2022 11:29:14 +0000 Subject: [PATCH] audit: pass email to consumers --- .../data_source/data_source.py | 16 +++++- .../postgres/postgres_data_source.py | 20 ++++--- notifications_routing/preferences.py | 54 ++++++++++--------- notifications_routing/utils.py | 3 +- tests/unit/test_router.py | 2 +- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/notifications_routing/data_source/data_source.py b/notifications_routing/data_source/data_source.py index 8a22331..b061775 100644 --- a/notifications_routing/data_source/data_source.py +++ b/notifications_routing/data_source/data_source.py @@ -41,7 +41,7 @@ class DataSource(ABC): pass @abstractmethod - def get_user_preferences(self, user_id: str, channel_id: str, **kwargs) -> Dict[str, List[str]]: + def get_user_preferences(self, user_id: str, channel_id: str, **kwargs) -> Dict[str, List["Preference"]]: """Specific implementation of get user's preferences. :param user_id: User ID @@ -50,7 +50,7 @@ class DataSource(ABC): pass @abstractmethod - def get_user_devices(self, user_id: str, **kwargs) -> Dict[str, List[str]]: + def get_user_devices(self, user_id: str, **kwargs) -> Dict[str, List["Device"]]: """Specific implementation of get user's devices. :param user_id: User ID @@ -144,3 +144,15 @@ class DataSource(ABC): def get_target_groups(self, channel_id: str, specified_target_groups: List[str], **kwargs) -> List[Dict[str, str]]: """Return a list of dictionaries with group_ids of a channel.""" pass + + +class Preference: + """Preference Model.""" + + pass + + +class Device: + """Device Model.""" + + pass diff --git a/notifications_routing/data_source/postgres/postgres_data_source.py b/notifications_routing/data_source/postgres/postgres_data_source.py index ee74356..8b840ba 100644 --- a/notifications_routing/data_source/postgres/postgres_data_source.py +++ b/notifications_routing/data_source/postgres/postgres_data_source.py @@ -30,6 +30,8 @@ from sqlalchemy.orm.exc import MultipleResultsFound from notifications_routing.authorization_service import get_group_users_api from notifications_routing.config import Config from notifications_routing.data_source.data_source import DataSource +from notifications_routing.data_source.data_source import Device as DeviceModel +from notifications_routing.data_source.data_source import Preference as PreferenceModel from notifications_routing.exceptions import DuplicatedError, MultipleResultsFoundError, NotFoundDataSourceError from notifications_routing.utils import FeedFrequency @@ -102,6 +104,12 @@ class PostgresDataSource(DataSource): DataSource.LAST_LOGIN: user.lastLogin, } + def is_target(member): + return str(member.id) in specified_target_users + + def is_not_unsubscribed(member): + return str(member.id) not in unsubscribed_ids + with self.session() as session: channel = self.__get_scalar(session, Channel, id=channel_id, deleteDate=None) if not channel: @@ -110,9 +118,7 @@ class PostgresDataSource(DataSource): unsubscribed_ids = [user.id for user in channel.unsubscribed] return [ - build_user(member) - for member in channel.members - if str(member.id) in specified_target_users and str(member.id) not in unsubscribed_ids + build_user(member) for member in channel.members if is_target(member) and is_not_unsubscribed(member) ] def get_channel_unsubscribed_users(self, channel_id: str, **kwargs) -> List[str]: @@ -170,7 +176,7 @@ class PostgresDataSource(DataSource): return group_users - def get_user_preferences(self, user_id: str, channel_id: str, **kwargs) -> Dict[str, List[str]]: + def get_user_preferences(self, user_id: str, channel_id: str, **kwargs) -> Dict[str, List["Preference"]]: """Return a dictionary with preferences of user for a specific channel.""" with self.session() as session: user = self.__get_scalar(session, User, id=user_id) @@ -194,7 +200,7 @@ class PostgresDataSource(DataSource): return user_preferences - def get_user_devices(self, user_id: str, **kwargs) -> Dict[str, List[str]]: + def get_user_devices(self, user_id: str, **kwargs) -> Dict[str, List["Device"]]: """Return a dictionary with devices of user.""" with self.session() as session: user = self.__get_scalar(session, User, id=user_id) @@ -373,7 +379,7 @@ preferences_disabled_channels = Table( ) -class Device(PostgresDataSource.Base): +class Device(PostgresDataSource.Base, DeviceModel): """Device Model.""" __tablename__ = "Devices" @@ -387,7 +393,7 @@ class Device(PostgresDataSource.Base): token = Column(String) -class Preference(PostgresDataSource.Base): +class Preference(PostgresDataSource.Base, PreferenceModel): """Preference Model.""" __tablename__ = "Preferences" diff --git a/notifications_routing/preferences.py b/notifications_routing/preferences.py index 0fc4f60..af44d9e 100644 --- a/notifications_routing/preferences.py +++ b/notifications_routing/preferences.py @@ -9,10 +9,9 @@ from megabus import Publisher from notifications_routing.auditing import audit_notification from notifications_routing.config import Config from notifications_routing.data_source.data_source import DataSource -from notifications_routing.data_source.postgres.postgres_data_source import UserFeedNotification +from notifications_routing.data_source.postgres.postgres_data_source import Device, Preference, UserFeedNotification from notifications_routing.exceptions import DuplicatedError - -from .utils import ( +from notifications_routing.utils import ( OutputMessageKeys, convert_notification_email_to_json_string, convert_notification_push_to_json_string, @@ -26,17 +25,17 @@ class TargetPreference: Holds method and devices together. """ - def __init__(self, method, devices, scheduledTime, scheduledDay, id): + def __init__(self, method, devices, scheduled_time, scheduled_day, preference_id): """Initialize the Target Preference.""" self.method = method self.devices = devices - self.scheduledTime = scheduledTime - self.scheduledDay = scheduledDay - self.id = id + self.scheduled_time = scheduled_time + self.scheduled_day = scheduled_day + self.preference_id = preference_id def get_delivery_methods_and_targets( - created_timestamp: str, priority: str, preferences: Dict[str, List[str]] + created_timestamp: str, priority: str, preferences: Dict[str, List[Preference]] ) -> Set[TargetPreference]: """Specific implementation of get user's delivery methods. @@ -120,18 +119,22 @@ def apply_user_preferences( logging.debug("Applying User Preferences delivery method: %s", preference.method) if preference.method in Config.FEED_METHODS: create_feed_notification( - data_source, message, user, preference.method, preference.scheduledTime, preference.scheduledDay + data_source, message, user, preference.method, preference.scheduled_time, preference.scheduled_day ) audit_notification( message[OutputMessageKeys.ID], - {"event": "Preference added to feed", "method": preference.method, "preference": preference.id}, + { + "event": "Preference added to feed", + "method": preference.method, + "preference": preference.preference_id, + }, user_id=user[data_source.EMAIL], ) continue if preference.method == "LIVE": for device in preference.devices: - send_to_device(publisher, message, device) + send_to_device(publisher, message, device, user[data_source.EMAIL]) audit_notification( message[OutputMessageKeys.ID], { @@ -149,7 +152,7 @@ def apply_user_preferences( logging.error("Invalid delivery method: %s", preference.method) -def send_to_device(publisher: megabus.Publisher, message: Dict, device): +def send_to_device(publisher: megabus.Publisher, message: Dict, device: Device, email: str): """Specific implementation of send to device. :param publisher: Publisher object used to publish messages @@ -165,20 +168,20 @@ def send_to_device(publisher: megabus.Publisher, message: Dict, device): if device.type == "BROWSER": if device.subType == "OTHER": logging.info(device) - send_live_webpush(publisher, message, device.token) + send_live_webpush(publisher, message, device.token, email) return if device.subType == "SAFARI": logging.info(device) - send_live_safaripush(publisher, message, device.token) + send_live_safaripush(publisher, message, device.token, email) return if device.type == "APP": if device.subType == "WINDOWS": - send_live_webpush(publisher, message, device.token, encoding="aesgcm") + send_live_webpush(publisher, message, device.token, email, encoding="aesgcm") return if device.subType == "MATTERMOST": - send_live_mattermost(publisher, message, device.token) + send_live_mattermost(publisher, message, device.token, email) return # if device.subType == "LINUX": @@ -204,7 +207,7 @@ def apply_all( publisher: megabus.Publisher, message: Dict, email: str, - user_devices: Dict[str, List[str]], + user_devices: Dict[str, List[Device]], ): """Specific implementation to send to all devices for Critical notifications. @@ -215,7 +218,7 @@ def apply_all( """ logging.debug("Applying to all types/devices") for device in user_devices[DataSource.DEVICES]: - send_to_device(publisher, message, device) + send_to_device(publisher, message, device, email) audit_notification( message[OutputMessageKeys.ID], { @@ -257,39 +260,42 @@ def send_live_email(publisher: Publisher, message: Dict, email: str): publisher.send(message, extension="email", ttl=Config.TTL, headers={"persistent": "true"}) -def send_live_webpush(publisher: Publisher, message: Dict, devicetoken: str, **kwargs): +def send_live_webpush(publisher: Publisher, message: Dict, devicetoken: str, email: str, **kwargs): """Send live browser web push notification. :param publisher: Publisher object used to publish messages :param message: message object :param devicetoken: browser webpush token + :param email: user's email :param encoding: allows to specify aesgcm crypto insteadn of default aes128gcm (mainly for Windows UWP test) """ encoding = kwargs.get("encoding", None) logging.debug("Sending to webpush:\n\tsummary: %s\n\ttoken: %s", message[OutputMessageKeys.SUMMARY], devicetoken) - message = convert_notification_push_to_json_string(message, devicetoken, encoding) + message = convert_notification_push_to_json_string(message, devicetoken, email, encoding) publisher.send(message, extension="webpush", ttl=Config.TTL, headers={"persistent": "true"}) -def send_live_safaripush(publisher: Publisher, message: Dict, devicetoken: str): +def send_live_safaripush(publisher: Publisher, message: Dict, devicetoken: str, email: str): """Send live safari web push notification. :param publisher: Publisher object used to publish messages :param message: message object :param devicetoken: browser webpush token + :param email: user's email """ logging.debug("Sending to safaripush:\n\tsummary: %s\n\ttoken: %s", message[OutputMessageKeys.SUMMARY], devicetoken) - message = convert_notification_push_to_json_string(message, devicetoken) + message = convert_notification_push_to_json_string(message, devicetoken, email) publisher.send(message, extension="safaripush", ttl=Config.TTL, headers={"persistent": "true"}) -def send_live_mattermost(publisher: Publisher, message: Dict, devicetoken: str, **kwargs): +def send_live_mattermost(publisher: Publisher, message: Dict, devicetoken: str, email: str, **kwargs): """Send live Mattermost notification. :param publisher: Publisher object used to publish messages :param message: message object :param devicetoken: mattermost identifier + :param email: user's email """ logging.debug("Sending to Mattermost:\n\tsummary: %s\n\ttoken: %s", message[OutputMessageKeys.SUMMARY], devicetoken) - message = convert_notification_push_to_json_string(message, devicetoken) + message = convert_notification_push_to_json_string(message, devicetoken, email) publisher.send(message, extension="mattermost", ttl=Config.TTL, headers={"persistent": "true"}) diff --git a/notifications_routing/utils.py b/notifications_routing/utils.py index ff87e31..693c425 100644 --- a/notifications_routing/utils.py +++ b/notifications_routing/utils.py @@ -100,7 +100,7 @@ def convert_notification_email_to_json_string(message, email): return json.dumps(notif) -def convert_notification_push_to_json_string(message, devicetoken, encoding=None): +def convert_notification_push_to_json_string(message, devicetoken, email, encoding=None): """Convert notification push to json string. encoding: allows to specify aesgcm as encryption instead of default aes128gcm (mostly for test Windows UWP app) @@ -119,6 +119,7 @@ def convert_notification_push_to_json_string(message, devicetoken, encoding=None "notification_id": message[OutputMessageKeys.ID], "created_at": message[OutputMessageKeys.CREATED_TIMESTAMP], "private": message.get(OutputMessageKeys.PRIVATE) or False, + "email": email, } ) diff --git a/tests/unit/test_router.py b/tests/unit/test_router.py index efba0b0..871bf23 100644 --- a/tests/unit/test_router.py +++ b/tests/unit/test_router.py @@ -165,7 +165,7 @@ def device(): def target_preference(preference): """Target Preference object.""" return TargetPreference( - preference.type, preference.devices, preference.userId, preference.scheduledDay, preference.id + preference.type, preference.devices, preference.scheduledTime, preference.scheduledDay, preference.id ) -- GitLab