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 # Used to break a path list into parts
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') 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 # Define the HTML Lookup Table
HTML_LOOKUP = { HTML_LOOKUP = {
400: 'Bad Request - Unsupported Parameters.', 400: 'Bad Request - Unsupported Parameters.',
@ -183,7 +198,7 @@ class URLBase(object):
self._last_io_datetime = datetime.now() self._last_io_datetime = datetime.now()
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Assembles the URL associated with the notification based on the Assembles the URL associated with the notification based on the
arguments provied. arguments provied.
@ -302,6 +317,44 @@ class URLBase(object):
# Python v2.7 # Python v2.7
return _quote(content, safe=safe) 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 @staticmethod
def urlencode(query, doseq=False, safe='', encoding=None, errors=None): def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
"""Convert a mapping object or a sequence of two-element tuples """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 .common import CONFIG_FORMATS
from .URLBase import URLBase from .URLBase import URLBase
from .URLBase import PrivacyMode
from .plugins.NotifyBase import NotifyBase from .plugins.NotifyBase import NotifyBase
from .config.ConfigBase import ConfigBase from .config.ConfigBase import ConfigBase
@ -63,5 +64,5 @@ __all__ = [
# Reference # Reference
'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'OverflowMode', 'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'OverflowMode',
'NOTIFY_TYPES', 'NOTIFY_IMAGE_SIZES', 'NOTIFY_FORMATS', 'OVERFLOW_MODES', '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 return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """

View File

@ -28,6 +28,7 @@ import six
import requests import requests
from .ConfigBase import ConfigBase from .ConfigBase import ConfigBase
from ..common import ConfigFormat from ..common import ConfigFormat
from ..URLBase import PrivacyMode
# Support YAML formats # Support YAML formats
# text/yaml # text/yaml
@ -89,7 +90,7 @@ class ConfigHTTP(ConfigBase):
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -111,7 +112,8 @@ class ConfigHTTP(ConfigBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=self.quote(self.user, safe=''), 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: elif self.user:
auth = '{user}@'.format( auth = '{user}@'.format(

View File

@ -38,6 +38,7 @@ except ImportError:
from urllib.parse import urlparse from urllib.parse import urlparse
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..utils import parse_bool from ..utils import parse_bool
from ..common import NotifyType from ..common import NotifyType
from ..common import NotifyImageSize from ..common import NotifyImageSize
@ -330,7 +331,7 @@ class NotifyBoxcar(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -343,10 +344,11 @@ class NotifyBoxcar(NotifyBase):
'verify': 'yes' if self.verify_certificate else 'no', '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, schema=self.secure_protocol,
access=NotifyBoxcar.quote(self.access, safe=''), access=self.pprint(self.access, privacy, safe=''),
secret=NotifyBoxcar.quote(self.secret, safe=''), secret=self.pprint(
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
targets='/'.join([ targets='/'.join([
NotifyBoxcar.quote(x, safe='') for x in chain( NotifyBoxcar.quote(x, safe='') for x in chain(
self.tags, self.device_tokens) if x != DEFAULT_TAG]), self.tags, self.device_tokens) if x != DEFAULT_TAG]),

View File

@ -42,6 +42,7 @@ from json import dumps
from base64 import b64encode from base64 import b64encode
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..utils import parse_bool from ..utils import parse_bool
@ -265,7 +266,7 @@ class NotifyClickSend(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -281,7 +282,8 @@ class NotifyClickSend(NotifyBase):
# Setup Authentication # Setup Authentication
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyClickSend.quote(self.user, safe=''), 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( return '{schema}://{auth}{targets}?{args}'.format(

View File

@ -38,6 +38,7 @@ from json import dumps
from json import loads from json import loads
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..utils import parse_bool from ..utils import parse_bool
@ -380,7 +381,7 @@ class NotifyD7Networks(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -402,7 +403,8 @@ class NotifyD7Networks(NotifyBase):
return '{schema}://{user}:{password}@{targets}/?{args}'.format( return '{schema}://{user}:{password}@{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
user=NotifyD7Networks.quote(self.user, safe=''), user=NotifyD7Networks.quote(self.user, safe=''),
password=NotifyD7Networks.quote(self.password, safe=''), password=self.pprint(
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
targets='/'.join( targets='/'.join(
[NotifyD7Networks.quote(x, safe='') for x in self.targets]), [NotifyD7Networks.quote(x, safe='') for x in self.targets]),
args=NotifyD7Networks.urlencode(args)) args=NotifyD7Networks.urlencode(args))

View File

@ -332,7 +332,7 @@ class NotifyDBus(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """

View File

@ -309,7 +309,7 @@ class NotifyDiscord(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -328,8 +328,8 @@ class NotifyDiscord(NotifyBase):
return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format( return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
webhook_id=NotifyDiscord.quote(self.webhook_id, safe=''), webhook_id=self.pprint(self.webhook_id, privacy, safe=''),
webhook_token=NotifyDiscord.quote(self.webhook_token, safe=''), webhook_token=self.pprint(self.webhook_token, privacy, safe=''),
args=NotifyDiscord.urlencode(args), args=NotifyDiscord.urlencode(args),
) )

View File

@ -31,6 +31,7 @@ from socket import error as SocketError
from datetime import datetime from datetime import datetime
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyFormat from ..common import NotifyFormat
from ..common import NotifyType from ..common import NotifyType
from ..utils import is_email from ..utils import is_email
@ -618,7 +619,7 @@ class NotifyEmail(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -652,7 +653,8 @@ class NotifyEmail(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyEmail.quote(user, safe=''), user=NotifyEmail.quote(user, safe=''),
password=NotifyEmail.quote(self.password, safe=''), password=self.pprint(
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
) )
else: else:
# user url # user url

View File

@ -35,6 +35,7 @@ from json import dumps
from json import loads from json import loads
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..utils import parse_bool from ..utils import parse_bool
from ..common import NotifyType from ..common import NotifyType
from .. import __version__ as VERSION from .. import __version__ as VERSION
@ -581,7 +582,7 @@ class NotifyEmby(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -599,7 +600,8 @@ class NotifyEmby(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyEmby.quote(self.user, safe=''), 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 else: # self.user is set
auth = '{user}@'.format( auth = '{user}@'.format(

View File

@ -161,7 +161,7 @@ class NotifyFaast(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -176,7 +176,7 @@ class NotifyFaast(NotifyBase):
return '{schema}://{authtoken}/?{args}'.format( return '{schema}://{authtoken}/?{args}'.format(
schema=self.protocol, schema=self.protocol,
authtoken=NotifyFaast.quote(self.authtoken, safe=''), authtoken=self.pprint(self.authtoken, privacy, safe=''),
args=NotifyFaast.urlencode(args), args=NotifyFaast.urlencode(args),
) )

View File

@ -305,7 +305,7 @@ class NotifyFlock(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -320,7 +320,7 @@ class NotifyFlock(NotifyBase):
return '{schema}://{token}/{targets}?{args}'\ return '{schema}://{token}/{targets}?{args}'\
.format( .format(
schema=self.secure_protocol, schema=self.secure_protocol,
token=NotifyFlock.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyFlock.quote(target, safe='') [NotifyFlock.quote(target, safe='')
for target in self.targets]), for target in self.targets]),

View File

@ -367,7 +367,7 @@ class NotifyGitter(NotifyBase):
return (True, content) return (True, content)
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -382,7 +382,7 @@ class NotifyGitter(NotifyBase):
return '{schema}://{token}/{targets}/?{args}'.format( return '{schema}://{token}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
token=NotifyGitter.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyGitter.quote(x, safe='') for x in self.targets]), [NotifyGitter.quote(x, safe='') for x in self.targets]),
args=NotifyGitter.urlencode(args)) args=NotifyGitter.urlencode(args))

View File

@ -201,7 +201,7 @@ class NotifyGnome(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """

View File

@ -223,7 +223,7 @@ class NotifyGotify(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -243,7 +243,7 @@ class NotifyGotify(NotifyBase):
hostname=NotifyGotify.quote(self.host, safe=''), hostname=NotifyGotify.quote(self.host, safe=''),
port='' if self.port is None or self.port == default_port port='' if self.port is None or self.port == default_port
else ':{}'.format(self.port), else ':{}'.format(self.port),
token=NotifyGotify.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
args=NotifyGotify.urlencode(args), args=NotifyGotify.urlencode(args),
) )

View File

@ -26,6 +26,7 @@
from .gntp import notifier from .gntp import notifier
from .gntp import errors from .gntp import errors
from ..NotifyBase import NotifyBase from ..NotifyBase import NotifyBase
from ...URLBase import PrivacyMode
from ...common import NotifyImageSize from ...common import NotifyImageSize
from ...common import NotifyType from ...common import NotifyType
from ...utils import parse_bool from ...utils import parse_bool
@ -88,26 +89,32 @@ class NotifyGrowl(NotifyBase):
# Default Growl Port # Default Growl Port
default_port = 23053 default_port = 23053
# Define object templates
# Define object templates # Define object templates
templates = ( templates = (
'{schema}://{apikey}', '{schema}://{host}',
'{schema}://{apikey}/{providerkey}', '{schema}://{host}:{port}',
'{schema}://{password}@{host}',
'{schema}://{password}@{host}:{port}',
) )
# Define our template tokens # Define our template tokens
template_tokens = dict(NotifyBase.template_tokens, **{ template_tokens = dict(NotifyBase.template_tokens, **{
'apikey': { 'host': {
'name': _('API Key'), 'name': _('Hostname'),
'type': 'string', 'type': 'string',
'private': True,
'required': True, 'required': True,
'map_to': 'host',
}, },
'providerkey': { 'port': {
'name': _('Provider Key'), 'name': _('Port'),
'type': 'int',
'min': 1,
'max': 65535,
},
'password': {
'name': _('Password'),
'type': 'string', 'type': 'string',
'private': True, 'private': True,
'map_to': 'fullpath',
}, },
}) })
@ -262,7 +269,7 @@ class NotifyGrowl(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -291,7 +298,8 @@ class NotifyGrowl(NotifyBase):
if self.user: if self.user:
# The growl password is stored in the user field # The growl password is stored in the user field
auth = '{password}@'.format( 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( return '{schema}://{auth}{hostname}{port}/?{args}'.format(

View File

@ -285,7 +285,7 @@ class NotifyIFTTT(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -303,7 +303,7 @@ class NotifyIFTTT(NotifyBase):
return '{schema}://{webhook_id}@{events}/?{args}'.format( return '{schema}://{webhook_id}@{events}/?{args}'.format(
schema=self.secure_protocol, 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='') events='/'.join([NotifyIFTTT.quote(x, safe='')
for x in self.events]), for x in self.events]),
args=NotifyIFTTT.urlencode(args), args=NotifyIFTTT.urlencode(args),

View File

@ -28,6 +28,7 @@ import requests
from json import dumps from json import dumps
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..common import NotifyType from ..common import NotifyType
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -120,7 +121,7 @@ class NotifyJSON(NotifyBase):
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -140,7 +141,8 @@ class NotifyJSON(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyJSON.quote(self.user, safe=''), 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: elif self.user:
auth = '{user}@'.format( auth = '{user}@'.format(

View File

@ -270,7 +270,7 @@ class NotifyJoin(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -285,7 +285,7 @@ class NotifyJoin(NotifyBase):
return '{schema}://{apikey}/{devices}/?{args}'.format( return '{schema}://{apikey}/{devices}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
apikey=NotifyJoin.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
devices='/'.join([NotifyJoin.quote(x, safe='') devices='/'.join([NotifyJoin.quote(x, safe='')
for x in self.devices]), for x in self.devices]),
args=NotifyJoin.urlencode(args)) args=NotifyJoin.urlencode(args))

View File

@ -214,7 +214,7 @@ class NotifyKumulos(NotifyBase):
return False return False
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -228,8 +228,8 @@ class NotifyKumulos(NotifyBase):
return '{schema}://{apikey}/{serverkey}/?{args}'.format( return '{schema}://{apikey}/{serverkey}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
apikey=NotifyKumulos.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
serverkey=NotifyKumulos.quote(self.serverkey, safe=''), serverkey=self.pprint(self.serverkey, privacy, safe=''),
args=NotifyKumulos.urlencode(args), args=NotifyKumulos.urlencode(args),
) )

View File

@ -316,7 +316,7 @@ class NotifyMSG91(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -334,7 +334,7 @@ class NotifyMSG91(NotifyBase):
return '{schema}://{authkey}/{targets}/?{args}'.format( return '{schema}://{authkey}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
authkey=self.authkey, authkey=self.pprint(self.authkey, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyMSG91.quote(x, safe='') for x in self.targets]), [NotifyMSG91.quote(x, safe='') for x in self.targets]),
args=NotifyMSG91.urlencode(args)) args=NotifyMSG91.urlencode(args))

View File

@ -293,7 +293,7 @@ class NotifyMSTeams(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -309,9 +309,9 @@ class NotifyMSTeams(NotifyBase):
return '{schema}://{token_a}/{token_b}/{token_c}/'\ return '{schema}://{token_a}/{token_b}/{token_c}/'\
'?{args}'.format( '?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
token_a=NotifyMSTeams.quote(self.token_a, safe=''), token_a=self.pprint(self.token_a, privacy, safe=''),
token_b=NotifyMSTeams.quote(self.token_b, safe=''), token_b=self.pprint(self.token_b, privacy, safe=''),
token_c=NotifyMSTeams.quote(self.token_c, safe=''), token_c=self.pprint(self.token_c, privacy, safe=''),
args=NotifyMSTeams.urlencode(args), args=NotifyMSTeams.urlencode(args),
) )

View File

@ -310,7 +310,7 @@ class NotifyMailgun(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -331,7 +331,7 @@ class NotifyMailgun(NotifyBase):
schema=self.secure_protocol, schema=self.secure_protocol,
host=self.host, host=self.host,
user=NotifyMailgun.quote(self.user, safe=''), user=NotifyMailgun.quote(self.user, safe=''),
apikey=NotifyMailgun.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyMailgun.quote(x, safe='') for x in self.targets]), [NotifyMailgun.quote(x, safe='') for x in self.targets]),
args=NotifyMailgun.urlencode(args)) args=NotifyMailgun.urlencode(args))

View File

@ -35,6 +35,7 @@ from json import loads
from time import time from time import time
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..common import NotifyFormat from ..common import NotifyFormat
@ -946,7 +947,7 @@ class NotifyMatrix(NotifyBase):
""" """
self._logout() self._logout()
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -965,7 +966,8 @@ class NotifyMatrix(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyMatrix.quote(self.user, safe=''), 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: elif self.user:

View File

@ -280,7 +280,7 @@ class NotifyMatterMost(NotifyBase):
# Return our overall status # Return our overall status
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. 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_port = 443 if self.secure else self.default_port
default_schema = self.secure_protocol if self.secure else self.protocol 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 \ return \
'{schema}://{hostname}{port}{fullpath}{authtoken}/?{args}'.format( '{schema}://{botname}{hostname}{port}{fullpath}{authtoken}' \
'/?{args}'.format(
schema=default_schema, schema=default_schema,
botname=botname,
hostname=NotifyMatterMost.quote(self.host, safe=''), hostname=NotifyMatterMost.quote(self.host, safe=''),
port='' if not self.port or self.port == default_port port='' if not self.port or self.port == default_port
else ':{}'.format(self.port), else ':{}'.format(self.port),
fullpath='/' if not self.fullpath else '{}/'.format( fullpath='/' if not self.fullpath else '{}/'.format(
NotifyMatterMost.quote(self.fullpath, safe='/')), NotifyMatterMost.quote(self.fullpath, safe='/')),
authtoken=NotifyMatterMost.quote(self.authtoken, safe=''), authtoken=self.pprint(self.authtoken, privacy, safe=''),
args=NotifyMatterMost.urlencode(args), args=NotifyMatterMost.urlencode(args),
) )

View File

@ -304,7 +304,7 @@ class NotifyMessageBird(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -318,7 +318,7 @@ class NotifyMessageBird(NotifyBase):
return '{schema}://{apikey}/{source}/{targets}/?{args}'.format( return '{schema}://{apikey}/{source}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
apikey=self.apikey, apikey=self.pprint(self.apikey, privacy, safe=''),
source=self.source, source=self.source,
targets='/'.join( targets='/'.join(
[NotifyMessageBird.quote(x, safe='') for x in self.targets]), [NotifyMessageBird.quote(x, safe='') for x in self.targets]),

View File

@ -33,6 +33,7 @@ import re
import requests import requests
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -334,7 +335,7 @@ class NotifyNexmo(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -349,8 +350,9 @@ class NotifyNexmo(NotifyBase):
return '{schema}://{key}:{secret}@{source}/{targets}/?{args}'.format( return '{schema}://{key}:{secret}@{source}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
key=self.apikey, key=self.pprint(self.apikey, privacy, safe=''),
secret=self.secret, secret=self.pprint(
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
source=NotifyNexmo.quote(self.source, safe=''), source=NotifyNexmo.quote(self.source, safe=''),
targets='/'.join( targets='/'.join(
[NotifyNexmo.quote(x, safe='') for x in self.targets]), [NotifyNexmo.quote(x, safe='') for x in self.targets]),

View File

@ -223,7 +223,7 @@ class NotifyProwl(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -247,9 +247,8 @@ class NotifyProwl(NotifyBase):
return '{schema}://{apikey}/{providerkey}/?{args}'.format( return '{schema}://{apikey}/{providerkey}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
apikey=NotifyProwl.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
providerkey='' if not self.providerkey providerkey=self.pprint(self.providerkey, privacy, safe=''),
else NotifyProwl.quote(self.providerkey, safe=''),
args=NotifyProwl.urlencode(args), args=NotifyProwl.urlencode(args),
) )

View File

@ -215,7 +215,7 @@ class NotifyPushBullet(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -235,7 +235,7 @@ class NotifyPushBullet(NotifyBase):
return '{schema}://{accesstoken}/{targets}/?{args}'.format( return '{schema}://{accesstoken}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
accesstoken=NotifyPushBullet.quote(self.accesstoken, safe=''), accesstoken=self.pprint(self.accesstoken, privacy, safe=''),
targets=targets, targets=targets,
args=NotifyPushBullet.urlencode(args)) args=NotifyPushBullet.urlencode(args))

View File

@ -29,6 +29,7 @@ from json import dumps
from itertools import chain from itertools import chain
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -285,7 +286,7 @@ class NotifyPushed(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. 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( return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
app_key=NotifyPushed.quote(self.app_key, safe=''), app_key=self.pprint(self.app_key, privacy, safe=''),
app_secret=NotifyPushed.quote(self.app_secret, safe=''), app_secret=self.pprint(
self.app_secret, privacy, mode=PrivacyMode.Secret, safe=''),
targets='/'.join( targets='/'.join(
[NotifyPushed.quote(x) for x in chain( [NotifyPushed.quote(x) for x in chain(
# Channels are prefixed with a pound/hashtag symbol # Channels are prefixed with a pound/hashtag symbol

View File

@ -27,6 +27,7 @@ import requests
from json import dumps from json import dumps
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -115,7 +116,7 @@ class NotifyPushjet(NotifyBase):
# store our key # store our key
self.secret_key = secret_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. Returns the URL built dynamically based on specified arguments.
""" """
@ -124,7 +125,9 @@ class NotifyPushjet(NotifyBase):
args = { args = {
'format': self.notify_format, 'format': self.notify_format,
'overflow': self.overflow_mode, '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', 'verify': 'yes' if self.verify_certificate else 'no',
} }
@ -135,7 +138,8 @@ class NotifyPushjet(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyPushjet.quote(self.user, safe=''), 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( return '{schema}://{auth}{hostname}{port}/?{args}'.format(

View File

@ -388,7 +388,7 @@ class NotifyPushover(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -424,12 +424,10 @@ class NotifyPushover(NotifyBase):
# it from the devices list # it from the devices list
devices = '' devices = ''
return '{schema}://{auth}{token}/{devices}/?{args}'.format( return '{schema}://{user_key}@{token}/{devices}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
auth='' if not self.user user_key=self.pprint(self.user, privacy, safe=''),
else '{user}@'.format( token=self.pprint(self.token, privacy, safe=''),
user=NotifyPushover.quote(self.user, safe='')),
token=NotifyPushover.quote(self.token, safe=''),
devices=devices, devices=devices,
args=NotifyPushover.urlencode(args)) args=NotifyPushover.urlencode(args))

View File

@ -31,6 +31,7 @@ from json import dumps
from itertools import chain from itertools import chain
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..common import NotifyFormat from ..common import NotifyFormat
from ..common import NotifyType from ..common import NotifyType
@ -279,7 +280,7 @@ class NotifyRocketChat(NotifyBase):
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -297,13 +298,14 @@ class NotifyRocketChat(NotifyBase):
if self.mode == RocketChatAuthMode.BASIC: if self.mode == RocketChatAuthMode.BASIC:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyRocketChat.quote(self.user, safe=''), user=NotifyRocketChat.quote(self.user, safe=''),
password=NotifyRocketChat.quote(self.password, safe=''), password=self.pprint(
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
) )
else: else:
auth = '{user}{webhook}@'.format( auth = '{user}{webhook}@'.format(
user='{}:'.format(NotifyRocketChat.quote(self.user, safe='')) user='{}:'.format(NotifyRocketChat.quote(self.user, safe=''))
if self.user else '', 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 default_port = 443 if self.secure else 80

View File

@ -279,7 +279,7 @@ class NotifyRyver(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -304,7 +304,7 @@ class NotifyRyver(NotifyBase):
schema=self.secure_protocol, schema=self.secure_protocol,
botname=botname, botname=botname,
organization=NotifyRyver.quote(self.organization, safe=''), organization=NotifyRyver.quote(self.organization, safe=''),
token=NotifyRyver.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
args=NotifyRyver.urlencode(args), args=NotifyRyver.urlencode(args),
) )

View File

@ -33,6 +33,7 @@ from xml.etree import ElementTree
from itertools import chain from itertools import chain
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -568,7 +569,7 @@ class NotifySNS(NotifyBase):
return response return response
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -583,9 +584,10 @@ class NotifySNS(NotifyBase):
return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\ return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\
'?{args}'.format( '?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
key_id=NotifySNS.quote(self.aws_access_key_id, safe=''), key_id=self.pprint(self.aws_access_key_id, privacy, safe=''),
key_secret=NotifySNS.quote( key_secret=self.pprint(
self.aws_secret_access_key, safe=''), self.aws_secret_access_key, privacy,
mode=PrivacyMode.Secret, safe=''),
region=NotifySNS.quote(self.aws_region_name, safe=''), region=NotifySNS.quote(self.aws_region_name, safe=''),
targets='/'.join( targets='/'.join(
[NotifySNS.quote(x) for x in chain( [NotifySNS.quote(x) for x in chain(

View File

@ -245,7 +245,7 @@ class NotifySendGrid(NotifyBase):
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -280,7 +280,7 @@ class NotifySendGrid(NotifyBase):
return '{schema}://{apikey}:{from_email}/{targets}?{args}'.format( return '{schema}://{apikey}:{from_email}/{targets}?{args}'.format(
schema=self.secure_protocol, 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='@'), from_email=self.quote(self.from_email, safe='@'),
targets='' if not has_targets else '/'.join( targets='' if not has_targets else '/'.join(
[NotifySendGrid.quote(x, safe='') for x in self.targets]), [NotifySendGrid.quote(x, safe='') for x in self.targets]),

View File

@ -27,6 +27,7 @@ from json import loads
import requests import requests
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -263,7 +264,7 @@ class NotifySimplePush(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -282,14 +283,16 @@ class NotifySimplePush(NotifyBase):
auth = '' auth = ''
if self.user and self.password: if self.user and self.password:
auth = '{salt}:{password}@'.format( auth = '{salt}:{password}@'.format(
salt=NotifySimplePush.quote(self.user, safe=''), salt=self.pprint(
password=NotifySimplePush.quote(self.password, safe=''), self.user, privacy, mode=PrivacyMode.Secret, safe=''),
password=self.pprint(
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
) )
return '{schema}://{auth}{apikey}/?{args}'.format( return '{schema}://{auth}{apikey}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
auth=auth, auth=auth,
apikey=NotifySimplePush.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
args=NotifySimplePush.urlencode(args), args=NotifySimplePush.urlencode(args),
) )

View File

@ -388,7 +388,7 @@ class NotifySlack(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -412,9 +412,9 @@ class NotifySlack(NotifyBase):
'?{args}'.format( '?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
botname=botname, botname=botname,
token_a=NotifySlack.quote(self.token_a, safe=''), token_a=self.pprint(self.token_a, privacy, safe=''),
token_b=NotifySlack.quote(self.token_b, safe=''), token_b=self.pprint(self.token_b, privacy, safe=''),
token_c=NotifySlack.quote(self.token_c, safe=''), token_c=self.pprint(self.token_c, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifySlack.quote(x, safe='') for x in self.channels]), [NotifySlack.quote(x, safe='') for x in self.channels]),
args=NotifySlack.urlencode(args), args=NotifySlack.urlencode(args),

View File

@ -188,7 +188,7 @@ class NotifyTechulusPush(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -202,7 +202,7 @@ class NotifyTechulusPush(NotifyBase):
return '{schema}://{apikey}/?{args}'.format( return '{schema}://{apikey}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
apikey=NotifyTechulusPush.quote(self.apikey, safe=''), apikey=self.pprint(self.apikey, privacy, safe=''),
args=NotifyTechulusPush.urlencode(args), args=NotifyTechulusPush.urlencode(args),
) )

View File

@ -553,7 +553,7 @@ class NotifyTelegram(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -571,7 +571,7 @@ class NotifyTelegram(NotifyBase):
# appended into the list of chat ids # appended into the list of chat ids
return '{schema}://{bot_token}/{targets}/?{args}'.format( return '{schema}://{bot_token}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
bot_token=NotifyTelegram.quote(self.bot_token, safe=''), bot_token=self.pprint(self.bot_token, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]), [NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]),
args=NotifyTelegram.urlencode(args)) args=NotifyTelegram.urlencode(args))

View File

@ -45,6 +45,7 @@ import requests
from json import loads from json import loads
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -374,7 +375,7 @@ class NotifyTwilio(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -388,8 +389,9 @@ class NotifyTwilio(NotifyBase):
return '{schema}://{sid}:{token}@{source}/{targets}/?{args}'.format( return '{schema}://{sid}:{token}@{source}/{targets}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
sid=self.account_sid, sid=self.pprint(
token=self.auth_token, self.account_sid, privacy, mode=PrivacyMode.Tail, safe=''),
token=self.pprint(self.auth_token, privacy, safe=''),
source=NotifyTwilio.quote(self.source, safe=''), source=NotifyTwilio.quote(self.source, safe=''),
targets='/'.join( targets='/'.join(
[NotifyTwilio.quote(x, safe='') for x in self.targets]), [NotifyTwilio.quote(x, safe='') for x in self.targets]),

View File

@ -32,6 +32,7 @@ from json import loads
from itertools import chain from itertools import chain
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyFormat from ..common import NotifyFormat
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
@ -223,7 +224,7 @@ class NotifyTwist(NotifyBase):
self.default_notification_channel)) self.default_notification_channel))
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -237,7 +238,8 @@ class NotifyTwist(NotifyBase):
return '{schema}://{password}:{user}@{host}/{targets}/?{args}'.format( return '{schema}://{password}:{user}@{host}/{targets}/?{args}'.format(
schema=self.secure_protocol, 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=''), user=self.quote(self.user, safe=''),
host=self.host, host=self.host,
targets='/'.join( targets='/'.join(

View File

@ -33,6 +33,7 @@ from requests_oauthlib import OAuth1
from json import dumps from json import dumps
from json import loads from json import loads
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..utils import parse_bool from ..utils import parse_bool
@ -558,7 +559,7 @@ class NotifyTwitter(NotifyBase):
""" """
return 10000 if self.mode == TwitterMessageMode.DM else 280 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. Returns the URL built dynamically based on specified arguments.
""" """
@ -578,10 +579,12 @@ class NotifyTwitter(NotifyBase):
return '{schema}://{ckey}/{csecret}/{akey}/{asecret}' \ return '{schema}://{ckey}/{csecret}/{akey}/{asecret}' \
'/{targets}/?{args}'.format( '/{targets}/?{args}'.format(
schema=self.secure_protocol[0], schema=self.secure_protocol[0],
ckey=NotifyTwitter.quote(self.ckey, safe=''), ckey=self.pprint(self.ckey, privacy, safe=''),
asecret=NotifyTwitter.quote(self.csecret, safe=''), csecret=self.pprint(
akey=NotifyTwitter.quote(self.akey, safe=''), self.csecret, privacy, mode=PrivacyMode.Secret, safe=''),
csecret=NotifyTwitter.quote(self.asecret, safe=''), akey=self.pprint(self.akey, privacy, safe=''),
asecret=self.pprint(
self.asecret, privacy, mode=PrivacyMode.Secret, safe=''),
targets='/'.join( targets='/'.join(
[NotifyTwitter.quote('@{}'.format(target), safe='') [NotifyTwitter.quote('@{}'.format(target), safe='')
for target in self.targets]), for target in self.targets]),

View File

@ -210,7 +210,7 @@ class NotifyWebexTeams(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -224,7 +224,7 @@ class NotifyWebexTeams(NotifyBase):
return '{schema}://{token}/?{args}'.format( return '{schema}://{token}/?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
token=NotifyWebexTeams.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
args=NotifyWebexTeams.urlencode(args), args=NotifyWebexTeams.urlencode(args),
) )

View File

@ -217,7 +217,7 @@ class NotifyWindows(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """

View File

@ -27,6 +27,7 @@ import requests
from json import dumps from json import dumps
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..utils import parse_bool from ..utils import parse_bool
@ -296,7 +297,7 @@ class NotifyXBMC(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -315,7 +316,8 @@ class NotifyXBMC(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyXBMC.quote(self.user, safe=''), 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: elif self.user:
auth = '{user}@'.format( auth = '{user}@'.format(
@ -327,7 +329,7 @@ class NotifyXBMC(NotifyBase):
default_port = 443 if self.secure else self.xbmc_default_port default_port = 443 if self.secure else self.xbmc_default_port
if self.secure: if self.secure:
# Append 's' to schema # Append 's' to schema
default_schema + 's' default_schema += 's'
return '{schema}://{auth}{hostname}{port}/?{args}'.format( return '{schema}://{auth}{hostname}{port}/?{args}'.format(
schema=default_schema, schema=default_schema,

View File

@ -28,6 +28,7 @@ import six
import requests import requests
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..common import NotifyType from ..common import NotifyType
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -138,7 +139,7 @@ class NotifyXML(NotifyBase):
return return
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -158,7 +159,8 @@ class NotifyXML(NotifyBase):
if self.user and self.password: if self.user and self.password:
auth = '{user}:{password}@'.format( auth = '{user}:{password}@'.format(
user=NotifyXML.quote(self.user, safe=''), 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: elif self.user:
auth = '{user}@'.format( auth = '{user}@'.format(

View File

@ -28,6 +28,7 @@ import ssl
from os.path import isfile from os.path import isfile
from .NotifyBase import NotifyBase from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType from ..common import NotifyType
from ..utils import parse_list from ..utils import parse_list
from ..AppriseLocale import gettext_lazy as _ from ..AppriseLocale import gettext_lazy as _
@ -344,7 +345,7 @@ class NotifyXMPP(NotifyBase):
return True return True
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. 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 default_schema = self.secure_protocol if self.secure else self.protocol
if self.user and self.password: if self.user and self.password:
auth = '{}:{}'.format( auth = '{user}:{password}'.format(
NotifyXMPP.quote(self.user, safe=''), user=NotifyXMPP.quote(self.user, safe=''),
NotifyXMPP.quote(self.password, safe='')) password=self.pprint(
self.password, privacy, mode=PrivacyMode.Secret, safe=''))
else: 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( return '{schema}://{auth}@{hostname}{port}/{jids}?{args}'.format(
auth=auth, auth=auth,

View File

@ -328,7 +328,7 @@ class NotifyZulip(NotifyBase):
return not has_error return not has_error
def url(self): def url(self, privacy=False, *args, **kwargs):
""" """
Returns the URL built dynamically based on specified arguments. Returns the URL built dynamically based on specified arguments.
""" """
@ -349,9 +349,9 @@ class NotifyZulip(NotifyBase):
return '{schema}://{botname}@{org}/{token}/' \ return '{schema}://{botname}@{org}/{token}/' \
'{targets}?{args}'.format( '{targets}?{args}'.format(
schema=self.secure_protocol, schema=self.secure_protocol,
botname=self.botname, botname=NotifyZulip.quote(self.botname, safe=''),
org=NotifyZulip.quote(organization, safe=''), org=NotifyZulip.quote(organization, safe=''),
token=NotifyZulip.quote(self.token, safe=''), token=self.pprint(self.token, privacy, safe=''),
targets='/'.join( targets='/'.join(
[NotifyZulip.quote(x, safe='') for x in self.targets]), [NotifyZulip.quote(x, safe='') for x in self.targets]),
args=NotifyZulip.urlencode(args), args=NotifyZulip.urlencode(args),

View File

@ -41,6 +41,8 @@ from apprise import NotifyType
from apprise import NotifyFormat from apprise import NotifyFormat
from apprise import NotifyImageSize from apprise import NotifyImageSize
from apprise import __version__ from apprise import __version__
from apprise import URLBase
from apprise import PrivacyMode
from apprise.plugins import SCHEMA_MAP from apprise.plugins import SCHEMA_MAP
from apprise.plugins import __load_matrix from apprise.plugins import __load_matrix
@ -359,6 +361,55 @@ def test_apprise():
'host': 'localhost'}, suppress_exceptions=True) is None) 'host': 'localhost'}, suppress_exceptions=True) is None)
assert(len(a) == 0) 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.get')
@mock.patch('requests.post') @mock.patch('requests.post')

View File

@ -181,6 +181,8 @@ TEST_URLS = (
# STARTTLS flag checking # STARTTLS flag checking
('mailtos://user:pass@gmail.com?mode=starttls', { ('mailtos://user:pass@gmail.com?mode=starttls', {
'instance': plugins.NotifyEmail, 'instance': plugins.NotifyEmail,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mailtos://user:****@gmail.com',
}), }),
# SSL flag checking # SSL flag checking
('mailtos://user:pass@gmail.com?mode=ssl', { ('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) # Can make a To address using what we have (l2g@nuxref.com)
('mailtos://nuxref.com?user=l2g&pass=.', { ('mailtos://nuxref.com?user=l2g&pass=.', {
'instance': plugins.NotifyEmail, 'instance': plugins.NotifyEmail,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mailtos://l2g:****@nuxref.com',
}), }),
('mailto://user:pass@localhost:2525', { ('mailto://user:pass@localhost:2525', {
'instance': plugins.NotifyEmail, 'instance': plugins.NotifyEmail,
@ -221,6 +225,10 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
# Our expected Query response (True, False, or exception type) # Our expected Query response (True, False, or exception type)
response = meta.get('response', True) 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 = meta.get(
'test_smtplib_exceptions', False) '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 # We loaded okay; now lets make sure we can reverse this url
assert(isinstance(obj.url(), six.string_types) is True) 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 # Instantiate the exact same object again using the URL from
# the one that was already created properly # the one that was already created properly
obj_cmp = Apprise.instantiate(obj.url()) 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 # We loaded okay; now lets make sure we can reverse this url
assert(isinstance(obj.url(), six.string_types) is True) 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 # Instantiate the exact same object again using the URL from
# the one that was already created properly # the one that was already created properly
obj_cmp = Apprise.instantiate(obj.url()) obj_cmp = Apprise.instantiate(obj.url())

View File

@ -85,6 +85,8 @@ TEST_URLS = (
('boxcar://%s/%s' % ('a' * 64, 'b' * 64), { ('boxcar://%s/%s' % ('a' * 64, 'b' * 64), {
'instance': plugins.NotifyBoxcar, 'instance': plugins.NotifyBoxcar,
'requests_response_code': requests.codes.created, 'requests_response_code': requests.codes.created,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'boxcar://a...a/****/',
}), }),
# Test without image set # Test without image set
('boxcar://%s/%s?image=True' % ('a' * 64, 'b' * 64), { ('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), { ('clicksend://user:pass@{}?batch=yes&to={}'.format('3' * 14, '6' * 14), {
# valid number but using the to= variable # valid number but using the to= variable
'instance': plugins.NotifyClickSend, 'instance': plugins.NotifyClickSend,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'clicksend://user:****',
}), }),
('clicksend://user:pass@{}?batch=no'.format('3' * 14), { ('clicksend://user:pass@{}?batch=no'.format('3' * 14), {
# valid number - no batch # valid number - no batch
@ -187,6 +191,8 @@ TEST_URLS = (
('d7sms://user:pass@{}?batch=yes'.format('3' * 14), { ('d7sms://user:pass@{}?batch=yes'.format('3' * 14), {
# valid number # valid number
'instance': plugins.NotifyD7Networks, 'instance': plugins.NotifyD7Networks,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'd7sms://user:****@',
}), }),
('d7sms://user:pass@{}?batch=yes'.format('7' * 14), { ('d7sms://user:pass@{}?batch=yes'.format('7' * 14), {
# valid number # valid number
@ -269,6 +275,9 @@ TEST_URLS = (
'i' * 24, 't' * 64), { 'i' * 24, 't' * 64), {
'instance': plugins.NotifyDiscord, 'instance': plugins.NotifyDiscord,
'requests_response_code': requests.codes.no_content, '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' % ( ('discord://%s/%s?format=markdown&footer=Yes&thumbnail=No' % (
'i' * 24, 't' * 64), { 'i' * 24, 't' * 64), {
@ -374,6 +383,9 @@ TEST_URLS = (
# tested very well using this matrix. It will resume in # tested very well using this matrix. It will resume in
# in test_notify_emby_plugin() # in test_notify_emby_plugin()
'response': False, '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() # The rest of the emby tests are in test_notify_emby_plugin()
@ -386,6 +398,9 @@ TEST_URLS = (
# Auth Token specified # Auth Token specified
('faast://%s' % ('a' * 32), { ('faast://%s' % ('a' * 32), {
'instance': plugins.NotifyFaast, 'instance': plugins.NotifyFaast,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'faast://a...a',
}), }),
('faast://%s' % ('a' * 32), { ('faast://%s' % ('a' * 32), {
'instance': plugins.NotifyFaast, 'instance': plugins.NotifyFaast,
@ -428,6 +443,9 @@ TEST_URLS = (
# Image handling # Image handling
('flock://%s?image=True' % ('t' * 24), { ('flock://%s?image=True' % ('t' * 24), {
'instance': plugins.NotifyFlock, 'instance': plugins.NotifyFlock,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'flock://t...t',
}), }),
('flock://%s?image=False' % ('t' * 24), { ('flock://%s?image=False' % ('t' * 24), {
'instance': plugins.NotifyFlock, 'instance': plugins.NotifyFlock,
@ -558,6 +576,9 @@ TEST_URLS = (
('gitter://%s/apprise?image=Yes' % ('a' * 40), { ('gitter://%s/apprise?image=Yes' % ('a' * 40), {
'instance': plugins.NotifyGitter, 'instance': plugins.NotifyGitter,
'response': False, '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) # Don't include image in post (this is the default anyway)
('gitter://%s/apprise?image=No' % ('a' * 40), { ('gitter://%s/apprise?image=No' % ('a' * 40), {
@ -596,6 +617,9 @@ TEST_URLS = (
# Provide a hostname and token # Provide a hostname and token
('gotify://hostname/%s' % ('t' * 16), { ('gotify://hostname/%s' % ('t' * 16), {
'instance': plugins.NotifyGotify, 'instance': plugins.NotifyGotify,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'gotify://hostname/t...t',
}), }),
# Provide a priority # Provide a priority
('gotify://hostname/%s?priority=high' % ('i' * 16), { ('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 # A nicely formed ifttt url with 1 event and a new key/value store
('ifttt://WebHookID@EventID/?+TemplateKey=TemplateVal', { ('ifttt://WebHookID@EventID/?+TemplateKey=TemplateVal', {
'instance': plugins.NotifyIFTTT, '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 # Test to= in which case we set the host to the webhook id
('ifttt://WebHookID?to=EventID,EventID2', { ('ifttt://WebHookID?to=EventID,EventID2', {
@ -703,6 +730,9 @@ TEST_URLS = (
# APIKey + device (using to=) # APIKey + device (using to=)
('join://%s?to=%s' % ('a' * 32, 'd' * 32), { ('join://%s?to=%s' % ('a' * 32, 'd' * 32), {
'instance': plugins.NotifyJoin, 'instance': plugins.NotifyJoin,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'join://a...a/',
}), }),
# APIKey + device # APIKey + device
('join://%s@%s?image=True' % ('a' * 32, 'd' * 32), { ('join://%s@%s?image=True' % ('a' * 32, 'd' * 32), {
@ -770,6 +800,9 @@ TEST_URLS = (
}), }),
('json://user:pass@localhost', { ('json://user:pass@localhost', {
'instance': plugins.NotifyJSON, 'instance': plugins.NotifyJSON,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'json://user:****@localhost',
}), }),
('json://user@localhost', { ('json://user@localhost', {
'instance': plugins.NotifyJSON, 'instance': plugins.NotifyJSON,
@ -789,8 +822,11 @@ TEST_URLS = (
('jsons://localhost:8080/path/', { ('jsons://localhost:8080/path/', {
'instance': plugins.NotifyJSON, 'instance': plugins.NotifyJSON,
}), }),
('jsons://user:pass@localhost:8080', { ('jsons://user:password@localhost:8080', {
'instance': plugins.NotifyJSON, 'instance': plugins.NotifyJSON,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'jsons://user:****@localhost:8080',
}), }),
('json://:@/', { ('json://:@/', {
'instance': None, 'instance': None,
@ -817,7 +853,6 @@ TEST_URLS = (
'instance': plugins.NotifyJSON, 'instance': plugins.NotifyJSON,
}), }),
################################## ##################################
# NotifyKODI # NotifyKODI
################################## ##################################
@ -832,6 +867,9 @@ TEST_URLS = (
}), }),
('kodi://user:pass@localhost', { ('kodi://user:pass@localhost', {
'instance': plugins.NotifyXBMC, 'instance': plugins.NotifyXBMC,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'kodi://user:****@localhost',
}), }),
('kodi://localhost:8080', { ('kodi://localhost:8080', {
'instance': plugins.NotifyXBMC, 'instance': plugins.NotifyXBMC,
@ -848,8 +886,11 @@ TEST_URLS = (
('kodis://localhost:8080/path/', { ('kodis://localhost:8080/path/', {
'instance': plugins.NotifyXBMC, 'instance': plugins.NotifyXBMC,
}), }),
('kodis://user:pass@localhost:8080', { ('kodis://user:password@localhost:8080', {
'instance': plugins.NotifyXBMC, 'instance': plugins.NotifyXBMC,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'kodis://user:****@localhost:8080',
}), }),
('kodi://localhost', { ('kodi://localhost', {
'instance': plugins.NotifyXBMC, 'instance': plugins.NotifyXBMC,
@ -917,18 +958,27 @@ TEST_URLS = (
('kumulos://{}/{}/'.format(UUID4, 'w' * 36), { ('kumulos://{}/{}/'.format(UUID4, 'w' * 36), {
# Everything is okay # Everything is okay
'instance': plugins.NotifyKumulos, 'instance': plugins.NotifyKumulos,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'kumulos://8...2/w...w/',
}), }),
('kumulos://{}/{}/'.format(UUID4, 'x' * 36), { ('kumulos://{}/{}/'.format(UUID4, 'x' * 36), {
'instance': plugins.NotifyKumulos, 'instance': plugins.NotifyKumulos,
# force a failure # force a failure
'response': False, 'response': False,
'requests_response_code': requests.codes.internal_server_error, '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), { ('kumulos://{}/{}/'.format(UUID4, 'y' * 36), {
'instance': plugins.NotifyKumulos, 'instance': plugins.NotifyKumulos,
# throw a bizzare code forcing us to fail to look it up # throw a bizzare code forcing us to fail to look it up
'response': False, 'response': False,
'requests_response_code': 999, 'requests_response_code': 999,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'kumulos://8...2/y...y/',
}), }),
('kumulos://{}/{}/'.format(UUID4, 'z' * 36), { ('kumulos://{}/{}/'.format(UUID4, 'z' * 36), {
'instance': plugins.NotifyKumulos, 'instance': plugins.NotifyKumulos,
@ -1041,6 +1091,9 @@ TEST_URLS = (
# Throws a series of connection and transfer exceptions when this flag # Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them # is set and tests that we gracfully handle them
'test_requests_exceptions': True, '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: # Matrix supports webhooks too; the following tests this now:
@ -1134,6 +1187,9 @@ TEST_URLS = (
}), }),
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', { ('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', {
'instance': plugins.NotifyMatterMost, 'instance': plugins.NotifyMatterMost,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mmost://user@localhost/3...4/',
}), }),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' ('mmost://localhost/3ccdd113474722377935511fc85d3dd4'
'?to=test&image=True', { '?to=test&image=True', {
@ -1148,6 +1204,9 @@ TEST_URLS = (
'include_image': False}), 'include_image': False}),
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', { ('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost, 'instance': plugins.NotifyMatterMost,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mmost://localhost:8080/3...4/',
}), }),
('mmost://localhost:0/3ccdd113474722377935511fc85d3dd4', { ('mmost://localhost:0/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost, 'instance': plugins.NotifyMatterMost,
@ -1251,6 +1310,9 @@ TEST_URLS = (
('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'a' * 32, UUID4), { ('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'a' * 32, UUID4), {
# All tokens provided - we're good no image # All tokens provided - we're good no image
'instance': plugins.NotifyMSTeams, '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), { ('msteams://{}@{}/{}/{}?tx'.format(UUID4, UUID4, 'a' * 32, UUID4), {
'instance': plugins.NotifyMSTeams, 'instance': plugins.NotifyMSTeams,
@ -1271,6 +1333,82 @@ TEST_URLS = (
'test_requests_exceptions': True, '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 # NotifyProwl
################################## ##################################
@ -1308,12 +1446,18 @@ TEST_URLS = (
'instance': TypeError, 'instance': TypeError,
}), }),
# APIKey + No Provider Key (empty) # APIKey + No Provider Key (empty)
('prowl://%s///' % ('a' * 40), { ('prowl://%s///' % ('w' * 40), {
'instance': plugins.NotifyProwl, 'instance': plugins.NotifyProwl,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'prowl://w...w/',
}), }),
# APIKey + Provider Key # APIKey + Provider Key
('prowl://%s/%s' % ('a' * 40, 'b' * 40), { ('prowl://%s/%s' % ('a' * 40, 'b' * 40), {
'instance': plugins.NotifyProwl, 'instance': plugins.NotifyProwl,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'prowl://a...a/b...b',
}), }),
# APIKey + with image # APIKey + with image
('prowl://%s' % ('a' * 40), { ('prowl://%s' % ('a' * 40), {
@ -1363,6 +1507,9 @@ TEST_URLS = (
# APIKey + 2 channels # APIKey + 2 channels
('pbul://%s/#channel1/#channel2' % ('a' * 32), { ('pbul://%s/#channel1/#channel2' % ('a' * 32), {
'instance': plugins.NotifyPushBullet, 'instance': plugins.NotifyPushBullet,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'pbul://a...a/',
}), }),
# APIKey + device # APIKey + device
('pbul://%s/device/' % ('a' * 32), { ('pbul://%s/device/' % ('a' * 32), {
@ -1439,6 +1586,9 @@ TEST_URLS = (
# APIkey # APIkey
('push://%s' % UUID4, { ('push://%s' % UUID4, {
'instance': plugins.NotifyTechulusPush, 'instance': plugins.NotifyTechulusPush,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'push://8...2/',
}), }),
# APIKey + bad url # APIKey + bad url
('push://:@/', { ('push://:@/', {
@ -1488,6 +1638,9 @@ TEST_URLS = (
# Application Key+Secret + dropped entry # Application Key+Secret + dropped entry
('pushed://%s/%s/dropped/' % ('a' * 32, 'a' * 64), { ('pushed://%s/%s/dropped/' % ('a' * 32, 'a' * 64), {
'instance': plugins.NotifyPushed, 'instance': plugins.NotifyPushed,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'pushed://a...a/****/',
}), }),
# Application Key+Secret + 2 channels # Application Key+Secret + 2 channels
('pushed://%s/%s/#channel1/#channel2' % ('a' * 32, 'a' * 64), { ('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) # Specify your own server with login (secret= MUST be provided)
('pjet://user:pass@localhost?secret=%s' % ('a' * 32), { ('pjet://user:pass@localhost?secret=%s' % ('a' * 32), {
'instance': plugins.NotifyPushjet, '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) # Specify your own server with login (no secret = fail normally)
# however this will work since we're providing depricated support # however this will work since we're providing depricated support
@ -1662,6 +1818,9 @@ TEST_URLS = (
# APIKey + Valid User + 2 Devices # APIKey + Valid User + 2 Devices
('pover://%s@%s/DEVICE1/DEVICE2/' % ('u' * 30, 'a' * 30), { ('pover://%s@%s/DEVICE1/DEVICE2/' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover, 'instance': plugins.NotifyPushover,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'pover://u...u@a...a',
}), }),
# APIKey + Valid User + invalid device # APIKey + Valid User + invalid device
('pover://%s@%s/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), { ('pover://%s@%s/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), {
@ -1850,6 +2009,8 @@ TEST_URLS = (
'userId': 'user', 'userId': 'user',
}, },
}, },
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'rocket://user:****@localhost',
}), }),
# A user/pass where the pass matches a webtoken # A user/pass where the pass matches a webtoken
# to ensure we get the right mode, we enforce basic mode # to ensure we get the right mode, we enforce basic mode
@ -1891,6 +2052,9 @@ TEST_URLS = (
('rockets://web/token@localhost/?avatar=No', { ('rockets://web/token@localhost/?avatar=No', {
# a simple webhook token with default values # a simple webhook token with default values
'instance': plugins.NotifyRocketChat, 'instance': plugins.NotifyRocketChat,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'rockets://w...n@localhost',
}), }),
('rockets://localhost/@user/?mode=webhook&webhook=web/token', { ('rockets://localhost/@user/?mode=webhook&webhook=web/token', {
'instance': plugins.NotifyRocketChat, 'instance': plugins.NotifyRocketChat,
@ -1975,6 +2139,9 @@ TEST_URLS = (
# No username specified; this is still okay as we use whatever # No username specified; this is still okay as we use whatever
# the user told the webhook to use; set our ryver mode # the user told the webhook to use; set our ryver mode
'instance': plugins.NotifyRyver, 'instance': plugins.NotifyRyver,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'ryver://apprise/c...G',
}), }),
# Support Native URLs # Support Native URLs
('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG', { ('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG', {
@ -2058,6 +2225,9 @@ TEST_URLS = (
'?template={}&+sub=value&+sub2=value2'.format(UUID4), { '?template={}&+sub=value&+sub2=value2'.format(UUID4), {
# A good email with a template + substitutions # A good email with a template + substitutions
'instance': plugins.NotifySendGrid, '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', { ('sendgrid://abcd:user@example.ca/newuser@example.ca', {
'instance': plugins.NotifySendGrid, 'instance': plugins.NotifySendGrid,
@ -2092,13 +2262,16 @@ TEST_URLS = (
# Expected notify() response # Expected notify() response
'notify_response': False, 'notify_response': False,
}), }),
('spush://{}'.format('X' * 14), { ('spush://{}'.format('Y' * 14), {
# API Key valid and expected response was valid # API Key valid and expected response was valid
'instance': plugins.NotifySimplePush, 'instance': plugins.NotifySimplePush,
# Set our response to OK # Set our response to OK
'requests_response_text': { 'requests_response_text': {
'status': 'OK', 'status': 'OK',
}, },
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'spush://Y...Y/',
}), }),
('spush://{}?event=Not%20So%20Good'.format('X' * 14), { ('spush://{}?event=Not%20So%20Good'.format('X' * 14), {
# API Key valid and expected response was valid # API Key valid and expected response was valid
@ -2117,6 +2290,9 @@ TEST_URLS = (
'requests_response_text': { 'requests_response_text': {
'status': 'OK', 'status': 'OK',
}, },
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'spush://****:****@X...X/',
}), }),
('spush://{}'.format('Y' * 14), { ('spush://{}'.format('Y' * 14), {
'instance': plugins.NotifySimplePush, 'instance': plugins.NotifySimplePush,
@ -2176,6 +2352,9 @@ TEST_URLS = (
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/' \ ('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/' \
'?to=#nuxref', { '?to=#nuxref', {
'instance': plugins.NotifySlack, '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', { ('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
'instance': plugins.NotifySlack, 'instance': plugins.NotifySlack,
@ -2248,6 +2427,9 @@ TEST_URLS = (
('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1', { ('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1', {
# Missing a topic and/or phone No # Missing a topic and/or phone No
'instance': plugins.NotifySNS, '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' \ ('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1' \
'?to=12223334444', { '?to=12223334444', {
@ -2453,6 +2635,9 @@ TEST_URLS = (
('twilio://AC{}:{}@12345/{}'.format('a' * 32, 'b' * 32, '4' * 11), { ('twilio://AC{}:{}@12345/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
# using short-code (5 characters) # using short-code (5 characters)
'instance': plugins.NotifyTwilio, '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), { ('twilio://AC{}:{}@123456/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
# using short-code (6 characters) # using short-code (6 characters)
@ -2518,6 +2703,9 @@ TEST_URLS = (
# Expected notify() response is False because internally we would # Expected notify() response is False because internally we would
# have failed to login # have failed to login
'notify_response': False, 'notify_response': False,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'twist://****:user1@example.com',
}), }),
('twist://password:user2@example.com', { ('twist://password:user2@example.com', {
# password:login acceptable # password:login acceptable
@ -2572,6 +2760,9 @@ TEST_URLS = (
# Expected notify() response False (because we won't be able # Expected notify() response False (because we won't be able
# to detect our user) # to detect our user)
'notify_response': False, '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' ('twitter://consumer_key/consumer_secret/access_token/access_secret'
'?cache=no', { '?cache=no', {
@ -2743,6 +2934,9 @@ TEST_URLS = (
('msg91://{}/15551232000'.format('a' * 23), { ('msg91://{}/15551232000'.format('a' * 23), {
# a valid message # a valid message
'instance': plugins.NotifyMSG91, 'instance': plugins.NotifyMSG91,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'msg91://a...a/15551232000',
}), }),
('msg91://{}/?to=15551232000'.format('a' * 23), { ('msg91://{}/?to=15551232000'.format('a' * 23), {
# a valid message # a valid message
@ -2796,6 +2990,9 @@ TEST_URLS = (
('msgbird://{}/15551232000/abcd'.format('a' * 25), { ('msgbird://{}/15551232000/abcd'.format('a' * 25), {
# invalid target phone number; we fall back to texting ourselves # invalid target phone number; we fall back to texting ourselves
'instance': plugins.NotifyMessageBird, 'instance': plugins.NotifyMessageBird,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'msgbird://a...a/15551232000',
}), }),
('msgbird://{}/15551232000/123'.format('a' * 25), { ('msgbird://{}/15551232000/123'.format('a' * 25), {
# invalid target phone number; we fall back to texting ourselves # invalid target phone number; we fall back to texting ourselves
@ -2824,79 +3021,6 @@ TEST_URLS = (
'test_requests_exceptions': True, '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 # NotifyWebexTeams
################################## ##################################
@ -2917,6 +3041,9 @@ TEST_URLS = (
('wxteams://{}'.format('a' * 80), { ('wxteams://{}'.format('a' * 80), {
# token provided - we're good # token provided - we're good
'instance': plugins.NotifyWebexTeams, 'instance': plugins.NotifyWebexTeams,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'wxteams://a...a/',
}), }),
# Support Native URLs # Support Native URLs
('https://api.ciscospark.com/v1/webhooks/incoming/{}'.format('a' * 80), { ('https://api.ciscospark.com/v1/webhooks/incoming/{}'.format('a' * 80), {
@ -3023,6 +3150,9 @@ TEST_URLS = (
}), }),
('xml://user:pass@localhost', { ('xml://user:pass@localhost', {
'instance': plugins.NotifyXML, 'instance': plugins.NotifyXML,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'xml://user:****@localhost',
}), }),
('xml://localhost:8080', { ('xml://localhost:8080', {
'instance': plugins.NotifyXML, 'instance': plugins.NotifyXML,
@ -3035,6 +3165,9 @@ TEST_URLS = (
}), }),
('xmls://user:pass@localhost', { ('xmls://user:pass@localhost', {
'instance': plugins.NotifyXML, 'instance': plugins.NotifyXML,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'xmls://user:****@localhost',
}), }),
('xmls://localhost:8080/path/', { ('xmls://localhost:8080/path/', {
'instance': plugins.NotifyXML, 'instance': plugins.NotifyXML,
@ -3095,6 +3228,9 @@ TEST_URLS = (
# Valid everything - no target so default is used # Valid everything - no target so default is used
('zulip://botname@apprise/{}'.format('a' * 32), { ('zulip://botname@apprise/{}'.format('a' * 32), {
'instance': plugins.NotifyZulip, 'instance': plugins.NotifyZulip,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'zulip://botname@apprise/a...a/',
}), }),
# Valid everything - organization as hostname # Valid everything - organization as hostname
('zulip://botname@apprise.zulipchat.com/{}'.format('a' * 32), { ('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) # Our expected Notify response (True or False)
notify_response = meta.get('notify_response', response) 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 # Allow us to force the server response code to be something other then
# the defaults # the defaults
requests_response_code = meta.get( 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 # We loaded okay; now lets make sure we can reverse this url
assert isinstance(obj.url(), six.string_types) is True 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 # Some Simple Invalid Instance Testing
assert instance.parse_url(None) is None assert instance.parse_url(None) is None
assert instance.parse_url(object) is None assert instance.parse_url(object) is None
assert instance.parse_url(42) 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 # Instantiate the exact same object again using the URL from
# the one that was already created properly # the one that was already created properly
obj_cmp = Apprise.instantiate(obj.url()) obj_cmp = Apprise.instantiate(obj.url())
@ -4798,6 +4946,11 @@ def test_notify_telegram_plugin(mock_post, mock_get):
# test url call # test url call
assert isinstance(obj.url(), six.string_types) is True 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: # Test that we can load the string we generate back:
obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url())) obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url()))
assert isinstance(obj, plugins.NotifyTelegram) is True assert isinstance(obj, plugins.NotifyTelegram) is True

View File

@ -162,16 +162,41 @@ def test_xmpp_plugin(tmpdir):
# Restore settings as they were # Restore settings as they were
del ssl.PROTOCOL_TLS 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 # Try Different Variations of our URL
for url in ( for entry in urls:
'xmpps://user:pass@example.com',
'xmpps://user:pass@example.com?xep=30,199,garbage,xep_99999999', url = entry['u']
'xmpps://user:pass@example.com?xep=ignored', privacy_url = entry['p']
'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'):
obj = apprise.Apprise.instantiate(url, suppress_exceptions=False) obj = apprise.Apprise.instantiate(url, suppress_exceptions=False)
@ -184,6 +209,11 @@ def test_xmpp_plugin(tmpdir):
# Test url() call # Test url() call
assert isinstance(obj.url(), six.string_types) is True 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 # test notifications
assert obj.notify( assert obj.notify(
title='title', body='body', title='title', body='body',