Skip to content

Custom auth polish #537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- 'docs'

env:
BUILDER_VERSION: v0.9.21
BUILDER_VERSION: v0.9.53
BUILDER_SOURCE: releases
BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net
PACKAGE_NAME: aws-iot-device-sdk-python-v2
Expand Down
20 changes: 14 additions & 6 deletions awsiot/mqtt5_client_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
import awscrt.auth
import awscrt.io
import awscrt.mqtt5
import urllib.parse


DEFAULT_WEBSOCKET_MQTT_PORT = 443
DEFAULT_DIRECT_MQTT_PORT = 8883
Expand Down Expand Up @@ -629,8 +631,7 @@ def direct_with_custom_authorizer(
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
parameter. The signature must be based on the private key associated with the custom authorizer. The
signature must be base64 encoded.
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
the SDK will not do so for you.
Required if the custom authorizer has signing enabled.

auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
properties.
Expand All @@ -656,8 +657,12 @@ def direct_with_custom_authorizer(
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")

if auth_authorizer_signature is not None:
encoded_signature = auth_authorizer_signature
if "%" not in encoded_signature:
encoded_signature = urllib.parse.quote(encoded_signature)

username_string = _add_to_username_parameter(
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
username_string, encoded_signature, "x-amz-customauthorizer-signature=")

if auth_token_key_name is not None and auth_token_value is not None:
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")
Expand Down Expand Up @@ -707,8 +712,7 @@ def websockets_with_custom_authorizer(
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
parameter. The signature must be based on the private key associated with the custom authorizer. The
signature must be base64 encoded.
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
the SDK will not do so for you.
Required if the custom authorizer has signing enabled.

auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
properties.
Expand Down Expand Up @@ -738,8 +742,12 @@ def websockets_with_custom_authorizer(
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")

if auth_authorizer_signature is not None:
encoded_signature = auth_authorizer_signature
if "%" not in encoded_signature:
encoded_signature = urllib.parse.quote(encoded_signature)

username_string = _add_to_username_parameter(
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
username_string, encoded_signature, "x-amz-customauthorizer-signature=")

if auth_token_key_name is not None and auth_token_value is not None:
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")
Expand Down
13 changes: 8 additions & 5 deletions awsiot/mqtt_connection_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
import awscrt.auth
import awscrt.io
import awscrt.mqtt
import urllib.parse


def _check_required_kwargs(**kwargs):
Expand Down Expand Up @@ -529,8 +530,7 @@ def direct_with_custom_authorizer(
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
parameter. The signature must be based on the private key associated with the custom authorizer. The
signature must be base64 encoded.
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
the SDK will not do so for you.
Required if the custom authorizer has signing enabled.

auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
properties.
Expand Down Expand Up @@ -590,8 +590,7 @@ def websockets_with_custom_authorizer(
auth_authorizer_signature (`str`): The digital signature of the token value in the `auth_token_value`
parameter. The signature must be based on the private key associated with the custom authorizer. The
signature must be base64 encoded.
Required if the custom authorizer has signing enabled. It is strongly suggested to URL-encode this value;
the SDK will not do so for you.
Required if the custom authorizer has signing enabled.

auth_token_key_name (`str`): Key used to extract the custom authorizer token from MQTT username query-string
properties.
Expand Down Expand Up @@ -644,8 +643,12 @@ def _with_custom_authorizer(auth_username=None,
username_string, auth_authorizer_name, "x-amz-customauthorizer-name=")

if auth_authorizer_signature is not None:
encoded_signature = auth_authorizer_signature
if "%" not in encoded_signature:
encoded_signature = urllib.parse.quote(encoded_signature)

username_string = _add_to_username_parameter(
username_string, auth_authorizer_signature, "x-amz-customauthorizer-signature=")
username_string, encoded_signature, "x-amz-customauthorizer-signature=")

if auth_token_key_name is not None and auth_token_value is not None:
username_string = _add_to_username_parameter(username_string, auth_token_value, auth_token_key_name + "=")
Expand Down
41 changes: 40 additions & 1 deletion test/test_mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
CUSTOM_AUTHORIZER_NAME_UNSIGNED = os.environ.get("CUSTOM_AUTHORIZER_NAME_UNSIGNED")
CUSTOM_AUTHORIZER_PASSWORD = os.environ.get("CUSTOM_AUTHORIZER_PASSWORD")
CUSTOM_AUTHORIZER_SIGNATURE = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE")
CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED")
CUSTOM_AUTHORIZER_TOKEN_KEY_NAME = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_KEY_NAME")
CUSTOM_AUTHORIZER_TOKEN_VALUE = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_VALUE")

Expand All @@ -27,7 +28,7 @@ def has_custom_auth_environment():
return (CUSTOM_AUTHORIZER_ENDPOINT is not None) and (CUSTOM_AUTHORIZER_NAME_SIGNED is not None) and \
(CUSTOM_AUTHORIZER_NAME_UNSIGNED is not None) and (CUSTOM_AUTHORIZER_PASSWORD is not None) and \
(CUSTOM_AUTHORIZER_SIGNATURE is not None) and (CUSTOM_AUTHORIZER_TOKEN_KEY_NAME is not None) and \
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None)
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None) and (CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED is not None)

class Config:
cache = None
Expand Down Expand Up @@ -193,6 +194,25 @@ def test_mqtt311_builder_direct_signed_custom_authorizer(self):

self._test_connection(connection)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt311_builder_direct_signed_custom_authorizer_unencoded(self):
elg = EventLoopGroup()
resolver = DefaultHostResolver(elg)
bootstrap = ClientBootstrap(elg, resolver)

connection = mqtt_connection_builder.direct_with_custom_authorizer(
auth_username="",
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
client_id=create_client_id(),
client_bootstrap=bootstrap)

self._test_connection(connection)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt311_builder_direct_unsigned_custom_authorizer(self):
elg = EventLoopGroup()
Expand Down Expand Up @@ -244,3 +264,22 @@ def test_mqtt311_builder_websocket_signed_custom_authorizer(self):

self._test_connection(connection)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt311_builder_websocket_signed_custom_authorizer_unencoded(self):
elg = EventLoopGroup()
resolver = DefaultHostResolver(elg)
bootstrap = ClientBootstrap(elg, resolver)

connection = mqtt_connection_builder.websockets_with_custom_authorizer(
auth_username="",
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
client_id=create_client_id(),
client_bootstrap=bootstrap)

self._test_connection(connection)

47 changes: 46 additions & 1 deletion test/test_mqtt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CUSTOM_AUTHORIZER_NAME_UNSIGNED = os.environ.get("CUSTOM_AUTHORIZER_NAME_UNSIGNED")
CUSTOM_AUTHORIZER_PASSWORD = os.environ.get("CUSTOM_AUTHORIZER_PASSWORD")
CUSTOM_AUTHORIZER_SIGNATURE = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE")
CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED = os.environ.get("CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED")
CUSTOM_AUTHORIZER_TOKEN_KEY_NAME = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_KEY_NAME")
CUSTOM_AUTHORIZER_TOKEN_VALUE = os.environ.get("CUSTOM_AUTHORIZER_TOKEN_VALUE")

Expand All @@ -29,7 +30,7 @@ def has_custom_auth_environment():
return (CUSTOM_AUTHORIZER_ENDPOINT is not None) and (CUSTOM_AUTHORIZER_NAME_SIGNED is not None) and \
(CUSTOM_AUTHORIZER_NAME_UNSIGNED is not None) and (CUSTOM_AUTHORIZER_PASSWORD is not None) and \
(CUSTOM_AUTHORIZER_SIGNATURE is not None) and (CUSTOM_AUTHORIZER_TOKEN_KEY_NAME is not None) and \
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None)
(CUSTOM_AUTHORIZER_TOKEN_VALUE is not None) and (CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED is not None)

class Config:
cache = None
Expand Down Expand Up @@ -236,6 +237,28 @@ def test_mqtt5_builder_direct_signed_custom_authorizer(self):

self._test_connection(client, callbacks)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt5_builder_direct_signed_custom_authorizer_unencoded(self):
elg = EventLoopGroup()
resolver = DefaultHostResolver(elg)
bootstrap = ClientBootstrap(elg, resolver)
callbacks = Mqtt5TestCallbacks()

client = mqtt5_client_builder.direct_with_custom_authorizer(
auth_username="",
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
client_id=create_client_id(),
client_bootstrap=bootstrap,
on_lifecycle_connection_success=callbacks.on_lifecycle_connection_success,
on_lifecycle_stopped=callbacks.on_lifecycle_stopped)

self._test_connection(client, callbacks)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt5_builder_direct_unsigned_custom_authorizer(self):
elg = EventLoopGroup()
Expand Down Expand Up @@ -277,6 +300,28 @@ def test_mqtt5_builder_websocket_signed_custom_authorizer(self):

self._test_connection(client, callbacks)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt5_builder_websocket_signed_custom_authorizer_unencoded(self):
elg = EventLoopGroup()
resolver = DefaultHostResolver(elg)
bootstrap = ClientBootstrap(elg, resolver)
callbacks = Mqtt5TestCallbacks()

client = mqtt5_client_builder.websockets_with_custom_authorizer(
auth_username="",
auth_authorizer_name=CUSTOM_AUTHORIZER_NAME_SIGNED,
auth_authorizer_signature=CUSTOM_AUTHORIZER_SIGNATURE_UNENCODED,
auth_password=CUSTOM_AUTHORIZER_PASSWORD,
auth_token_key_name=CUSTOM_AUTHORIZER_TOKEN_KEY_NAME,
auth_token_value=CUSTOM_AUTHORIZER_TOKEN_VALUE,
endpoint=CUSTOM_AUTHORIZER_ENDPOINT,
client_id=create_client_id(),
client_bootstrap=bootstrap,
on_lifecycle_connection_success=callbacks.on_lifecycle_connection_success,
on_lifecycle_stopped=callbacks.on_lifecycle_stopped)

self._test_connection(client, callbacks)

@unittest.skipIf(not has_custom_auth_environment(), 'requires custom authentication env vars')
def test_mqtt5_builder_websocket_unsigned_custom_authorizer(self):
elg = EventLoopGroup()
Expand Down