url() supports privacy flag for masking pwds, tokens, apikeys, etc (#156)

This commit is contained in:
Chris Caron 2019-09-29 17:17:25 -04:00 committed by GitHub
parent 1047f36c6e
commit 1d84f7fd8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 595 additions and 222 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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