Skip to content

Commit

Permalink
Add support for setting the apns-push-type header via device data (#309)
Browse files Browse the repository at this point in the history
Signed-off-by: N-Pex <[email protected]>
Co-authored-by: Brendan Abolivier <[email protected]>
  • Loading branch information
N-Pex and babolivier authored Jul 1, 2022
1 parent cae8e8a commit 2a33681
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ For either type, it can accept:
- the `platform` parameter which determines whether the production or sandbox
APNS environment is used.
Valid values are 'production' or 'sandbox'. If not provided, 'production' is used.

- the `push_type` parameter which determines what value for the `apns-push-type` header is sent to
APNs. If not provided, the header is not sent.

### gcm

Expand Down
1 change: 1 addition & 0 deletions changelog.d/309.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new `push_type` configuration option for APNs apps, to control the value of the `apns-push-type` header when sending requests.
22 changes: 21 additions & 1 deletion sygnal/apnspushkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import aioapns
from aioapns import APNs, NotificationRequest
from aioapns.common import NotificationResult
from aioapns.common import NotificationResult, PushType
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import load_pem_x509_certificate
from opentracing import Span, logs, tags
Expand Down Expand Up @@ -109,8 +109,18 @@ class ApnsPushkin(ConcurrencyLimitedPushkin):
"key_id",
"keyfile",
"topic",
"push_type",
} | ConcurrencyLimitedPushkin.UNDERSTOOD_CONFIG_FIELDS

APNS_PUSH_TYPES = {
"alert": PushType.ALERT,
"background": PushType.BACKGROUND,
"voip": PushType.VOIP,
"complication": PushType.COMPLICATION,
"fileprovider": PushType.FILEPROVIDER,
"mdm": PushType.MDM,
}

def __init__(self, name: str, sygnal: "Sygnal", config: Dict[str, Any]) -> None:
super().__init__(name, sygnal, config)

Expand Down Expand Up @@ -188,6 +198,15 @@ def __init__(self, name: str, sygnal: "Sygnal", config: Dict[str, Any]) -> None:
loop=loop,
)

push_type = self.get_config("push_type", str)
if not push_type:
self.push_type = None
else:
if push_type not in self.APNS_PUSH_TYPES.keys():
raise PushkinSetupException(f"Invalid value for push_type: {push_type}")

self.push_type = self.APNS_PUSH_TYPES[push_type]

# without this, aioapns will retry every second forever.
self.apns_client.pool.max_connection_attempts = 3

Expand Down Expand Up @@ -230,6 +249,7 @@ async def _dispatch_request(
message=shaved_payload,
priority=prio,
notification_id=notif_id,
push_type=self.push_type,
)

try:
Expand Down
59 changes: 58 additions & 1 deletion tests/test_apns.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
# limitations under the License.
from unittest.mock import MagicMock, patch

from aioapns.common import NotificationResult
from aioapns.common import NotificationResult, PushType

from sygnal import apnstruncate
from sygnal.apnspushkin import ApnsPushkin

from tests import testutils

PUSHKIN_ID = "com.example.apns"
PUSHKIN_ID_WITH_PUSH_TYPE = "com.example.apns.push_type"

TEST_CERTFILE_PATH = "/path/to/my/certfile.pem"

Expand All @@ -47,6 +48,12 @@
"data": {"default_payload": None},
}

DEVICE_EXAMPLE_FOR_PUSH_TYPE_PUSHKIN = {
"app_id": "com.example.apns.push_type",
"pushkey": "spqr",
"pushkey_ts": 42,
}


class ApnsTestCase(testutils.TestCase):
def setUp(self):
Expand All @@ -64,9 +71,11 @@ def setUp(self):

self.apns_pushkin_snotif = MagicMock()
test_pushkin = self.get_test_pushkin(PUSHKIN_ID)
test_pushkin_push_type = self.get_test_pushkin(PUSHKIN_ID_WITH_PUSH_TYPE)
# type safety: using ignore here due to mypy not handling monkeypatching,
# see https://github.com/python/mypy/issues/2427
test_pushkin._send_notification = self.apns_pushkin_snotif # type: ignore[assignment] # noqa: E501
test_pushkin_push_type._send_notification = self.apns_pushkin_snotif # type: ignore[assignment] # noqa: E501

def get_test_pushkin(self, name: str) -> ApnsPushkin:
test_pushkin = self.sygnal.pushkins[name]
Expand All @@ -76,6 +85,11 @@ def get_test_pushkin(self, name: str) -> ApnsPushkin:
def config_setup(self, config):
super().config_setup(config)
config["apps"][PUSHKIN_ID] = {"type": "apns", "certfile": TEST_CERTFILE_PATH}
config["apps"][PUSHKIN_ID_WITH_PUSH_TYPE] = {
"type": "apns",
"certfile": TEST_CERTFILE_PATH,
"push_type": "alert",
}

def test_payload_truncation(self):
"""
Expand Down Expand Up @@ -333,3 +347,46 @@ def test_retry_on_5xx(self):
# Assert
self.assertGreater(method.call_count, 1)
self.assertEqual(502, resp)

def test_expected_with_push_type(self):
"""
Tests the expected case: a good response from APNS means we pass on
a good response to the homeserver.
"""
# Arrange
method = self.apns_pushkin_snotif
method.side_effect = testutils.make_async_magic_mock(
NotificationResult("notID", "200")
)

# Act
resp = self._request(
self._make_dummy_notification([DEVICE_EXAMPLE_FOR_PUSH_TYPE_PUSHKIN])
)

# Assert
self.assertEqual(1, method.call_count)
((notification_req,), _kwargs) = method.call_args

self.assertEqual(
{
"room_id": "!slw48wfj34rtnrf:example.com",
"event_id": "$qTOWWTEL48yPm3uT-gdNhFcoHxfKbZuqRVnnWWSkGBs",
"aps": {
"alert": {
"loc-key": "MSG_FROM_USER_IN_ROOM_WITH_CONTENT",
"loc-args": [
"Major Tom",
"Mission Control",
"I'm floating in a most peculiar way.",
],
},
"badge": 3,
},
},
notification_req.message,
)

self.assertEqual(PushType.ALERT, notification_req.push_type)

self.assertEqual({"rejected": []}, resp)

0 comments on commit 2a33681

Please sign in to comment.