From 1d84f7fd8a8f9afe3535b4bab3726ed8203e00b5 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 29 Sep 2019 17:17:25 -0400 Subject: [PATCH] url() supports privacy flag for masking pwds, tokens, apikeys, etc (#156) --- apprise/URLBase.py | 55 ++++- apprise/__init__.py | 3 +- apprise/config/ConfigFile.py | 2 +- apprise/config/ConfigHTTP.py | 6 +- apprise/plugins/NotifyBoxcar.py | 10 +- apprise/plugins/NotifyClickSend.py | 6 +- apprise/plugins/NotifyD7Networks.py | 6 +- apprise/plugins/NotifyDBus.py | 2 +- apprise/plugins/NotifyDiscord.py | 6 +- apprise/plugins/NotifyEmail.py | 6 +- apprise/plugins/NotifyEmby.py | 6 +- apprise/plugins/NotifyFaast.py | 4 +- apprise/plugins/NotifyFlock.py | 4 +- apprise/plugins/NotifyGitter.py | 4 +- apprise/plugins/NotifyGnome.py | 2 +- apprise/plugins/NotifyGotify.py | 4 +- apprise/plugins/NotifyGrowl/__init__.py | 30 ++- apprise/plugins/NotifyIFTTT.py | 4 +- apprise/plugins/NotifyJSON.py | 6 +- apprise/plugins/NotifyJoin.py | 4 +- apprise/plugins/NotifyKumulos.py | 6 +- apprise/plugins/NotifyMSG91.py | 4 +- apprise/plugins/NotifyMSTeams.py | 8 +- apprise/plugins/NotifyMailgun.py | 4 +- apprise/plugins/NotifyMatrix.py | 6 +- apprise/plugins/NotifyMatterMost.py | 15 +- apprise/plugins/NotifyMessageBird.py | 4 +- apprise/plugins/NotifyNexmo.py | 8 +- apprise/plugins/NotifyProwl.py | 7 +- apprise/plugins/NotifyPushBullet.py | 4 +- apprise/plugins/NotifyPushed.py | 8 +- apprise/plugins/NotifyPushjet.py | 10 +- apprise/plugins/NotifyPushover.py | 10 +- apprise/plugins/NotifyRocketChat.py | 8 +- apprise/plugins/NotifyRyver.py | 4 +- apprise/plugins/NotifySNS.py | 10 +- apprise/plugins/NotifySendGrid.py | 4 +- apprise/plugins/NotifySimplePush.py | 11 +- apprise/plugins/NotifySlack.py | 8 +- apprise/plugins/NotifyTechulusPush.py | 4 +- apprise/plugins/NotifyTelegram.py | 4 +- apprise/plugins/NotifyTwilio.py | 8 +- apprise/plugins/NotifyTwist.py | 6 +- apprise/plugins/NotifyTwitter.py | 13 +- apprise/plugins/NotifyWebexTeams.py | 4 +- apprise/plugins/NotifyWindows.py | 2 +- apprise/plugins/NotifyXBMC.py | 8 +- apprise/plugins/NotifyXML.py | 6 +- apprise/plugins/NotifyXMPP.py | 14 +- apprise/plugins/NotifyZulip.py | 6 +- test/test_api.py | 51 ++++ test/test_email_plugin.py | 21 ++ test/test_growl_plugin.py | 4 + test/test_rest_plugins.py | 309 ++++++++++++++++++------ test/test_xmpp_plugin.py | 48 +++- 55 files changed, 595 insertions(+), 222 deletions(-) diff --git a/apprise/URLBase.py b/apprise/URLBase.py index af5e67d5..b919ea29 100644 --- a/apprise/URLBase.py +++ b/apprise/URLBase.py @@ -50,6 +50,21 @@ from .utils import parse_list # Used to break a path list into parts PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') + +class PrivacyMode(object): + # Defines different privacy modes strings can be printed as + # Astrisk sets 4 of them: e.g. **** + # This is used for passwords + Secret = '*' + + # Outer takes the first and last character displaying them with + # 3 dots between. Hence, 'i-am-a-token' would become 'i...n' + Outer = 'o' + + # Displays the last four characters + Tail = 't' + + # Define the HTML Lookup Table HTML_LOOKUP = { 400: 'Bad Request - Unsupported Parameters.', @@ -183,7 +198,7 @@ class URLBase(object): self._last_io_datetime = datetime.now() return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Assembles the URL associated with the notification based on the arguments provied. @@ -302,6 +317,44 @@ class URLBase(object): # Python v2.7 return _quote(content, safe=safe) + @staticmethod + def pprint(content, privacy=True, mode=PrivacyMode.Outer, + # privacy print; quoting is ignored when privacy is set to True + quote=True, safe='/', encoding=None, errors=None): + """ + Privacy Print is used to mainpulate the string before passing it into + part of the URL. It is used to mask/hide private details such as + tokens, passwords, apikeys, etc from on-lookers. If the privacy=False + is set, then the quote variable is the next flag checked. + + Quoting is never done if the privacy flag is set to true to avoid + skewing the expected output. + """ + + if not privacy: + if quote: + # Return quoted string if specified to do so + return URLBase.quote( + content, safe=safe, encoding=encoding, errors=errors) + + # Return content 'as-is' + return content + + if mode is PrivacyMode.Secret: + # Return 4 Asterisks + return '****' + + if not isinstance(content, six.string_types) or not content: + # Nothing more to do + return '' + + if mode is PrivacyMode.Tail: + # Return the trailing 4 characters + return '...{}'.format(content[-4:]) + + # Default mode is Outer Mode + return '{}...{}'.format(content[0:1], content[-1:]) + @staticmethod def urlencode(query, doseq=False, safe='', encoding=None, errors=None): """Convert a mapping object or a sequence of two-element tuples diff --git a/apprise/__init__.py b/apprise/__init__.py index 33aa9343..32da4ffc 100644 --- a/apprise/__init__.py +++ b/apprise/__init__.py @@ -43,6 +43,7 @@ from .common import ConfigFormat from .common import CONFIG_FORMATS from .URLBase import URLBase +from .URLBase import PrivacyMode from .plugins.NotifyBase import NotifyBase from .config.ConfigBase import ConfigBase @@ -63,5 +64,5 @@ __all__ = [ # Reference 'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'OverflowMode', 'NOTIFY_TYPES', 'NOTIFY_IMAGE_SIZES', 'NOTIFY_FORMATS', 'OVERFLOW_MODES', - 'ConfigFormat', 'CONFIG_FORMATS', + 'ConfigFormat', 'CONFIG_FORMATS', 'PrivacyMode', ] diff --git a/apprise/config/ConfigFile.py b/apprise/config/ConfigFile.py index e9ab93bf..0ca406dc 100644 --- a/apprise/config/ConfigFile.py +++ b/apprise/config/ConfigFile.py @@ -57,7 +57,7 @@ class ConfigFile(ConfigBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ diff --git a/apprise/config/ConfigHTTP.py b/apprise/config/ConfigHTTP.py index 6c3a3259..16fa44aa 100644 --- a/apprise/config/ConfigHTTP.py +++ b/apprise/config/ConfigHTTP.py @@ -28,6 +28,7 @@ import six import requests from .ConfigBase import ConfigBase from ..common import ConfigFormat +from ..URLBase import PrivacyMode # Support YAML formats # text/yaml @@ -89,7 +90,7 @@ class ConfigHTTP(ConfigBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -111,7 +112,8 @@ class ConfigHTTP(ConfigBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=self.quote(self.user, safe=''), - password=self.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) elif self.user: auth = '{user}@'.format( diff --git a/apprise/plugins/NotifyBoxcar.py b/apprise/plugins/NotifyBoxcar.py index 5c74f44d..72c0e8f5 100644 --- a/apprise/plugins/NotifyBoxcar.py +++ b/apprise/plugins/NotifyBoxcar.py @@ -38,6 +38,7 @@ except ImportError: from urllib.parse import urlparse from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..utils import parse_bool from ..common import NotifyType from ..common import NotifyImageSize @@ -330,7 +331,7 @@ class NotifyBoxcar(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -343,10 +344,11 @@ class NotifyBoxcar(NotifyBase): 'verify': 'yes' if self.verify_certificate else 'no', } - return '{schema}://{access}/{secret}/{targets}/?{args}'.format( + return '{schema}://{access}/{secret}/{targets}?{args}'.format( schema=self.secure_protocol, - access=NotifyBoxcar.quote(self.access, safe=''), - secret=NotifyBoxcar.quote(self.secret, safe=''), + access=self.pprint(self.access, privacy, safe=''), + secret=self.pprint( + self.secret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join([ NotifyBoxcar.quote(x, safe='') for x in chain( self.tags, self.device_tokens) if x != DEFAULT_TAG]), diff --git a/apprise/plugins/NotifyClickSend.py b/apprise/plugins/NotifyClickSend.py index fd8ebcb9..8535bd27 100644 --- a/apprise/plugins/NotifyClickSend.py +++ b/apprise/plugins/NotifyClickSend.py @@ -42,6 +42,7 @@ from json import dumps from base64 import b64encode from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..utils import parse_bool @@ -265,7 +266,7 @@ class NotifyClickSend(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -281,7 +282,8 @@ class NotifyClickSend(NotifyBase): # Setup Authentication auth = '{user}:{password}@'.format( user=NotifyClickSend.quote(self.user, safe=''), - password=NotifyClickSend.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) return '{schema}://{auth}{targets}?{args}'.format( diff --git a/apprise/plugins/NotifyD7Networks.py b/apprise/plugins/NotifyD7Networks.py index 1b7fcb5e..3596159f 100644 --- a/apprise/plugins/NotifyD7Networks.py +++ b/apprise/plugins/NotifyD7Networks.py @@ -38,6 +38,7 @@ from json import dumps from json import loads from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..utils import parse_bool @@ -380,7 +381,7 @@ class NotifyD7Networks(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -402,7 +403,8 @@ class NotifyD7Networks(NotifyBase): return '{schema}://{user}:{password}@{targets}/?{args}'.format( schema=self.secure_protocol, user=NotifyD7Networks.quote(self.user, safe=''), - password=NotifyD7Networks.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join( [NotifyD7Networks.quote(x, safe='') for x in self.targets]), args=NotifyD7Networks.urlencode(args)) diff --git a/apprise/plugins/NotifyDBus.py b/apprise/plugins/NotifyDBus.py index b1db496c..b82c3e08 100644 --- a/apprise/plugins/NotifyDBus.py +++ b/apprise/plugins/NotifyDBus.py @@ -332,7 +332,7 @@ class NotifyDBus(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ diff --git a/apprise/plugins/NotifyDiscord.py b/apprise/plugins/NotifyDiscord.py index 84ed8ee8..1d2981e2 100644 --- a/apprise/plugins/NotifyDiscord.py +++ b/apprise/plugins/NotifyDiscord.py @@ -309,7 +309,7 @@ class NotifyDiscord(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -328,8 +328,8 @@ class NotifyDiscord(NotifyBase): return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format( schema=self.secure_protocol, - webhook_id=NotifyDiscord.quote(self.webhook_id, safe=''), - webhook_token=NotifyDiscord.quote(self.webhook_token, safe=''), + webhook_id=self.pprint(self.webhook_id, privacy, safe=''), + webhook_token=self.pprint(self.webhook_token, privacy, safe=''), args=NotifyDiscord.urlencode(args), ) diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index 1effc674..e880302c 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -31,6 +31,7 @@ from socket import error as SocketError from datetime import datetime from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyFormat from ..common import NotifyType from ..utils import is_email @@ -618,7 +619,7 @@ class NotifyEmail(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -652,7 +653,8 @@ class NotifyEmail(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyEmail.quote(user, safe=''), - password=NotifyEmail.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) else: # user url diff --git a/apprise/plugins/NotifyEmby.py b/apprise/plugins/NotifyEmby.py index 82ac7da2..5fa56122 100644 --- a/apprise/plugins/NotifyEmby.py +++ b/apprise/plugins/NotifyEmby.py @@ -35,6 +35,7 @@ from json import dumps from json import loads from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..utils import parse_bool from ..common import NotifyType from .. import __version__ as VERSION @@ -581,7 +582,7 @@ class NotifyEmby(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -599,7 +600,8 @@ class NotifyEmby(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyEmby.quote(self.user, safe=''), - password=NotifyEmby.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) else: # self.user is set auth = '{user}@'.format( diff --git a/apprise/plugins/NotifyFaast.py b/apprise/plugins/NotifyFaast.py index 39df2c21..1a19b8cb 100644 --- a/apprise/plugins/NotifyFaast.py +++ b/apprise/plugins/NotifyFaast.py @@ -161,7 +161,7 @@ class NotifyFaast(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -176,7 +176,7 @@ class NotifyFaast(NotifyBase): return '{schema}://{authtoken}/?{args}'.format( schema=self.protocol, - authtoken=NotifyFaast.quote(self.authtoken, safe=''), + authtoken=self.pprint(self.authtoken, privacy, safe=''), args=NotifyFaast.urlencode(args), ) diff --git a/apprise/plugins/NotifyFlock.py b/apprise/plugins/NotifyFlock.py index b3e65672..dadf42f5 100644 --- a/apprise/plugins/NotifyFlock.py +++ b/apprise/plugins/NotifyFlock.py @@ -305,7 +305,7 @@ class NotifyFlock(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -320,7 +320,7 @@ class NotifyFlock(NotifyBase): return '{schema}://{token}/{targets}?{args}'\ .format( schema=self.secure_protocol, - token=NotifyFlock.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), targets='/'.join( [NotifyFlock.quote(target, safe='') for target in self.targets]), diff --git a/apprise/plugins/NotifyGitter.py b/apprise/plugins/NotifyGitter.py index 47f69a07..dc7ee49a 100644 --- a/apprise/plugins/NotifyGitter.py +++ b/apprise/plugins/NotifyGitter.py @@ -367,7 +367,7 @@ class NotifyGitter(NotifyBase): return (True, content) - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -382,7 +382,7 @@ class NotifyGitter(NotifyBase): return '{schema}://{token}/{targets}/?{args}'.format( schema=self.secure_protocol, - token=NotifyGitter.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), targets='/'.join( [NotifyGitter.quote(x, safe='') for x in self.targets]), args=NotifyGitter.urlencode(args)) diff --git a/apprise/plugins/NotifyGnome.py b/apprise/plugins/NotifyGnome.py index b985e203..93205ad4 100644 --- a/apprise/plugins/NotifyGnome.py +++ b/apprise/plugins/NotifyGnome.py @@ -201,7 +201,7 @@ class NotifyGnome(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ diff --git a/apprise/plugins/NotifyGotify.py b/apprise/plugins/NotifyGotify.py index 33d34c56..3c2e494c 100644 --- a/apprise/plugins/NotifyGotify.py +++ b/apprise/plugins/NotifyGotify.py @@ -223,7 +223,7 @@ class NotifyGotify(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -243,7 +243,7 @@ class NotifyGotify(NotifyBase): hostname=NotifyGotify.quote(self.host, safe=''), port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), - token=NotifyGotify.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), args=NotifyGotify.urlencode(args), ) diff --git a/apprise/plugins/NotifyGrowl/__init__.py b/apprise/plugins/NotifyGrowl/__init__.py index 2e8fa6a7..239b8835 100644 --- a/apprise/plugins/NotifyGrowl/__init__.py +++ b/apprise/plugins/NotifyGrowl/__init__.py @@ -26,6 +26,7 @@ from .gntp import notifier from .gntp import errors from ..NotifyBase import NotifyBase +from ...URLBase import PrivacyMode from ...common import NotifyImageSize from ...common import NotifyType from ...utils import parse_bool @@ -88,26 +89,32 @@ class NotifyGrowl(NotifyBase): # Default Growl Port default_port = 23053 + # Define object templates # Define object templates templates = ( - '{schema}://{apikey}', - '{schema}://{apikey}/{providerkey}', + '{schema}://{host}', + '{schema}://{host}:{port}', + '{schema}://{password}@{host}', + '{schema}://{password}@{host}:{port}', ) # Define our template tokens template_tokens = dict(NotifyBase.template_tokens, **{ - 'apikey': { - 'name': _('API Key'), + 'host': { + 'name': _('Hostname'), 'type': 'string', - 'private': True, 'required': True, - 'map_to': 'host', }, - 'providerkey': { - 'name': _('Provider Key'), + 'port': { + 'name': _('Port'), + 'type': 'int', + 'min': 1, + 'max': 65535, + }, + 'password': { + 'name': _('Password'), 'type': 'string', 'private': True, - 'map_to': 'fullpath', }, }) @@ -262,7 +269,7 @@ class NotifyGrowl(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -291,7 +298,8 @@ class NotifyGrowl(NotifyBase): if self.user: # The growl password is stored in the user field auth = '{password}@'.format( - password=NotifyGrowl.quote(self.user, safe=''), + password=self.pprint( + self.user, privacy, mode=PrivacyMode.Secret, safe=''), ) return '{schema}://{auth}{hostname}{port}/?{args}'.format( diff --git a/apprise/plugins/NotifyIFTTT.py b/apprise/plugins/NotifyIFTTT.py index 85c913ad..d557be7b 100644 --- a/apprise/plugins/NotifyIFTTT.py +++ b/apprise/plugins/NotifyIFTTT.py @@ -285,7 +285,7 @@ class NotifyIFTTT(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -303,7 +303,7 @@ class NotifyIFTTT(NotifyBase): return '{schema}://{webhook_id}@{events}/?{args}'.format( schema=self.secure_protocol, - webhook_id=NotifyIFTTT.quote(self.webhook_id, safe=''), + webhook_id=self.pprint(self.webhook_id, privacy, safe=''), events='/'.join([NotifyIFTTT.quote(x, safe='') for x in self.events]), args=NotifyIFTTT.urlencode(args), diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py index 97e7406b..2858e18c 100644 --- a/apprise/plugins/NotifyJSON.py +++ b/apprise/plugins/NotifyJSON.py @@ -28,6 +28,7 @@ import requests from json import dumps from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyImageSize from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ @@ -120,7 +121,7 @@ class NotifyJSON(NotifyBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -140,7 +141,8 @@ class NotifyJSON(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyJSON.quote(self.user, safe=''), - password=NotifyJSON.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) elif self.user: auth = '{user}@'.format( diff --git a/apprise/plugins/NotifyJoin.py b/apprise/plugins/NotifyJoin.py index 864c7c16..88fcc679 100644 --- a/apprise/plugins/NotifyJoin.py +++ b/apprise/plugins/NotifyJoin.py @@ -270,7 +270,7 @@ class NotifyJoin(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -285,7 +285,7 @@ class NotifyJoin(NotifyBase): return '{schema}://{apikey}/{devices}/?{args}'.format( schema=self.secure_protocol, - apikey=NotifyJoin.quote(self.apikey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), devices='/'.join([NotifyJoin.quote(x, safe='') for x in self.devices]), args=NotifyJoin.urlencode(args)) diff --git a/apprise/plugins/NotifyKumulos.py b/apprise/plugins/NotifyKumulos.py index fd94a805..723c1674 100644 --- a/apprise/plugins/NotifyKumulos.py +++ b/apprise/plugins/NotifyKumulos.py @@ -214,7 +214,7 @@ class NotifyKumulos(NotifyBase): return False return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -228,8 +228,8 @@ class NotifyKumulos(NotifyBase): return '{schema}://{apikey}/{serverkey}/?{args}'.format( schema=self.secure_protocol, - apikey=NotifyKumulos.quote(self.apikey, safe=''), - serverkey=NotifyKumulos.quote(self.serverkey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), + serverkey=self.pprint(self.serverkey, privacy, safe=''), args=NotifyKumulos.urlencode(args), ) diff --git a/apprise/plugins/NotifyMSG91.py b/apprise/plugins/NotifyMSG91.py index ffd43126..a2e6dc79 100644 --- a/apprise/plugins/NotifyMSG91.py +++ b/apprise/plugins/NotifyMSG91.py @@ -316,7 +316,7 @@ class NotifyMSG91(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -334,7 +334,7 @@ class NotifyMSG91(NotifyBase): return '{schema}://{authkey}/{targets}/?{args}'.format( schema=self.secure_protocol, - authkey=self.authkey, + authkey=self.pprint(self.authkey, privacy, safe=''), targets='/'.join( [NotifyMSG91.quote(x, safe='') for x in self.targets]), args=NotifyMSG91.urlencode(args)) diff --git a/apprise/plugins/NotifyMSTeams.py b/apprise/plugins/NotifyMSTeams.py index b75be7de..64b5fd65 100644 --- a/apprise/plugins/NotifyMSTeams.py +++ b/apprise/plugins/NotifyMSTeams.py @@ -293,7 +293,7 @@ class NotifyMSTeams(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -309,9 +309,9 @@ class NotifyMSTeams(NotifyBase): return '{schema}://{token_a}/{token_b}/{token_c}/'\ '?{args}'.format( schema=self.secure_protocol, - token_a=NotifyMSTeams.quote(self.token_a, safe=''), - token_b=NotifyMSTeams.quote(self.token_b, safe=''), - token_c=NotifyMSTeams.quote(self.token_c, safe=''), + token_a=self.pprint(self.token_a, privacy, safe=''), + token_b=self.pprint(self.token_b, privacy, safe=''), + token_c=self.pprint(self.token_c, privacy, safe=''), args=NotifyMSTeams.urlencode(args), ) diff --git a/apprise/plugins/NotifyMailgun.py b/apprise/plugins/NotifyMailgun.py index b53982f6..e315c624 100644 --- a/apprise/plugins/NotifyMailgun.py +++ b/apprise/plugins/NotifyMailgun.py @@ -310,7 +310,7 @@ class NotifyMailgun(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -331,7 +331,7 @@ class NotifyMailgun(NotifyBase): schema=self.secure_protocol, host=self.host, user=NotifyMailgun.quote(self.user, safe=''), - apikey=NotifyMailgun.quote(self.apikey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), targets='/'.join( [NotifyMailgun.quote(x, safe='') for x in self.targets]), args=NotifyMailgun.urlencode(args)) diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index a5579e53..47c848c5 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -35,6 +35,7 @@ from json import loads from time import time from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..common import NotifyImageSize from ..common import NotifyFormat @@ -946,7 +947,7 @@ class NotifyMatrix(NotifyBase): """ self._logout() - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -965,7 +966,8 @@ class NotifyMatrix(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyMatrix.quote(self.user, safe=''), - password=NotifyMatrix.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) elif self.user: diff --git a/apprise/plugins/NotifyMatterMost.py b/apprise/plugins/NotifyMatterMost.py index 57d95e41..91721149 100644 --- a/apprise/plugins/NotifyMatterMost.py +++ b/apprise/plugins/NotifyMatterMost.py @@ -280,7 +280,7 @@ class NotifyMatterMost(NotifyBase): # Return our overall status return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -302,15 +302,24 @@ class NotifyMatterMost(NotifyBase): default_port = 443 if self.secure else self.default_port default_schema = self.secure_protocol if self.secure else self.protocol + # Determine if there is a botname present + botname = '' + if self.user: + botname = '{botname}@'.format( + botname=NotifyMatterMost.quote(self.user, safe=''), + ) + return \ - '{schema}://{hostname}{port}{fullpath}{authtoken}/?{args}'.format( + '{schema}://{botname}{hostname}{port}{fullpath}{authtoken}' \ + '/?{args}'.format( schema=default_schema, + botname=botname, hostname=NotifyMatterMost.quote(self.host, safe=''), port='' if not self.port or self.port == default_port else ':{}'.format(self.port), fullpath='/' if not self.fullpath else '{}/'.format( NotifyMatterMost.quote(self.fullpath, safe='/')), - authtoken=NotifyMatterMost.quote(self.authtoken, safe=''), + authtoken=self.pprint(self.authtoken, privacy, safe=''), args=NotifyMatterMost.urlencode(args), ) diff --git a/apprise/plugins/NotifyMessageBird.py b/apprise/plugins/NotifyMessageBird.py index b33f8003..3de85652 100644 --- a/apprise/plugins/NotifyMessageBird.py +++ b/apprise/plugins/NotifyMessageBird.py @@ -304,7 +304,7 @@ class NotifyMessageBird(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -318,7 +318,7 @@ class NotifyMessageBird(NotifyBase): return '{schema}://{apikey}/{source}/{targets}/?{args}'.format( schema=self.secure_protocol, - apikey=self.apikey, + apikey=self.pprint(self.apikey, privacy, safe=''), source=self.source, targets='/'.join( [NotifyMessageBird.quote(x, safe='') for x in self.targets]), diff --git a/apprise/plugins/NotifyNexmo.py b/apprise/plugins/NotifyNexmo.py index 916bdf8c..f400f2c4 100644 --- a/apprise/plugins/NotifyNexmo.py +++ b/apprise/plugins/NotifyNexmo.py @@ -33,6 +33,7 @@ import re import requests from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..AppriseLocale import gettext_lazy as _ @@ -334,7 +335,7 @@ class NotifyNexmo(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -349,8 +350,9 @@ class NotifyNexmo(NotifyBase): return '{schema}://{key}:{secret}@{source}/{targets}/?{args}'.format( schema=self.secure_protocol, - key=self.apikey, - secret=self.secret, + key=self.pprint(self.apikey, privacy, safe=''), + secret=self.pprint( + self.secret, privacy, mode=PrivacyMode.Secret, safe=''), source=NotifyNexmo.quote(self.source, safe=''), targets='/'.join( [NotifyNexmo.quote(x, safe='') for x in self.targets]), diff --git a/apprise/plugins/NotifyProwl.py b/apprise/plugins/NotifyProwl.py index 38e6431f..bb56787b 100644 --- a/apprise/plugins/NotifyProwl.py +++ b/apprise/plugins/NotifyProwl.py @@ -223,7 +223,7 @@ class NotifyProwl(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -247,9 +247,8 @@ class NotifyProwl(NotifyBase): return '{schema}://{apikey}/{providerkey}/?{args}'.format( schema=self.secure_protocol, - apikey=NotifyProwl.quote(self.apikey, safe=''), - providerkey='' if not self.providerkey - else NotifyProwl.quote(self.providerkey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), + providerkey=self.pprint(self.providerkey, privacy, safe=''), args=NotifyProwl.urlencode(args), ) diff --git a/apprise/plugins/NotifyPushBullet.py b/apprise/plugins/NotifyPushBullet.py index 50af8be6..1bed494b 100644 --- a/apprise/plugins/NotifyPushBullet.py +++ b/apprise/plugins/NotifyPushBullet.py @@ -215,7 +215,7 @@ class NotifyPushBullet(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -235,7 +235,7 @@ class NotifyPushBullet(NotifyBase): return '{schema}://{accesstoken}/{targets}/?{args}'.format( schema=self.secure_protocol, - accesstoken=NotifyPushBullet.quote(self.accesstoken, safe=''), + accesstoken=self.pprint(self.accesstoken, privacy, safe=''), targets=targets, args=NotifyPushBullet.urlencode(args)) diff --git a/apprise/plugins/NotifyPushed.py b/apprise/plugins/NotifyPushed.py index fa226138..318443bd 100644 --- a/apprise/plugins/NotifyPushed.py +++ b/apprise/plugins/NotifyPushed.py @@ -29,6 +29,7 @@ from json import dumps from itertools import chain from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..AppriseLocale import gettext_lazy as _ @@ -285,7 +286,7 @@ class NotifyPushed(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -299,8 +300,9 @@ class NotifyPushed(NotifyBase): return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format( schema=self.secure_protocol, - app_key=NotifyPushed.quote(self.app_key, safe=''), - app_secret=NotifyPushed.quote(self.app_secret, safe=''), + app_key=self.pprint(self.app_key, privacy, safe=''), + app_secret=self.pprint( + self.app_secret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join( [NotifyPushed.quote(x) for x in chain( # Channels are prefixed with a pound/hashtag symbol diff --git a/apprise/plugins/NotifyPushjet.py b/apprise/plugins/NotifyPushjet.py index 5f2d3b5e..74d5a58f 100644 --- a/apprise/plugins/NotifyPushjet.py +++ b/apprise/plugins/NotifyPushjet.py @@ -27,6 +27,7 @@ import requests from json import dumps from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ @@ -115,7 +116,7 @@ class NotifyPushjet(NotifyBase): # store our key self.secret_key = secret_key - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -124,7 +125,9 @@ class NotifyPushjet(NotifyBase): args = { 'format': self.notify_format, 'overflow': self.overflow_mode, - 'secret': self.secret_key, + 'secret': self.pprint( + self.secret_key, privacy, + mode=PrivacyMode.Secret, quote=False), 'verify': 'yes' if self.verify_certificate else 'no', } @@ -135,7 +138,8 @@ class NotifyPushjet(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyPushjet.quote(self.user, safe=''), - password=NotifyPushjet.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) return '{schema}://{auth}{hostname}{port}/?{args}'.format( diff --git a/apprise/plugins/NotifyPushover.py b/apprise/plugins/NotifyPushover.py index ebc77ae0..df0b7ef4 100644 --- a/apprise/plugins/NotifyPushover.py +++ b/apprise/plugins/NotifyPushover.py @@ -388,7 +388,7 @@ class NotifyPushover(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -424,12 +424,10 @@ class NotifyPushover(NotifyBase): # it from the devices list devices = '' - return '{schema}://{auth}{token}/{devices}/?{args}'.format( + return '{schema}://{user_key}@{token}/{devices}/?{args}'.format( schema=self.secure_protocol, - auth='' if not self.user - else '{user}@'.format( - user=NotifyPushover.quote(self.user, safe='')), - token=NotifyPushover.quote(self.token, safe=''), + user_key=self.pprint(self.user, privacy, safe=''), + token=self.pprint(self.token, privacy, safe=''), devices=devices, args=NotifyPushover.urlencode(args)) diff --git a/apprise/plugins/NotifyRocketChat.py b/apprise/plugins/NotifyRocketChat.py index b0860e34..feab2f74 100644 --- a/apprise/plugins/NotifyRocketChat.py +++ b/apprise/plugins/NotifyRocketChat.py @@ -31,6 +31,7 @@ from json import dumps from itertools import chain from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyImageSize from ..common import NotifyFormat from ..common import NotifyType @@ -279,7 +280,7 @@ class NotifyRocketChat(NotifyBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -297,13 +298,14 @@ class NotifyRocketChat(NotifyBase): if self.mode == RocketChatAuthMode.BASIC: auth = '{user}:{password}@'.format( user=NotifyRocketChat.quote(self.user, safe=''), - password=NotifyRocketChat.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) else: auth = '{user}{webhook}@'.format( user='{}:'.format(NotifyRocketChat.quote(self.user, safe='')) if self.user else '', - webhook=NotifyRocketChat.quote(self.webhook, safe=''), + webhook=self.pprint(self.webhook, privacy, safe=''), ) default_port = 443 if self.secure else 80 diff --git a/apprise/plugins/NotifyRyver.py b/apprise/plugins/NotifyRyver.py index ebc67de1..7380994f 100644 --- a/apprise/plugins/NotifyRyver.py +++ b/apprise/plugins/NotifyRyver.py @@ -279,7 +279,7 @@ class NotifyRyver(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -304,7 +304,7 @@ class NotifyRyver(NotifyBase): schema=self.secure_protocol, botname=botname, organization=NotifyRyver.quote(self.organization, safe=''), - token=NotifyRyver.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), args=NotifyRyver.urlencode(args), ) diff --git a/apprise/plugins/NotifySNS.py b/apprise/plugins/NotifySNS.py index c7509eb0..3774ea70 100644 --- a/apprise/plugins/NotifySNS.py +++ b/apprise/plugins/NotifySNS.py @@ -33,6 +33,7 @@ from xml.etree import ElementTree from itertools import chain from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..AppriseLocale import gettext_lazy as _ @@ -568,7 +569,7 @@ class NotifySNS(NotifyBase): return response - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -583,9 +584,10 @@ class NotifySNS(NotifyBase): return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\ '?{args}'.format( schema=self.secure_protocol, - key_id=NotifySNS.quote(self.aws_access_key_id, safe=''), - key_secret=NotifySNS.quote( - self.aws_secret_access_key, safe=''), + key_id=self.pprint(self.aws_access_key_id, privacy, safe=''), + key_secret=self.pprint( + self.aws_secret_access_key, privacy, + mode=PrivacyMode.Secret, safe=''), region=NotifySNS.quote(self.aws_region_name, safe=''), targets='/'.join( [NotifySNS.quote(x) for x in chain( diff --git a/apprise/plugins/NotifySendGrid.py b/apprise/plugins/NotifySendGrid.py index c759fbdf..4ffa373a 100644 --- a/apprise/plugins/NotifySendGrid.py +++ b/apprise/plugins/NotifySendGrid.py @@ -245,7 +245,7 @@ class NotifySendGrid(NotifyBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -280,7 +280,7 @@ class NotifySendGrid(NotifyBase): return '{schema}://{apikey}:{from_email}/{targets}?{args}'.format( schema=self.secure_protocol, - apikey=self.quote(self.apikey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), from_email=self.quote(self.from_email, safe='@'), targets='' if not has_targets else '/'.join( [NotifySendGrid.quote(x, safe='') for x in self.targets]), diff --git a/apprise/plugins/NotifySimplePush.py b/apprise/plugins/NotifySimplePush.py index 9ae2825b..bd0b9c0c 100644 --- a/apprise/plugins/NotifySimplePush.py +++ b/apprise/plugins/NotifySimplePush.py @@ -27,6 +27,7 @@ from json import loads import requests from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ @@ -263,7 +264,7 @@ class NotifySimplePush(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -282,14 +283,16 @@ class NotifySimplePush(NotifyBase): auth = '' if self.user and self.password: auth = '{salt}:{password}@'.format( - salt=NotifySimplePush.quote(self.user, safe=''), - password=NotifySimplePush.quote(self.password, safe=''), + salt=self.pprint( + self.user, privacy, mode=PrivacyMode.Secret, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) return '{schema}://{auth}{apikey}/?{args}'.format( schema=self.secure_protocol, auth=auth, - apikey=NotifySimplePush.quote(self.apikey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), args=NotifySimplePush.urlencode(args), ) diff --git a/apprise/plugins/NotifySlack.py b/apprise/plugins/NotifySlack.py index 17a7ebbc..aa0e90a2 100644 --- a/apprise/plugins/NotifySlack.py +++ b/apprise/plugins/NotifySlack.py @@ -388,7 +388,7 @@ class NotifySlack(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -412,9 +412,9 @@ class NotifySlack(NotifyBase): '?{args}'.format( schema=self.secure_protocol, botname=botname, - token_a=NotifySlack.quote(self.token_a, safe=''), - token_b=NotifySlack.quote(self.token_b, safe=''), - token_c=NotifySlack.quote(self.token_c, safe=''), + token_a=self.pprint(self.token_a, privacy, safe=''), + token_b=self.pprint(self.token_b, privacy, safe=''), + token_c=self.pprint(self.token_c, privacy, safe=''), targets='/'.join( [NotifySlack.quote(x, safe='') for x in self.channels]), args=NotifySlack.urlencode(args), diff --git a/apprise/plugins/NotifyTechulusPush.py b/apprise/plugins/NotifyTechulusPush.py index 53f7b461..10f38b70 100644 --- a/apprise/plugins/NotifyTechulusPush.py +++ b/apprise/plugins/NotifyTechulusPush.py @@ -188,7 +188,7 @@ class NotifyTechulusPush(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -202,7 +202,7 @@ class NotifyTechulusPush(NotifyBase): return '{schema}://{apikey}/?{args}'.format( schema=self.secure_protocol, - apikey=NotifyTechulusPush.quote(self.apikey, safe=''), + apikey=self.pprint(self.apikey, privacy, safe=''), args=NotifyTechulusPush.urlencode(args), ) diff --git a/apprise/plugins/NotifyTelegram.py b/apprise/plugins/NotifyTelegram.py index 2ce0ddc9..b3b5213a 100644 --- a/apprise/plugins/NotifyTelegram.py +++ b/apprise/plugins/NotifyTelegram.py @@ -553,7 +553,7 @@ class NotifyTelegram(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -571,7 +571,7 @@ class NotifyTelegram(NotifyBase): # appended into the list of chat ids return '{schema}://{bot_token}/{targets}/?{args}'.format( schema=self.secure_protocol, - bot_token=NotifyTelegram.quote(self.bot_token, safe=''), + bot_token=self.pprint(self.bot_token, privacy, safe=''), targets='/'.join( [NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]), args=NotifyTelegram.urlencode(args)) diff --git a/apprise/plugins/NotifyTwilio.py b/apprise/plugins/NotifyTwilio.py index 2bb1042b..d60ef0be 100644 --- a/apprise/plugins/NotifyTwilio.py +++ b/apprise/plugins/NotifyTwilio.py @@ -45,6 +45,7 @@ import requests from json import loads from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..AppriseLocale import gettext_lazy as _ @@ -374,7 +375,7 @@ class NotifyTwilio(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -388,8 +389,9 @@ class NotifyTwilio(NotifyBase): return '{schema}://{sid}:{token}@{source}/{targets}/?{args}'.format( schema=self.secure_protocol, - sid=self.account_sid, - token=self.auth_token, + sid=self.pprint( + self.account_sid, privacy, mode=PrivacyMode.Tail, safe=''), + token=self.pprint(self.auth_token, privacy, safe=''), source=NotifyTwilio.quote(self.source, safe=''), targets='/'.join( [NotifyTwilio.quote(x, safe='') for x in self.targets]), diff --git a/apprise/plugins/NotifyTwist.py b/apprise/plugins/NotifyTwist.py index 1c15ce94..0aafe18a 100644 --- a/apprise/plugins/NotifyTwist.py +++ b/apprise/plugins/NotifyTwist.py @@ -32,6 +32,7 @@ from json import loads from itertools import chain from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyFormat from ..common import NotifyType from ..utils import parse_list @@ -223,7 +224,7 @@ class NotifyTwist(NotifyBase): self.default_notification_channel)) return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -237,7 +238,8 @@ class NotifyTwist(NotifyBase): return '{schema}://{password}:{user}@{host}/{targets}/?{args}'.format( schema=self.secure_protocol, - password=self.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), user=self.quote(self.user, safe=''), host=self.host, targets='/'.join( diff --git a/apprise/plugins/NotifyTwitter.py b/apprise/plugins/NotifyTwitter.py index 2ecd6133..5287ed12 100644 --- a/apprise/plugins/NotifyTwitter.py +++ b/apprise/plugins/NotifyTwitter.py @@ -33,6 +33,7 @@ from requests_oauthlib import OAuth1 from json import dumps from json import loads from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..utils import parse_bool @@ -558,7 +559,7 @@ class NotifyTwitter(NotifyBase): """ return 10000 if self.mode == TwitterMessageMode.DM else 280 - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -578,10 +579,12 @@ class NotifyTwitter(NotifyBase): return '{schema}://{ckey}/{csecret}/{akey}/{asecret}' \ '/{targets}/?{args}'.format( schema=self.secure_protocol[0], - ckey=NotifyTwitter.quote(self.ckey, safe=''), - asecret=NotifyTwitter.quote(self.csecret, safe=''), - akey=NotifyTwitter.quote(self.akey, safe=''), - csecret=NotifyTwitter.quote(self.asecret, safe=''), + ckey=self.pprint(self.ckey, privacy, safe=''), + csecret=self.pprint( + self.csecret, privacy, mode=PrivacyMode.Secret, safe=''), + akey=self.pprint(self.akey, privacy, safe=''), + asecret=self.pprint( + self.asecret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join( [NotifyTwitter.quote('@{}'.format(target), safe='') for target in self.targets]), diff --git a/apprise/plugins/NotifyWebexTeams.py b/apprise/plugins/NotifyWebexTeams.py index 687ff0b0..cc79c7ef 100644 --- a/apprise/plugins/NotifyWebexTeams.py +++ b/apprise/plugins/NotifyWebexTeams.py @@ -210,7 +210,7 @@ class NotifyWebexTeams(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -224,7 +224,7 @@ class NotifyWebexTeams(NotifyBase): return '{schema}://{token}/?{args}'.format( schema=self.secure_protocol, - token=NotifyWebexTeams.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), args=NotifyWebexTeams.urlencode(args), ) diff --git a/apprise/plugins/NotifyWindows.py b/apprise/plugins/NotifyWindows.py index 257324d3..50e7e60a 100644 --- a/apprise/plugins/NotifyWindows.py +++ b/apprise/plugins/NotifyWindows.py @@ -217,7 +217,7 @@ class NotifyWindows(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ diff --git a/apprise/plugins/NotifyXBMC.py b/apprise/plugins/NotifyXBMC.py index 3b29930b..d286ac60 100644 --- a/apprise/plugins/NotifyXBMC.py +++ b/apprise/plugins/NotifyXBMC.py @@ -27,6 +27,7 @@ import requests from json import dumps from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..common import NotifyImageSize from ..utils import parse_bool @@ -296,7 +297,7 @@ class NotifyXBMC(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -315,7 +316,8 @@ class NotifyXBMC(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyXBMC.quote(self.user, safe=''), - password=NotifyXBMC.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) elif self.user: auth = '{user}@'.format( @@ -327,7 +329,7 @@ class NotifyXBMC(NotifyBase): default_port = 443 if self.secure else self.xbmc_default_port if self.secure: # Append 's' to schema - default_schema + 's' + default_schema += 's' return '{schema}://{auth}{hostname}{port}/?{args}'.format( schema=default_schema, diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py index f262200b..5ff78a71 100644 --- a/apprise/plugins/NotifyXML.py +++ b/apprise/plugins/NotifyXML.py @@ -28,6 +28,7 @@ import six import requests from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyImageSize from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ @@ -138,7 +139,7 @@ class NotifyXML(NotifyBase): return - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -158,7 +159,8 @@ class NotifyXML(NotifyBase): if self.user and self.password: auth = '{user}:{password}@'.format( user=NotifyXML.quote(self.user, safe=''), - password=NotifyXML.quote(self.password, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) elif self.user: auth = '{user}@'.format( diff --git a/apprise/plugins/NotifyXMPP.py b/apprise/plugins/NotifyXMPP.py index 6fc82196..6d5ee4d4 100644 --- a/apprise/plugins/NotifyXMPP.py +++ b/apprise/plugins/NotifyXMPP.py @@ -28,6 +28,7 @@ import ssl from os.path import isfile from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import parse_list from ..AppriseLocale import gettext_lazy as _ @@ -344,7 +345,7 @@ class NotifyXMPP(NotifyBase): return True - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -374,12 +375,15 @@ class NotifyXMPP(NotifyBase): default_schema = self.secure_protocol if self.secure else self.protocol if self.user and self.password: - auth = '{}:{}'.format( - NotifyXMPP.quote(self.user, safe=''), - NotifyXMPP.quote(self.password, safe='')) + auth = '{user}:{password}'.format( + user=NotifyXMPP.quote(self.user, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, safe='')) else: - auth = self.password if self.password else self.user + auth = self.pprint( + self.password if self.password else self.user, privacy, + mode=PrivacyMode.Secret, safe='') return '{schema}://{auth}@{hostname}{port}/{jids}?{args}'.format( auth=auth, diff --git a/apprise/plugins/NotifyZulip.py b/apprise/plugins/NotifyZulip.py index 376f4cdc..93edd0ba 100644 --- a/apprise/plugins/NotifyZulip.py +++ b/apprise/plugins/NotifyZulip.py @@ -328,7 +328,7 @@ class NotifyZulip(NotifyBase): return not has_error - def url(self): + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. """ @@ -349,9 +349,9 @@ class NotifyZulip(NotifyBase): return '{schema}://{botname}@{org}/{token}/' \ '{targets}?{args}'.format( schema=self.secure_protocol, - botname=self.botname, + botname=NotifyZulip.quote(self.botname, safe=''), org=NotifyZulip.quote(organization, safe=''), - token=NotifyZulip.quote(self.token, safe=''), + token=self.pprint(self.token, privacy, safe=''), targets='/'.join( [NotifyZulip.quote(x, safe='') for x in self.targets]), args=NotifyZulip.urlencode(args), diff --git a/test/test_api.py b/test/test_api.py index a88df9f8..ffa37302 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -41,6 +41,8 @@ from apprise import NotifyType from apprise import NotifyFormat from apprise import NotifyImageSize from apprise import __version__ +from apprise import URLBase +from apprise import PrivacyMode from apprise.plugins import SCHEMA_MAP from apprise.plugins import __load_matrix @@ -359,6 +361,55 @@ def test_apprise(): 'host': 'localhost'}, suppress_exceptions=True) is None) assert(len(a) == 0) + # Privacy Print + # PrivacyMode.Secret always returns the same thing to avoid guessing + assert URLBase.pprint( + None, privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + 42, privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + object, privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + "", privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + "a", privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + "ab", privacy=True, mode=PrivacyMode.Secret) == '****' + assert URLBase.pprint( + "abcdefghijk", privacy=True, mode=PrivacyMode.Secret) == '****' + + # PrivacyMode.Outer + assert URLBase.pprint( + None, privacy=True, mode=PrivacyMode.Outer) == '' + assert URLBase.pprint( + 42, privacy=True, mode=PrivacyMode.Outer) == '' + assert URLBase.pprint( + object, privacy=True, mode=PrivacyMode.Outer) == '' + assert URLBase.pprint( + "", privacy=True, mode=PrivacyMode.Outer) == '' + assert URLBase.pprint( + "a", privacy=True, mode=PrivacyMode.Outer) == 'a...a' + assert URLBase.pprint( + "ab", privacy=True, mode=PrivacyMode.Outer) == 'a...b' + assert URLBase.pprint( + "abcdefghijk", privacy=True, mode=PrivacyMode.Outer) == 'a...k' + + # PrivacyMode.Tail + assert URLBase.pprint( + None, privacy=True, mode=PrivacyMode.Tail) == '' + assert URLBase.pprint( + 42, privacy=True, mode=PrivacyMode.Tail) == '' + assert URLBase.pprint( + object, privacy=True, mode=PrivacyMode.Tail) == '' + assert URLBase.pprint( + "", privacy=True, mode=PrivacyMode.Tail) == '' + assert URLBase.pprint( + "a", privacy=True, mode=PrivacyMode.Tail) == '...a' + assert URLBase.pprint( + "ab", privacy=True, mode=PrivacyMode.Tail) == '...ab' + assert URLBase.pprint( + "abcdefghijk", privacy=True, mode=PrivacyMode.Tail) == '...hijk' + @mock.patch('requests.get') @mock.patch('requests.post') diff --git a/test/test_email_plugin.py b/test/test_email_plugin.py index 3a1e7c0a..55444041 100644 --- a/test/test_email_plugin.py +++ b/test/test_email_plugin.py @@ -181,6 +181,8 @@ TEST_URLS = ( # STARTTLS flag checking ('mailtos://user:pass@gmail.com?mode=starttls', { 'instance': plugins.NotifyEmail, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'mailtos://user:****@gmail.com', }), # SSL flag checking ('mailtos://user:pass@gmail.com?mode=ssl', { @@ -189,6 +191,8 @@ TEST_URLS = ( # Can make a To address using what we have (l2g@nuxref.com) ('mailtos://nuxref.com?user=l2g&pass=.', { 'instance': plugins.NotifyEmail, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'mailtos://l2g:****@nuxref.com', }), ('mailto://user:pass@localhost:2525', { 'instance': plugins.NotifyEmail, @@ -221,6 +225,10 @@ def test_email_plugin(mock_smtp, mock_smtpssl): # Our expected Query response (True, False, or exception type) response = meta.get('response', True) + # Our expected privacy url + # Don't set this if don't need to check it's value + privacy_url = meta.get('privacy_url') + test_smtplib_exceptions = meta.get( 'test_smtplib_exceptions', False) @@ -274,6 +282,19 @@ def test_email_plugin(mock_smtp, mock_smtpssl): # We loaded okay; now lets make sure we can reverse this url assert(isinstance(obj.url(), six.string_types) is True) + # Test url() with privacy=True + assert(isinstance( + obj.url(privacy=True), six.string_types) is True) + + # Some Simple Invalid Instance Testing + assert instance.parse_url(None) is None + assert instance.parse_url(object) is None + assert instance.parse_url(42) is None + + if privacy_url: + # Assess that our privacy url is as expected + assert obj.url(privacy=True).startswith(privacy_url) + # Instantiate the exact same object again using the URL from # the one that was already created properly obj_cmp = Apprise.instantiate(obj.url()) diff --git a/test/test_growl_plugin.py b/test/test_growl_plugin.py index 06aa2a10..0325f79a 100644 --- a/test/test_growl_plugin.py +++ b/test/test_growl_plugin.py @@ -227,6 +227,10 @@ def test_growl_plugin(mock_gntp): # We loaded okay; now lets make sure we can reverse this url assert(isinstance(obj.url(), six.string_types) is True) + # Test our privacy=True flag + assert(isinstance( + obj.url(privacy=True), six.string_types) is True) + # Instantiate the exact same object again using the URL from # the one that was already created properly obj_cmp = Apprise.instantiate(obj.url()) diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py index 776f2d8d..c8ce4e67 100644 --- a/test/test_rest_plugins.py +++ b/test/test_rest_plugins.py @@ -85,6 +85,8 @@ TEST_URLS = ( ('boxcar://%s/%s' % ('a' * 64, 'b' * 64), { 'instance': plugins.NotifyBoxcar, 'requests_response_code': requests.codes.created, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'boxcar://a...a/****/', }), # Test without image set ('boxcar://%s/%s?image=True' % ('a' * 64, 'b' * 64), { @@ -151,6 +153,8 @@ TEST_URLS = ( ('clicksend://user:pass@{}?batch=yes&to={}'.format('3' * 14, '6' * 14), { # valid number but using the to= variable 'instance': plugins.NotifyClickSend, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'clicksend://user:****', }), ('clicksend://user:pass@{}?batch=no'.format('3' * 14), { # valid number - no batch @@ -187,6 +191,8 @@ TEST_URLS = ( ('d7sms://user:pass@{}?batch=yes'.format('3' * 14), { # valid number 'instance': plugins.NotifyD7Networks, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'd7sms://user:****@', }), ('d7sms://user:pass@{}?batch=yes'.format('7' * 14), { # valid number @@ -269,6 +275,9 @@ TEST_URLS = ( 'i' * 24, 't' * 64), { 'instance': plugins.NotifyDiscord, 'requests_response_code': requests.codes.no_content, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'discord://i...i/t...t/', }), ('discord://%s/%s?format=markdown&footer=Yes&thumbnail=No' % ( 'i' * 24, 't' * 64), { @@ -374,6 +383,9 @@ TEST_URLS = ( # tested very well using this matrix. It will resume in # in test_notify_emby_plugin() 'response': False, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'embys://l2g:****@localhost', }), # The rest of the emby tests are in test_notify_emby_plugin() @@ -386,6 +398,9 @@ TEST_URLS = ( # Auth Token specified ('faast://%s' % ('a' * 32), { 'instance': plugins.NotifyFaast, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'faast://a...a', }), ('faast://%s' % ('a' * 32), { 'instance': plugins.NotifyFaast, @@ -428,6 +443,9 @@ TEST_URLS = ( # Image handling ('flock://%s?image=True' % ('t' * 24), { 'instance': plugins.NotifyFlock, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'flock://t...t', }), ('flock://%s?image=False' % ('t' * 24), { 'instance': plugins.NotifyFlock, @@ -558,6 +576,9 @@ TEST_URLS = ( ('gitter://%s/apprise?image=Yes' % ('a' * 40), { 'instance': plugins.NotifyGitter, 'response': False, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'gitter://a...a/apprise', }), # Don't include image in post (this is the default anyway) ('gitter://%s/apprise?image=No' % ('a' * 40), { @@ -596,6 +617,9 @@ TEST_URLS = ( # Provide a hostname and token ('gotify://hostname/%s' % ('t' * 16), { 'instance': plugins.NotifyGotify, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'gotify://hostname/t...t', }), # Provide a priority ('gotify://hostname/%s?priority=high' % ('i' * 16), { @@ -644,6 +668,9 @@ TEST_URLS = ( # A nicely formed ifttt url with 1 event and a new key/value store ('ifttt://WebHookID@EventID/?+TemplateKey=TemplateVal', { 'instance': plugins.NotifyIFTTT, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'ifttt://W...D', }), # Test to= in which case we set the host to the webhook id ('ifttt://WebHookID?to=EventID,EventID2', { @@ -703,6 +730,9 @@ TEST_URLS = ( # APIKey + device (using to=) ('join://%s?to=%s' % ('a' * 32, 'd' * 32), { 'instance': plugins.NotifyJoin, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'join://a...a/', }), # APIKey + device ('join://%s@%s?image=True' % ('a' * 32, 'd' * 32), { @@ -770,6 +800,9 @@ TEST_URLS = ( }), ('json://user:pass@localhost', { 'instance': plugins.NotifyJSON, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'json://user:****@localhost', }), ('json://user@localhost', { 'instance': plugins.NotifyJSON, @@ -789,8 +822,11 @@ TEST_URLS = ( ('jsons://localhost:8080/path/', { 'instance': plugins.NotifyJSON, }), - ('jsons://user:pass@localhost:8080', { + ('jsons://user:password@localhost:8080', { 'instance': plugins.NotifyJSON, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'jsons://user:****@localhost:8080', }), ('json://:@/', { 'instance': None, @@ -817,7 +853,6 @@ TEST_URLS = ( 'instance': plugins.NotifyJSON, }), - ################################## # NotifyKODI ################################## @@ -832,6 +867,9 @@ TEST_URLS = ( }), ('kodi://user:pass@localhost', { 'instance': plugins.NotifyXBMC, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'kodi://user:****@localhost', }), ('kodi://localhost:8080', { 'instance': plugins.NotifyXBMC, @@ -848,8 +886,11 @@ TEST_URLS = ( ('kodis://localhost:8080/path/', { 'instance': plugins.NotifyXBMC, }), - ('kodis://user:pass@localhost:8080', { + ('kodis://user:password@localhost:8080', { 'instance': plugins.NotifyXBMC, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'kodis://user:****@localhost:8080', }), ('kodi://localhost', { 'instance': plugins.NotifyXBMC, @@ -917,18 +958,27 @@ TEST_URLS = ( ('kumulos://{}/{}/'.format(UUID4, 'w' * 36), { # Everything is okay 'instance': plugins.NotifyKumulos, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'kumulos://8...2/w...w/', }), ('kumulos://{}/{}/'.format(UUID4, 'x' * 36), { 'instance': plugins.NotifyKumulos, # force a failure 'response': False, 'requests_response_code': requests.codes.internal_server_error, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'kumulos://8...2/x...x/', }), ('kumulos://{}/{}/'.format(UUID4, 'y' * 36), { 'instance': plugins.NotifyKumulos, # throw a bizzare code forcing us to fail to look it up 'response': False, 'requests_response_code': 999, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'kumulos://8...2/y...y/', }), ('kumulos://{}/{}/'.format(UUID4, 'z' * 36), { 'instance': plugins.NotifyKumulos, @@ -1041,6 +1091,9 @@ TEST_URLS = ( # Throws a series of connection and transfer exceptions when this flag # is set and tests that we gracfully handle them 'test_requests_exceptions': True, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'matrix://user:****@localhost:1234/', }), # Matrix supports webhooks too; the following tests this now: @@ -1134,6 +1187,9 @@ TEST_URLS = ( }), ('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', { 'instance': plugins.NotifyMatterMost, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'mmost://user@localhost/3...4/', }), ('mmost://localhost/3ccdd113474722377935511fc85d3dd4' '?to=test&image=True', { @@ -1148,6 +1204,9 @@ TEST_URLS = ( 'include_image': False}), ('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', { 'instance': plugins.NotifyMatterMost, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'mmost://localhost:8080/3...4/', }), ('mmost://localhost:0/3ccdd113474722377935511fc85d3dd4', { 'instance': plugins.NotifyMatterMost, @@ -1251,6 +1310,9 @@ TEST_URLS = ( ('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'a' * 32, UUID4), { # All tokens provided - we're good no image 'instance': plugins.NotifyMSTeams, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'msteams://8...2/a...a/8...2/', }), ('msteams://{}@{}/{}/{}?tx'.format(UUID4, UUID4, 'a' * 32, UUID4), { 'instance': plugins.NotifyMSTeams, @@ -1271,6 +1333,82 @@ TEST_URLS = ( 'test_requests_exceptions': True, }), + ################################## + # NotifyNexmo + ################################## + ('nexmo://', { + # No API Key specified + 'instance': TypeError, + }), + ('nexmo://:@/', { + # invalid Auth key + 'instance': TypeError, + }), + ('nexmo://{}@12345678'.format('a' * 8), { + # Just a key provided + 'instance': TypeError, + }), + ('nexmo://{}:{}@_'.format('a' * 8, 'b' * 16), { + # key and secret provided but invalid from + 'instance': TypeError, + }), + ('nexmo://{}:{}@{}'.format('a' * 23, 'b' * 16, '1' * 11), { + # key invalid and secret + 'instance': TypeError, + }), + ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 2, '2' * 11), { + # key and invalid secret + 'instance': TypeError, + }), + ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '3' * 9), { + # key and secret provided and from but invalid from no + 'instance': TypeError, + }), + ('nexmo://{}:{}@{}/?ttl=0'.format('a' * 8, 'b' * 16, '3' * 11), { + # Invalid ttl defined + 'instance': TypeError, + }), + ('nexmo://{}:{}@{}/123/{}/abcd/'.format( + 'a' * 8, 'b' * 16, '3' * 11, '9' * 15), { + # valid everything but target numbers + 'instance': plugins.NotifyNexmo, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'nexmo://a...a:****@', + }), + ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '5' * 11), { + # using phone no with no target - we text ourselves in + # this case + 'instance': plugins.NotifyNexmo, + }), + ('nexmo://_?key={}&secret={}&from={}'.format( + 'a' * 8, 'b' * 16, '5' * 11), { + # use get args to acomplish the same thing + 'instance': plugins.NotifyNexmo, + }), + ('nexmo://_?key={}&secret={}&source={}'.format( + 'a' * 8, 'b' * 16, '5' * 11), { + # use get args to acomplish the same thing (use source instead of from) + 'instance': plugins.NotifyNexmo, + }), + ('nexmo://_?key={}&secret={}&from={}&to={}'.format( + 'a' * 8, 'b' * 16, '5' * 11, '7' * 13), { + # use to= + 'instance': plugins.NotifyNexmo, + }), + ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), { + 'instance': plugins.NotifyNexmo, + # throw a bizzare code forcing us to fail to look it up + 'response': False, + 'requests_response_code': 999, + }), + ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), { + 'instance': plugins.NotifyNexmo, + # Throws a series of connection and transfer exceptions when this flag + # is set and tests that we gracfully handle them + 'test_requests_exceptions': True, + }), + ################################## # NotifyProwl ################################## @@ -1308,12 +1446,18 @@ TEST_URLS = ( 'instance': TypeError, }), # APIKey + No Provider Key (empty) - ('prowl://%s///' % ('a' * 40), { + ('prowl://%s///' % ('w' * 40), { 'instance': plugins.NotifyProwl, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'prowl://w...w/', }), # APIKey + Provider Key ('prowl://%s/%s' % ('a' * 40, 'b' * 40), { 'instance': plugins.NotifyProwl, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'prowl://a...a/b...b', }), # APIKey + with image ('prowl://%s' % ('a' * 40), { @@ -1363,6 +1507,9 @@ TEST_URLS = ( # APIKey + 2 channels ('pbul://%s/#channel1/#channel2' % ('a' * 32), { 'instance': plugins.NotifyPushBullet, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'pbul://a...a/', }), # APIKey + device ('pbul://%s/device/' % ('a' * 32), { @@ -1439,6 +1586,9 @@ TEST_URLS = ( # APIkey ('push://%s' % UUID4, { 'instance': plugins.NotifyTechulusPush, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'push://8...2/', }), # APIKey + bad url ('push://:@/', { @@ -1488,6 +1638,9 @@ TEST_URLS = ( # Application Key+Secret + dropped entry ('pushed://%s/%s/dropped/' % ('a' * 32, 'a' * 64), { 'instance': plugins.NotifyPushed, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'pushed://a...a/****/', }), # Application Key+Secret + 2 channels ('pushed://%s/%s/#channel1/#channel2' % ('a' * 32, 'a' * 64), { @@ -1589,6 +1742,9 @@ TEST_URLS = ( # Specify your own server with login (secret= MUST be provided) ('pjet://user:pass@localhost?secret=%s' % ('a' * 32), { 'instance': plugins.NotifyPushjet, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'pjet://user:****@localhost', }), # Specify your own server with login (no secret = fail normally) # however this will work since we're providing depricated support @@ -1662,6 +1818,9 @@ TEST_URLS = ( # APIKey + Valid User + 2 Devices ('pover://%s@%s/DEVICE1/DEVICE2/' % ('u' * 30, 'a' * 30), { 'instance': plugins.NotifyPushover, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'pover://u...u@a...a', }), # APIKey + Valid User + invalid device ('pover://%s@%s/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), { @@ -1850,6 +2009,8 @@ TEST_URLS = ( 'userId': 'user', }, }, + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'rocket://user:****@localhost', }), # A user/pass where the pass matches a webtoken # to ensure we get the right mode, we enforce basic mode @@ -1891,6 +2052,9 @@ TEST_URLS = ( ('rockets://web/token@localhost/?avatar=No', { # a simple webhook token with default values 'instance': plugins.NotifyRocketChat, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'rockets://w...n@localhost', }), ('rockets://localhost/@user/?mode=webhook&webhook=web/token', { 'instance': plugins.NotifyRocketChat, @@ -1975,6 +2139,9 @@ TEST_URLS = ( # No username specified; this is still okay as we use whatever # the user told the webhook to use; set our ryver mode 'instance': plugins.NotifyRyver, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'ryver://apprise/c...G', }), # Support Native URLs ('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG', { @@ -2058,6 +2225,9 @@ TEST_URLS = ( '?template={}&+sub=value&+sub2=value2'.format(UUID4), { # A good email with a template + substitutions 'instance': plugins.NotifySendGrid, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'sendgrid://a...d:user@example.com/', }), ('sendgrid://abcd:user@example.ca/newuser@example.ca', { 'instance': plugins.NotifySendGrid, @@ -2092,13 +2262,16 @@ TEST_URLS = ( # Expected notify() response 'notify_response': False, }), - ('spush://{}'.format('X' * 14), { + ('spush://{}'.format('Y' * 14), { # API Key valid and expected response was valid 'instance': plugins.NotifySimplePush, # Set our response to OK 'requests_response_text': { 'status': 'OK', }, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'spush://Y...Y/', }), ('spush://{}?event=Not%20So%20Good'.format('X' * 14), { # API Key valid and expected response was valid @@ -2117,6 +2290,9 @@ TEST_URLS = ( 'requests_response_text': { 'status': 'OK', }, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'spush://****:****@X...X/', }), ('spush://{}'.format('Y' * 14), { 'instance': plugins.NotifySimplePush, @@ -2176,6 +2352,9 @@ TEST_URLS = ( ('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/' \ '?to=#nuxref', { 'instance': plugins.NotifySlack, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'slack://username@T...2/A...D/T...Q/', }), ('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', { 'instance': plugins.NotifySlack, @@ -2248,6 +2427,9 @@ TEST_URLS = ( ('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1', { # Missing a topic and/or phone No 'instance': plugins.NotifySNS, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'sns://T...2/****/us-east-1', }), ('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1' \ '?to=12223334444', { @@ -2453,6 +2635,9 @@ TEST_URLS = ( ('twilio://AC{}:{}@12345/{}'.format('a' * 32, 'b' * 32, '4' * 11), { # using short-code (5 characters) 'instance': plugins.NotifyTwilio, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'twilio://...aaaa:b...b@12345', }), ('twilio://AC{}:{}@123456/{}'.format('a' * 32, 'b' * 32, '4' * 11), { # using short-code (6 characters) @@ -2518,6 +2703,9 @@ TEST_URLS = ( # Expected notify() response is False because internally we would # have failed to login 'notify_response': False, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'twist://****:user1@example.com', }), ('twist://password:user2@example.com', { # password:login acceptable @@ -2572,6 +2760,9 @@ TEST_URLS = ( # Expected notify() response False (because we won't be able # to detect our user) 'notify_response': False, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'twitter://c...y/****/a...n/****', }), ('twitter://consumer_key/consumer_secret/access_token/access_secret' '?cache=no', { @@ -2743,6 +2934,9 @@ TEST_URLS = ( ('msg91://{}/15551232000'.format('a' * 23), { # a valid message 'instance': plugins.NotifyMSG91, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'msg91://a...a/15551232000', }), ('msg91://{}/?to=15551232000'.format('a' * 23), { # a valid message @@ -2796,6 +2990,9 @@ TEST_URLS = ( ('msgbird://{}/15551232000/abcd'.format('a' * 25), { # invalid target phone number; we fall back to texting ourselves 'instance': plugins.NotifyMessageBird, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'msgbird://a...a/15551232000', }), ('msgbird://{}/15551232000/123'.format('a' * 25), { # invalid target phone number; we fall back to texting ourselves @@ -2824,79 +3021,6 @@ TEST_URLS = ( 'test_requests_exceptions': True, }), - ################################## - # NotifyNexmo - ################################## - ('nexmo://', { - # No API Key specified - 'instance': TypeError, - }), - ('nexmo://:@/', { - # invalid Auth key - 'instance': TypeError, - }), - ('nexmo://{}@12345678'.format('a' * 8), { - # Just a key provided - 'instance': TypeError, - }), - ('nexmo://{}:{}@_'.format('a' * 8, 'b' * 16), { - # key and secret provided but invalid from - 'instance': TypeError, - }), - ('nexmo://{}:{}@{}'.format('a' * 23, 'b' * 16, '1' * 11), { - # key invalid and secret - 'instance': TypeError, - }), - ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 2, '2' * 11), { - # key and invalid secret - 'instance': TypeError, - }), - ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '3' * 9), { - # key and secret provided and from but invalid from no - 'instance': TypeError, - }), - ('nexmo://{}:{}@{}/?ttl=0'.format('a' * 8, 'b' * 16, '3' * 11), { - # Invalid ttl defined - 'instance': TypeError, - }), - ('nexmo://{}:{}@{}/123/{}/abcd/'.format( - 'a' * 8, 'b' * 16, '3' * 11, '9' * 15), { - # valid everything but target numbers - 'instance': plugins.NotifyNexmo, - }), - ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '5' * 11), { - # using phone no with no target - we text ourselves in - # this case - 'instance': plugins.NotifyNexmo, - }), - ('nexmo://_?key={}&secret={}&from={}'.format( - 'a' * 8, 'b' * 16, '5' * 11), { - # use get args to acomplish the same thing - 'instance': plugins.NotifyNexmo, - }), - ('nexmo://_?key={}&secret={}&source={}'.format( - 'a' * 8, 'b' * 16, '5' * 11), { - # use get args to acomplish the same thing (use source instead of from) - 'instance': plugins.NotifyNexmo, - }), - ('nexmo://_?key={}&secret={}&from={}&to={}'.format( - 'a' * 8, 'b' * 16, '5' * 11, '7' * 13), { - # use to= - 'instance': plugins.NotifyNexmo, - }), - ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), { - 'instance': plugins.NotifyNexmo, - # throw a bizzare code forcing us to fail to look it up - 'response': False, - 'requests_response_code': 999, - }), - ('nexmo://{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), { - 'instance': plugins.NotifyNexmo, - # Throws a series of connection and transfer exceptions when this flag - # is set and tests that we gracfully handle them - 'test_requests_exceptions': True, - }), - ################################## # NotifyWebexTeams ################################## @@ -2917,6 +3041,9 @@ TEST_URLS = ( ('wxteams://{}'.format('a' * 80), { # token provided - we're good 'instance': plugins.NotifyWebexTeams, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'wxteams://a...a/', }), # Support Native URLs ('https://api.ciscospark.com/v1/webhooks/incoming/{}'.format('a' * 80), { @@ -3023,6 +3150,9 @@ TEST_URLS = ( }), ('xml://user:pass@localhost', { 'instance': plugins.NotifyXML, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'xml://user:****@localhost', }), ('xml://localhost:8080', { 'instance': plugins.NotifyXML, @@ -3035,6 +3165,9 @@ TEST_URLS = ( }), ('xmls://user:pass@localhost', { 'instance': plugins.NotifyXML, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'xmls://user:****@localhost', }), ('xmls://localhost:8080/path/', { 'instance': plugins.NotifyXML, @@ -3095,6 +3228,9 @@ TEST_URLS = ( # Valid everything - no target so default is used ('zulip://botname@apprise/{}'.format('a' * 32), { 'instance': plugins.NotifyZulip, + + # Our expected url(privacy=True) startswith() response: + 'privacy_url': 'zulip://botname@apprise/a...a/', }), # Valid everything - organization as hostname ('zulip://botname@apprise.zulipchat.com/{}'.format('a' * 32), { @@ -3177,6 +3313,10 @@ def test_rest_plugins(mock_post, mock_get): # Our expected Notify response (True or False) notify_response = meta.get('notify_response', response) + # Our expected privacy url + # Don't set this if don't need to check it's value + privacy_url = meta.get('privacy_url') + # Allow us to force the server response code to be something other then # the defaults requests_response_code = meta.get( @@ -3257,11 +3397,19 @@ def test_rest_plugins(mock_post, mock_get): # We loaded okay; now lets make sure we can reverse this url assert isinstance(obj.url(), six.string_types) is True + # Test url() with privacy=True + assert isinstance( + obj.url(privacy=True), six.string_types) is True + # Some Simple Invalid Instance Testing assert instance.parse_url(None) is None assert instance.parse_url(object) is None assert instance.parse_url(42) is None + if privacy_url: + # Assess that our privacy url is as expected + assert obj.url(privacy=True).startswith(privacy_url) + # Instantiate the exact same object again using the URL from # the one that was already created properly obj_cmp = Apprise.instantiate(obj.url()) @@ -4798,6 +4946,11 @@ def test_notify_telegram_plugin(mock_post, mock_get): # test url call assert isinstance(obj.url(), six.string_types) is True + + # test privacy version of url + assert isinstance(obj.url(privacy=True), six.string_types) is True + assert obj.url(privacy=True).startswith('tgram://1...p/') is True + # Test that we can load the string we generate back: obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url())) assert isinstance(obj, plugins.NotifyTelegram) is True diff --git a/test/test_xmpp_plugin.py b/test/test_xmpp_plugin.py index 84586958..87fbbb02 100644 --- a/test/test_xmpp_plugin.py +++ b/test/test_xmpp_plugin.py @@ -162,16 +162,41 @@ def test_xmpp_plugin(tmpdir): # Restore settings as they were del ssl.PROTOCOL_TLS + urls = ( + { + 'u': 'xmpps://user:pass@example.com', + 'p': 'xmpps://user:****@example.com', + }, { + 'u': 'xmpps://user:pass@example.com?' + 'xep=30,199,garbage,xep_99999999', + 'p': 'xmpps://user:****@example.com', + }, { + 'u': 'xmpps://user:pass@example.com?xep=ignored', + 'p': 'xmpps://user:****@example.com', + }, { + 'u': 'xmpps://pass@example.com/' + 'user@test.com, user2@test.com/resource', + 'p': 'xmpps://****@example.com', + }, { + 'u': 'xmpps://pass@example.com:5226?jid=user@test.com', + 'p': 'xmpps://****@example.com:5226', + }, { + 'u': 'xmpps://pass@example.com?jid=user@test.com&verify=False', + 'p': 'xmpps://****@example.com', + }, { + 'u': 'xmpps://user:pass@example.com?verify=False', + 'p': 'xmpps://user:****@example.com', + }, { + 'u': 'xmpp://user:pass@example.com?to=user@test.com', + 'p': 'xmpp://user:****@example.com', + } + ) + # Try Different Variations of our URL - for url in ( - 'xmpps://user:pass@example.com', - 'xmpps://user:pass@example.com?xep=30,199,garbage,xep_99999999', - 'xmpps://user:pass@example.com?xep=ignored', - 'xmpps://pass@example.com/user@test.com, user2@test.com/resource', - 'xmpps://pass@example.com:5226?jid=user@test.com', - 'xmpps://pass@example.com?jid=user@test.com&verify=False', - 'xmpps://user:pass@example.com?verify=False', - 'xmpp://user:pass@example.com?to=user@test.com'): + for entry in urls: + + url = entry['u'] + privacy_url = entry['p'] obj = apprise.Apprise.instantiate(url, suppress_exceptions=False) @@ -184,6 +209,11 @@ def test_xmpp_plugin(tmpdir): # Test url() call assert isinstance(obj.url(), six.string_types) is True + # Test url(privacy=True) call + assert isinstance(obj.url(privacy=True), six.string_types) is True + + assert obj.url(privacy=True).startswith(privacy_url) + # test notifications assert obj.notify( title='title', body='body',