Skip to content

Commit d81075b

Browse files
authored
Accept prefixed topic named (#126)
* Accept prefixed topic named * Minor refactoring
1 parent 3d31d6a commit d81075b

File tree

2 files changed

+19
-9
lines changed

2 files changed

+19
-9
lines changed

firebase_admin/messaging.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class Message(object):
163163
apns: An instance of ``messaging.ApnsConfig`` (optional).
164164
token: The registration token of the device to which the message should be sent (optional).
165165
topic: Name of the FCM topic to which the message should be sent (optional). Topic name
166-
must not contain the ``/topics/`` prefix.
166+
may contain the ``/topics/`` prefix.
167167
condition: The FCM condition to which the message should be sent (optional).
168168
"""
169169

@@ -671,6 +671,18 @@ def encode_notification(cls, notification):
671671
}
672672
return cls.remove_null_values(result)
673673

674+
@classmethod
675+
def sanitize_topic_name(cls, topic):
676+
if not topic:
677+
return None
678+
prefix = '/topics/'
679+
if topic.startswith(prefix):
680+
topic = topic[len(prefix):]
681+
# Checks for illegal characters and empty string.
682+
if not re.match(r'^[a-zA-Z0-9-_\.~%]+$', topic):
683+
raise ValueError('Malformed topic name.')
684+
return topic
685+
674686
def default(self, obj): # pylint: disable=method-hidden
675687
if not isinstance(obj, Message):
676688
return json.JSONEncoder.default(self, obj)
@@ -685,16 +697,11 @@ def default(self, obj): # pylint: disable=method-hidden
685697
'topic': _Validators.check_string('Message.topic', obj.topic, non_empty=True),
686698
'webpush': _MessageEncoder.encode_webpush(obj.webpush),
687699
}
700+
result['topic'] = _MessageEncoder.sanitize_topic_name(result.get('topic'))
688701
result = _MessageEncoder.remove_null_values(result)
689702
target_count = sum([t in result for t in ['token', 'topic', 'condition']])
690703
if target_count != 1:
691704
raise ValueError('Exactly one of token, topic or condition must be specified.')
692-
topic = result.get('topic')
693-
if topic:
694-
if topic.startswith('/topics/'):
695-
raise ValueError('Topic name must not contain the /topics/ prefix.')
696-
if not re.match(r'^[a-zA-Z0-9-_\.~%]+$', topic):
697-
raise ValueError('Illegal characters in topic name.')
698705
return result
699706

700707

tests/test_messaging.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def test_invalid_condition(self, target):
7171
check_encoding(messaging.Message(condition=target))
7272
assert str(excinfo.value) == 'Message.condition must be a non-empty string.'
7373

74-
@pytest.mark.parametrize('topic', ['/topics/foo', '/foo/bar', 'foo bar'])
75-
def test_topic_name_prefix(self, topic):
74+
@pytest.mark.parametrize('topic', ['/topics/', '/foo/bar', 'foo bar'])
75+
def test_malformed_topic_name(self, topic):
7676
with pytest.raises(ValueError):
7777
check_encoding(messaging.Message(topic=topic))
7878

@@ -92,6 +92,9 @@ def test_data_message(self):
9292
messaging.Message(topic='topic', data={'k1': 'v1', 'k2': 'v2'}),
9393
{'topic': 'topic', 'data': {'k1': 'v1', 'k2': 'v2'}})
9494

95+
def test_prefixed_topic(self):
96+
check_encoding(messaging.Message(topic='/topics/topic'), {'topic': 'topic'})
97+
9598

9699
class TestNotificationEncoder(object):
97100

0 commit comments

Comments
 (0)