Skip to content

Commit 13ca9a0

Browse files
authored
Custom auth polish (#537)
* Test driven development hur * Conditional signature encoding * Doc string updates
1 parent 685afa9 commit 13ca9a0

File tree

5 files changed

+109
-14
lines changed

5 files changed

+109
-14
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
- 'docs'
88

99
env:
10-
BUILDER_VERSION: v0.9.21
10+
BUILDER_VERSION: v0.9.53
1111
BUILDER_SOURCE: releases
1212
BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net
1313
PACKAGE_NAME: aws-iot-device-sdk-python-v2

awsiot/mqtt5_client_builder.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@
179179
import awscrt.auth
180180
import awscrt.io
181181
import awscrt.mqtt5
182+
import urllib.parse
183+
182184

183185
DEFAULT_WEBSOCKET_MQTT_PORT = 443
184186
DEFAULT_DIRECT_MQTT_PORT = 8883
@@ -629,8 +631,7 @@ def direct_with_custom_authorizer(
629631
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
630632
parameter. The signature must be based on the private key associated with the custom authorizer. The
631633
signature must be base64 encoded.
632-
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
633-
the SDK will not do so for you.
634+
Required if the custom authorizer has signing enabled.
634635
635636
auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
636637
properties.
@@ -656,8 +657,12 @@ def direct_with_custom_authorizer(
656657
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")
657658

658659
if auth_authorizer_signature is not None:
660+
encoded_signature = auth_authorizer_signature
661+
if "%" not in encoded_signature:
662+
encoded_signature = urllib.parse.quote(encoded_signature)
663+
659664
username_string = _add_to_username_parameter(
660-
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
665+
username_string, encoded_signature, "x-amz-customauthorizer-signature=")
661666

662667
if auth_token_key_name is not None and auth_token_value is not None:
663668
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")
@@ -707,8 +712,7 @@ def websockets_with_custom_authorizer(
707712
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
708713
parameter. The signature must be based on the private key associated with the custom authorizer. The
709714
signature must be base64 encoded.
710-
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
711-
the SDK will not do so for you.
715+
Required if the custom authorizer has signing enabled.
712716
713717
auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
714718
properties.
@@ -738,8 +742,12 @@ def websockets_with_custom_authorizer(
738742
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")
739743

740744
if auth_authorizer_signature is not None:
745+
encoded_signature = auth_authorizer_signature
746+
if "%" not in encoded_signature:
747+
encoded_signature = urllib.parse.quote(encoded_signature)
748+
741749
username_string = _add_to_username_parameter(
742-
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
750+
username_string, encoded_signature, "x-amz-customauthorizer-signature=")
743751

744752
if auth_token_key_name is not None and auth_token_value is not None:
745753
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")

awsiot/mqtt_connection_builder.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
import awscrt.auth
124124
import awscrt.io
125125
import awscrt.mqtt
126+
import urllib.parse
126127

127128

128129
def _check_required_kwargs(**kwargs):
@@ -529,8 +530,7 @@ def direct_with_custom_authorizer(
529530
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
530531
parameter. The signature must be based on the private key associated with the custom authorizer. The
531532
signature must be base64 encoded.
532-
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
533-
the SDK will not do so for you.
533+
Required if the custom authorizer has signing enabled.
534534
535535
auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
536536
properties.
@@ -590,8 +590,7 @@ def websockets_with_custom_authorizer(
590590
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
591591
parameter. The signature must be based on the private key associated with the custom authorizer. The
592592
signature must be base64 encoded.
593-
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
594-
the SDK will not do so for you.
593+
Required if the custom authorizer has signing enabled.
595594
596595
auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
597596
properties.
@@ -644,8 +643,12 @@ def _with_custom_authorizer(auth_username=None,
644643
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")
645644

646645
if auth_authorizer_signature is not None:
646+
encoded_signature = auth_authorizer_signature
647+
if "%" not in encoded_signature:
648+
encoded_signature = urllib.parse.quote(encoded_signature)
649+
647650
username_string = _add_to_username_parameter(
648-
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
651+
username_string, encoded_signature, "x-amz-customauthorizer-signature=")
649652

650653
if auth_token_key_name is not None and auth_token_value is not None:
651654
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")

test/test_mqtt.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
CUSTOM_AUTHORIZER_NAME_UNSIGNED = os.environ.get("CUSTOM_AUTHORIZER_NAME_UNSIGNED")
2020
CUSTOM_AUTHORIZER_PASSWORD = os.environ.get("CUSTOM_AUTHORIZER_PASSWORD")
2121
CUSTOM_AUTHORIZER_SIGNATURE = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE")
22+
CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED")
2223
CUSTOM_AUTHORIZER_TOKEN_KEY_NAME = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_KEY_NAME")
2324
CUSTOM_AUTHORIZER_TOKEN_VALUE = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_VALUE")
2425

@@ -27,7 +28,7 @@ def has_custom_auth_environment():
2728
return (CUSTOM_AUTHORIZER_ENDPOINT is not None) and (CUSTOM_AUTHORIZER_NAME_SIGNED is not None) and \
2829
(CUSTOM_AUTHORIZER_NAME_UNSIGNED is not None) and (CUSTOM_AUTHORIZER_PASSWORD is not None) and \
2930
(CUSTOM_AUTHORIZER_SIGNATURE is not None) and (CUSTOM_AUTHORIZER_TOKEN_KEY_NAME is not None) and \
30-
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None)
31+
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None) and (CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED is not None)
3132

3233
class Config:
3334
cache = None
@@ -193,6 +194,25 @@ def test_mqtt311_builder_direct_signed_custom_authorizer(self):
193194

194195
self._test_connection(connection)
195196

197+
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
198+
def test_mqtt311_builder_direct_signed_custom_authorizer_unencoded(self):
199+
elg = EventLoopGroup()
200+
resolver = DefaultHostResolver(elg)
201+
bootstrap = ClientBootstrap(elg, resolver)
202+
203+
connection = mqtt_connection_builder.direct_with_custom_authorizer(
204+
auth_username="",
205+
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
206+
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
207+
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
208+
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
209+
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
210+
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
211+
client_id=create_client_id(),
212+
client_bootstrap=bootstrap)
213+
214+
self._test_connection(connection)
215+
196216
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
197217
def test_mqtt311_builder_direct_unsigned_custom_authorizer(self):
198218
elg = EventLoopGroup()
@@ -244,3 +264,22 @@ def test_mqtt311_builder_websocket_signed_custom_authorizer(self):
244264

245265
self._test_connection(connection)
246266

267+
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
268+
def test_mqtt311_builder_websocket_signed_custom_authorizer_unencoded(self):
269+
elg = EventLoopGroup()
270+
resolver = DefaultHostResolver(elg)
271+
bootstrap = ClientBootstrap(elg, resolver)
272+
273+
connection = mqtt_connection_builder.websockets_with_custom_authorizer(
274+
auth_username="",
275+
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
276+
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
277+
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
278+
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
279+
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
280+
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
281+
client_id=create_client_id(),
282+
client_bootstrap=bootstrap)
283+
284+
self._test_connection(connection)
285+

test/test_mqtt5.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
CUSTOM_AUTHORIZER_NAME_UNSIGNED = os.environ.get("CUSTOM_AUTHORIZER_NAME_UNSIGNED")
2222
CUSTOM_AUTHORIZER_PASSWORD = os.environ.get("CUSTOM_AUTHORIZER_PASSWORD")
2323
CUSTOM_AUTHORIZER_SIGNATURE = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE")
24+
CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED")
2425
CUSTOM_AUTHORIZER_TOKEN_KEY_NAME = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_KEY_NAME")
2526
CUSTOM_AUTHORIZER_TOKEN_VALUE = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_VALUE")
2627

@@ -29,7 +30,7 @@ def has_custom_auth_environment():
2930
return (CUSTOM_AUTHORIZER_ENDPOINT is not None) and (CUSTOM_AUTHORIZER_NAME_SIGNED is not None) and \
3031
(CUSTOM_AUTHORIZER_NAME_UNSIGNED is not None) and (CUSTOM_AUTHORIZER_PASSWORD is not None) and \
3132
(CUSTOM_AUTHORIZER_SIGNATURE is not None) and (CUSTOM_AUTHORIZER_TOKEN_KEY_NAME is not None) and \
32-
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None)
33+
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None) and (CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED is not None)
3334

3435
class Config:
3536
cache = None
@@ -236,6 +237,28 @@ def test_mqtt5_builder_direct_signed_custom_authorizer(self):
236237

237238
self._test_connection(client, callbacks)
238239

240+
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
241+
def test_mqtt5_builder_direct_signed_custom_authorizer_unencoded(self):
242+
elg = EventLoopGroup()
243+
resolver = DefaultHostResolver(elg)
244+
bootstrap = ClientBootstrap(elg, resolver)
245+
callbacks = Mqtt5TestCallbacks()
246+
247+
client = mqtt5_client_builder.direct_with_custom_authorizer(
248+
auth_username="",
249+
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
250+
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
251+
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
252+
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
253+
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
254+
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
255+
client_id=create_client_id(),
256+
client_bootstrap=bootstrap,
257+
on_lifecycle_connection_success=callbacks.on_lifecycle_connection_success,
258+
on_lifecycle_stopped=callbacks.on_lifecycle_stopped)
259+
260+
self._test_connection(client, callbacks)
261+
239262
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
240263
def test_mqtt5_builder_direct_unsigned_custom_authorizer(self):
241264
elg = EventLoopGroup()
@@ -277,6 +300,28 @@ def test_mqtt5_builder_websocket_signed_custom_authorizer(self):
277300

278301
self._test_connection(client, callbacks)
279302

303+
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
304+
def test_mqtt5_builder_websocket_signed_custom_authorizer_unencoded(self):
305+
elg = EventLoopGroup()
306+
resolver = DefaultHostResolver(elg)
307+
bootstrap = ClientBootstrap(elg, resolver)
308+
callbacks = Mqtt5TestCallbacks()
309+
310+
client = mqtt5_client_builder.websockets_with_custom_authorizer(
311+
auth_username="",
312+
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
313+
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
314+
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
315+
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
316+
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
317+
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
318+
client_id=create_client_id(),
319+
client_bootstrap=bootstrap,
320+
on_lifecycle_connection_success=callbacks.on_lifecycle_connection_success,
321+
on_lifecycle_stopped=callbacks.on_lifecycle_stopped)
322+
323+
self._test_connection(client, callbacks)
324+
280325
@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
281326
def test_mqtt5_builder_websocket_unsigned_custom_authorizer(self):
282327
elg = EventLoopGroup()

0 commit comments

Comments
 (0)