From bd3d4aea3fe99ae1381798627fc778c97aeac01f Mon Sep 17 00:00:00 2001 From: Bartosz Prokop Date: Thu, 16 Dec 2021 19:39:56 +0100 Subject: [PATCH] Feature[PAM]: Revoke token functionality --- .github/workflows/run_acceptance_tests.yml | 3 + .pubnub.yml | 17 ++- CHANGELOG.md | 6 + pubnub/endpoints/access/revoke.py | 30 ----- pubnub/endpoints/access/revoke_token.py | 43 +++++++ pubnub/enums.py | 2 + pubnub/managers.py | 5 +- pubnub/models/consumer/v3/access_manager.py | 8 ++ pubnub/pubnub_core.py | 8 +- setup.py | 2 +- tests/acceptance/pam/steps/given_steps.py | 61 ++++++++-- tests/acceptance/pam/steps/then_steps.py | 24 +++- tests/acceptance/pam/steps/when_steps.py | 32 +++++- tests/functional/test_revoke.py | 108 ------------------ tests/helper.py | 29 ++++- tests/integrational/asyncio/test_pam.py | 10 -- .../native_sync/pam/revoke_token.yaml | 84 ++++++++++++++ .../native_sync/test_revoke_v3.py | 29 +++++ 18 files changed, 324 insertions(+), 177 deletions(-) delete mode 100644 pubnub/endpoints/access/revoke.py create mode 100644 pubnub/endpoints/access/revoke_token.py delete mode 100644 tests/functional/test_revoke.py create mode 100644 tests/integrational/fixtures/native_sync/pam/revoke_token.yaml create mode 100644 tests/integrational/native_sync/test_revoke_v3.py diff --git a/.github/workflows/run_acceptance_tests.yml b/.github/workflows/run_acceptance_tests.yml index cd62e23f..e7403bce 100644 --- a/.github/workflows/run_acceptance_tests.yml +++ b/.github/workflows/run_acceptance_tests.yml @@ -22,7 +22,10 @@ jobs: token: ${{ secrets.GH_TOKEN }} - name: Install Python dependencies and run acceptance tests run: | + cp sdk-specifications/features/access/authorization-failure-reporting.feature tests/acceptance/pam cp sdk-specifications/features/access/grant-token.feature tests/acceptance/pam + cp sdk-specifications/features/access/revoke-token.feature tests/acceptance/pam + sudo pip3 install -r requirements-dev.txt behave --junit tests/acceptance/pam - name: Expose acceptance tests reports diff --git a/.pubnub.yml b/.pubnub.yml index 719d041a..2ace9b93 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,5 +1,5 @@ name: python -version: 5.4.0 +version: 5.5.0 schema: 1 scm: github.com/pubnub/python sdks: @@ -18,7 +18,7 @@ sdks: distributions: - distribution-type: library distribution-repository: package - package-name: pubnub-5.1.3 + package-name: pubnub-5.5.0 location: https://pypi.org/project/pubnub/ supported-platforms: supported-operating-systems: @@ -97,8 +97,8 @@ sdks: - distribution-type: library distribution-repository: git release - package-name: pubnub-5.1.3 - location: https://github.com/pubnub/python/releases/download/v5.1.3/pubnub-5.1.3.tar.gz + package-name: pubnub-5.5.0 + location: https://github.com/pubnub/python/releases/download/v5.5.0/pubnub-5.5.0.tar.gz supported-platforms: supported-operating-systems: Linux: @@ -169,6 +169,14 @@ sdks: license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt is-required: Required changelog: + - date: 2021-12-16 + version: v5.5.0 + changes: + + - date: 2021-12-16 + version: v5.4.0 + changes: + - version: v5.4.0 date: 2021-10-07 changes: @@ -479,6 +487,7 @@ features: - ACCESS-GRANT-TOKEN - ACCESS-PARSE-TOKEN - ACCESS-SET-TOKEN + - ACCESS-REVOKE-TOKEN channel-groups: - CHANNEL-GROUPS-ADD-CHANNELS - CHANNEL-GROUPS-REMOVE-CHANNELS diff --git a/CHANGELOG.md b/CHANGELOG.md index 601935d3..1729245b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v5.5.0 +December 16 2021 + +## v5.4.0 +December 16 2021 + ## [v5.4.0](https://github.com/pubnub/python/releases/tag/v5.4.0) [Full Changelog](https://github.com/pubnub/python/compare/v5.3.1...v5.4.0) diff --git a/pubnub/endpoints/access/revoke.py b/pubnub/endpoints/access/revoke.py deleted file mode 100644 index db7568e3..00000000 --- a/pubnub/endpoints/access/revoke.py +++ /dev/null @@ -1,30 +0,0 @@ -from pubnub.endpoints.access.grant import Grant -from pubnub.enums import PNOperationType - - -class Revoke(Grant): - def __init__(self, pubnub): - Grant.__init__(self, pubnub) - self._read = False - self._write = False - self._manage = False - self._get = False - self._update = False - self._join = False - - self._sort_params = True - - def read(self, flag): - raise NotImplementedError - - def write(self, flag): - raise NotImplementedError - - def manage(self, flag): - raise NotImplementedError - - def operation_type(self): - return PNOperationType.PNAccessManagerRevoke - - def name(self): - return "Revoke" diff --git a/pubnub/endpoints/access/revoke_token.py b/pubnub/endpoints/access/revoke_token.py new file mode 100644 index 00000000..2479879d --- /dev/null +++ b/pubnub/endpoints/access/revoke_token.py @@ -0,0 +1,43 @@ +from pubnub.enums import PNOperationType, HttpMethod +from pubnub.endpoints.endpoint import Endpoint +from pubnub.models.consumer.v3.access_manager import PNRevokeTokenResult +from pubnub import utils + + +class RevokeToken(Endpoint): + REVOKE_TOKEN_PATH = "/v3/pam/%s/grant/%s" + + def __init__(self, pubnub, token): + Endpoint.__init__(self, pubnub) + self.token = token + + def validate_params(self): + self.validate_subscribe_key() + self.validate_secret_key() + + def create_response(self, envelope): + return PNRevokeTokenResult(envelope) + + def is_auth_required(self): + return False + + def request_timeout(self): + return self.pubnub.config.non_subscribe_request_timeout + + def connect_timeout(self): + return self.pubnub.config.connect_timeout + + def http_method(self): + return HttpMethod.DELETE + + def custom_params(self): + return {} + + def build_path(self): + return RevokeToken.REVOKE_TOKEN_PATH % (self.pubnub.config.subscribe_key, utils.url_encode(self.token)) + + def operation_type(self): + return PNOperationType.PNAccessManagerRevokeToken + + def name(self): + return "RevokeToken" diff --git a/pubnub/enums.py b/pubnub/enums.py index b9836b4b..63c2935c 100644 --- a/pubnub/enums.py +++ b/pubnub/enums.py @@ -69,7 +69,9 @@ class PNOperationType(object): PNFireOperation = 25 PNSignalOperation = 26 + PNAccessManagerRevokeToken = 40 PNAccessManagerGrantToken = 41 + PNAddMessageAction = 42 PNGetMessageActions = 43 PNDeleteMessageAction = 44 diff --git a/pubnub/managers.py b/pubnub/managers.py index 70925186..181e122d 100644 --- a/pubnub/managers.py +++ b/pubnub/managers.py @@ -466,6 +466,9 @@ def endpoint_name_for_operation(operation_type): PNOperationType.PNAccessManagerRevoke: 'pam', PNOperationType.PNTimeOperation: 'pam', + PNOperationType.PNAccessManagerGrantToken: 'pamv3', + PNOperationType.PNAccessManagerRevokeToken: 'pamv3', + PNOperationType.PNSignalOperation: 'sig', PNOperationType.PNSetUuidMetadataOperation: 'obj', @@ -488,8 +491,6 @@ def endpoint_name_for_operation(operation_type): PNOperationType.PNRemoveMembershipsOperation: 'obj', PNOperationType.PNManageMembershipsOperation: 'obj', - PNOperationType.PNAccessManagerGrantToken: 'pamv3', - PNOperationType.PNAddMessageAction: 'msga', PNOperationType.PNGetMessageActions: 'msga', PNOperationType.PNDeleteMessageAction: 'msga', diff --git a/pubnub/models/consumer/v3/access_manager.py b/pubnub/models/consumer/v3/access_manager.py index 5f49a17d..88e41068 100644 --- a/pubnub/models/consumer/v3/access_manager.py +++ b/pubnub/models/consumer/v3/access_manager.py @@ -21,3 +21,11 @@ def __str__(self): def get_token(self): return self.token + + +class PNRevokeTokenResult: + def __init__(self, result): + self.status = result['status'] + + def __str__(self): + return "Revoke token success with status: %s" % self.status diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index e08e9322..4d074df8 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -27,7 +27,7 @@ from .endpoints.access.audit import Audit from .endpoints.access.grant import Grant from .endpoints.access.grant_token import GrantToken -from .endpoints.access.revoke import Revoke +from .endpoints.access.revoke_token import RevokeToken from .endpoints.channel_groups.add_channel_to_channel_group import AddChannelToChannelGroup from .endpoints.channel_groups.list_channels_in_channel_group import ListChannelsInChannelGroup from .endpoints.channel_groups.remove_channel_from_channel_group import RemoveChannelFromChannelGroup @@ -65,7 +65,7 @@ class PubNubCore: """A base class for PubNub Python API implementations""" - SDK_VERSION = "5.4.0" + SDK_VERSION = "5.5.0" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 @@ -170,8 +170,8 @@ def grant(self): def grant_token(self): return GrantToken(self) - def revoke(self): - return Revoke(self) + def revoke_token(self, token): + return RevokeToken(self, token) def audit(self): return Audit(self) diff --git a/setup.py b/setup.py index 3f8e606e..c20dd754 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='pubnub', - version='5.4.0', + version='5.5.0', description='PubNub Real-time push service in the cloud', author='PubNub', author_email='support@pubnub.com', diff --git a/tests/acceptance/pam/steps/given_steps.py b/tests/acceptance/pam/steps/given_steps.py index 0b63ba71..7ef17ad7 100644 --- a/tests/acceptance/pam/steps/given_steps.py +++ b/tests/acceptance/pam/steps/given_steps.py @@ -4,7 +4,7 @@ from pubnub.models.consumer.v3.channel import Channel from pubnub.models.consumer.v3.group import Group from pubnub.models.consumer.v3.uuid import UUID -from tests.helper import PAM_TOKEN_WITH_ALL_PERMS_GRANTED +from tests.helper import PAM_TOKEN_WITH_ALL_PERMS_GRANTED, PAM_TOKEN_EXPIRED, PAM_TOKEN_WITH_PUBLISH_ENABLED @given("I have a keyset with access manager enabled") @@ -22,6 +22,33 @@ def step_impl(context): } +@given("I have a keyset with access manager enabled - without secret key") +def step_impl(context): + pubnub_instance = PubNub(pnconf_pam_acceptance_copy()) + pubnub_instance.config.secret_key = None + context.peer_without_secret_key = pubnub_instance + + +@given("a valid token with permissions to publish with channel {channel}") +def step_impl(context, channel): + context.token = PAM_TOKEN_WITH_PUBLISH_ENABLED + + +@given("an expired token with permissions to publish with channel {channel}") +def step_impl(context, channel): + context.token = PAM_TOKEN_EXPIRED + + +@given("the token string {token}") +def step_impl(context, token): + context.token = token.strip("'") + + +@given("a token") +def step_impl(context): + context.token = PAM_TOKEN_WITH_PUBLISH_ENABLED + + @given("the TTL {ttl}") def step_impl(context, ttl): context.TTL = ttl @@ -209,17 +236,17 @@ def step_impl(context): @given("I have a known token containing UUID pattern Permissions") def step_impl(context): - context.token_to_parse = PAM_TOKEN_WITH_ALL_PERMS_GRANTED + context.token = PAM_TOKEN_WITH_ALL_PERMS_GRANTED @given("I have a known token containing UUID resource permissions") def step_impl(context): - context.token_to_parse = PAM_TOKEN_WITH_ALL_PERMS_GRANTED + context.token = PAM_TOKEN_WITH_ALL_PERMS_GRANTED @given("I have a known token containing an authorized UUID") def step_impl(context): - context.token_to_parse = PAM_TOKEN_WITH_ALL_PERMS_GRANTED + context.token = PAM_TOKEN_WITH_ALL_PERMS_GRANTED @given("token resource permission READ") @@ -254,29 +281,43 @@ def step_impl(context): @given("the error status code is {status}") def step_impl(context, status): - assert context.grant_call_error["status"] == int(status) + assert context.pam_call_error["status"] == int(status) @given("the error message is {err_msg}") def step_impl(context, err_msg): - assert context.grant_call_error["error"]["message"] == err_msg.strip("'") + assert context.pam_call_error["error"]["message"] == err_msg.strip("'") @given("the error source is {err_source}") def step_impl(context, err_source): - assert context.grant_call_error["error"]["source"] == err_source.strip("'") + assert context.pam_call_error["error"]["source"] == err_source.strip("'") @given("the error detail message is {err_detail}") def step_impl(context, err_detail): - assert context.grant_call_error["error"]["details"][0]["message"] == err_detail.strip("'") + err_detail = err_detail.strip("'") + if err_detail == "not empty": + assert context.pam_call_error["error"]["details"][0]["message"] + else: + assert context.pam_call_error["error"]["details"][0]["message"] == err_detail @given("the error detail location is {err_detail_location}") def step_impl(context, err_detail_location): - assert context.grant_call_error["error"]["details"][0]["location"] == err_detail_location.strip("'") + assert context.pam_call_error["error"]["details"][0]["location"] == err_detail_location.strip("'") @given("the error detail location type is {err_detail_location_type}") def step_impl(context, err_detail_location_type): - assert context.grant_call_error["error"]["details"][0]["locationType"] == err_detail_location_type.strip("'") + assert context.pam_call_error["error"]["details"][0]["locationType"] == err_detail_location_type.strip("'") + + +@given("the error service is {service_name}") +def step_impl(context, service_name): + assert context.pam_call_error["service"] == service_name.strip("'") + + +@given("the auth error message is {message}") +def step_impl(context, message): + assert context.pam_call_error["message"] == message.strip("'") diff --git a/tests/acceptance/pam/steps/then_steps.py b/tests/acceptance/pam/steps/then_steps.py index ee2c1e7e..6f3d4b8a 100644 --- a/tests/acceptance/pam/steps/then_steps.py +++ b/tests/acceptance/pam/steps/then_steps.py @@ -1,4 +1,6 @@ +import json from behave import then +from pubnub.exceptions import PubNubException @then("the token contains the TTL 60") @@ -58,10 +60,26 @@ def step_impl(context, channel_group): @then("I see the error message {error} and details {error_details}") def step_impl(context, error, error_details): - assert context.grant_call_error["error"]["message"] == error.strip("'") - assert context.grant_call_error["error"]["details"][0]["message"] == error_details.strip("'") + assert context.pam_call_error["error"]["message"] == error.strip("'") + assert context.pam_call_error["error"]["details"][0]["message"] == error_details.strip("'") @then("an error is returned") def step_impl(context): - assert context.grant_call_error + assert context.pam_call_error + + +@then("I get confirmation that token has been revoked") +def step_impl(context): + assert context.revoke_result.result.status == 200 + + +@then("an auth error is returned") +def step_impl(context): + assert isinstance(context.pam_call_result, PubNubException) + context.pam_call_error = json.loads(context.pam_call_result._errormsg) + + +@then("the result is successful") +def step_impl(context): + assert context.publish_result.result.timetoken diff --git a/tests/acceptance/pam/steps/when_steps.py b/tests/acceptance/pam/steps/when_steps.py index b88e044a..3ab4b526 100644 --- a/tests/acceptance/pam/steps/when_steps.py +++ b/tests/acceptance/pam/steps/when_steps.py @@ -2,6 +2,7 @@ from behave import when import pubnub +from pubnub.exceptions import PubNubException def execute_pam_call(context): @@ -22,14 +23,41 @@ def step_impl(context): try: execute_pam_call(context) except pubnub.exceptions.PubNubException as err: - context.grant_call_error = json.loads(err._errormsg) + context.pam_call_error = json.loads(err._errormsg) @when("I parse the token") def step_impl(context): - context.parsed_token = context.peer.parse_token(context.token_to_parse) + context.parsed_token = context.peer.parse_token(context.token) @when("I grant a token specifying those permissions") def step_impl(context): execute_pam_call(context) + + +@when("I publish a message using that auth token with channel {channel}") +def step_impl(context, channel): + context.peer_without_secret_key.set_token(context.token) + context.publish_result = context.peer_without_secret_key.publish().channel( + channel.strip("'") + ).message("Tejjjjj").sync() + + +@when("I attempt to publish a message using that auth token with channel {channel}") +def step_impl(context, channel): + try: + context.peer_without_secret_key.set_token(context.token) + context.pam_call_result = context.peer_without_secret_key.publish().channel( + channel.strip("'") + ).message("Tejjjjj").sync() + except PubNubException as err: + context.pam_call_result = err + + +@when("I revoke a token") +def step_impl(context): + try: + context.revoke_result = context.peer.revoke_token(context.token).sync() + except PubNubException as err: + context.pam_call_error = json.loads(err._errormsg) diff --git a/tests/functional/test_revoke.py b/tests/functional/test_revoke.py deleted file mode 100644 index 94408f84..00000000 --- a/tests/functional/test_revoke.py +++ /dev/null @@ -1,108 +0,0 @@ -import unittest - -from pubnub import utils -from pubnub.endpoints.access.revoke import Revoke -from pubnub.enums import HttpMethod - -try: - from mock import MagicMock -except ImportError: - from unittest.mock import MagicMock - -from pubnub.pubnub import PubNub -from tests.helper import pnconf_pam_copy, sdk_name -from pubnub.managers import TelemetryManager - -pnconf = pnconf_pam_copy() -# pnconf.secret_key = None - - -class TestRevoke(unittest.TestCase): - def setUp(self): - - self.pubnub = MagicMock( - spec=PubNub, - config=pnconf, - sdk_name=sdk_name, - timestamp=MagicMock(return_value=123), - uuid=None - ) - self.pubnub.uuid = "UUID_RevokeUnitTest" - self.pubnub._telemetry_manager = TelemetryManager() - self.revoke = Revoke(self.pubnub) - - def test_revoke_to_channel(self): - self.revoke.channels('ch') - - self.assertEqual(self.revoke.build_path(), Revoke.GRANT_PATH % pnconf.subscribe_key) - - pam_args = utils.prepare_pam_arguments({ - 'timestamp': 123, - 'channel': 'ch', - 'r': '0', - 'w': '0', - 'm': '0', - 'g': '0', - 'u': '0', - 'j': '0', - 'pnsdk': sdk_name, - 'uuid': self.pubnub.uuid - }) - sign_input = HttpMethod.string(self.revoke.http_method()).upper() + "\n" + \ - pnconf.publish_key + "\n" + \ - self.revoke.build_path() + "\n" + \ - pam_args + "\n" - self.assertEqual(self.revoke.build_params_callback()({}), { - 'pnsdk': sdk_name, - 'uuid': self.pubnub.uuid, - 'timestamp': '123', - 'channel': 'ch', - 'r': '0', - 'w': '0', - 'm': '0', - 'g': '0', - 'u': '0', - 'j': '0', - 'signature': "v2." + utils.sign_sha256(pnconf.secret_key, sign_input).rstrip("=") - }) - - def test_revoke_read_to_channel(self): - def revoke(): - self.revoke.channels('ch').read(True).write(True) - - self.assertRaises(NotImplementedError, revoke) - - def test_grant_read_and_write_to_channel_group(self): - self.revoke.channel_groups(['gr1', 'gr2']) - - self.assertEqual(self.revoke.build_path(), Revoke.GRANT_PATH % pnconf.subscribe_key) - - pam_args = utils.prepare_pam_arguments({ - 'r': '0', - 'w': '0', - 'm': '0', - 'g': '0', - 'u': '0', - 'j': '0', - 'timestamp': 123, - 'channel-group': 'gr1,gr2', - 'pnsdk': sdk_name, - 'uuid': self.pubnub.uuid - }) - sign_input = HttpMethod.string(self.revoke.http_method()).upper() + "\n" + \ - pnconf.publish_key + "\n" + \ - self.revoke.build_path() + "\n" + \ - pam_args + "\n" - self.assertEqual(self.revoke.build_params_callback()({}), { - 'pnsdk': sdk_name, - 'uuid': self.pubnub.uuid, - 'r': '0', - 'w': '0', - 'm': '0', - 'g': '0', - 'u': '0', - 'j': '0', - 'timestamp': '123', - 'channel-group': 'gr1,gr2', - 'signature': "v2." + utils.sign_sha256(pnconf.secret_key, sign_input).rstrip("=") - }) diff --git a/tests/helper.py b/tests/helper.py index 1054e68c..97e44f91 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -10,9 +10,23 @@ PAM_TOKEN_WITH_ALL_PERMS_GRANTED = ( - 'qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQVDdXNyoENzcGOgRHV1aWShZ' - 'nV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaGFubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoW' - 'pedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc' + "qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQVDdXNyoENzcGOgRHV1aWShZ" + "nV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaGFubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoW" + "pedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc" +) + +PAM_TOKEN_EXPIRED = ( + "qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQ" + "VDdXNyoENzcGOgRHV1aWShZnV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaG" + "FubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoWpedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1" + "dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc" +) + +PAM_TOKEN_WITH_PUBLISH_ENABLED = ( + "qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQ" + "VDdXNyoENzcGOgRHV1aWShZnV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaG" + "FubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoWpedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1" + "dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc" ) @@ -58,6 +72,11 @@ pnconf_pam.enable_subscribe = False +pnconf_pam_stub = PNConfiguration() +pnconf_pam_stub.publish_key = "pub-stub" +pnconf_pam_stub.subscribe_key = "sub-c-stub" +pnconf_pam_stub.secret_key = "sec-c-stub" + pnconf_ssl = PNConfiguration() pnconf_ssl.publish_key = pub_key pnconf_ssl.subscribe_key = sub_key @@ -116,6 +135,10 @@ def pnconf_pam_copy(): return deepcopy(pnconf_pam) +def pnconf_pam_stub_copy(): + return deepcopy(pnconf_pam_stub) + + def pnconf_pam_acceptance_copy(): pam_config = copy(pnconf_pam) pam_config.origin = "localhost:8090" diff --git a/tests/integrational/asyncio/test_pam.py b/tests/integrational/asyncio/test_pam.py index fb44dfbe..638728ed 100644 --- a/tests/integrational/asyncio/test_pam.py +++ b/tests/integrational/asyncio/test_pam.py @@ -25,16 +25,6 @@ async def test_global_level(event_loop): assert env.result.manage_enabled is False assert env.result.delete_enabled is False - env = await pubnub.revoke().future() - - assert isinstance(env.result, PNAccessManagerGrantResult) - assert len(env.result.channels) == 0 - assert len(env.result.groups) == 0 - assert env.result.read_enabled is False - assert env.result.write_enabled is False - assert env.result.manage_enabled is False - assert env.result.delete_enabled is False - await pubnub.stop() diff --git a/tests/integrational/fixtures/native_sync/pam/revoke_token.yaml b/tests/integrational/fixtures/native_sync/pam/revoke_token.yaml new file mode 100644 index 00000000..482c2e05 --- /dev/null +++ b/tests/integrational/fixtures/native_sync/pam/revoke_token.yaml @@ -0,0 +1,84 @@ +interactions: +- request: + body: '{"ttl": 60, "permissions": {"resources": {"channels": {"test_channel": + 207}, "groups": {}, "uuids": {}, "users": {}, "spaces": {}}, "patterns": {"channels": + {}, "groups": {}, "uuids": {}, "users": {}, "spaces": {}}, "meta": {}, "uuid": + "test"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '244' + Content-type: + - application/json + User-Agent: + - PubNub-Python/5.4.0 + method: POST + uri: https://ps.pndsn.com/v3/pam/sub-c-stub/grant + response: + body: + string: '{"data":{"message":"Success","token":"qEF2AkF0GmGFTxxDdHRsGDxDcmVzpURjaGFuoWx0ZXN0X2NoYW5uZWwYz0NncnCgQ3VzcqBDc3BjoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDdXNyoENzcGOgRHV1aWSgRG1ldGGgRHV1aWRkdGVzdENzaWdYIMD7y8nuLytwo00ZNv2Dv9_nQU46Zg5f7qql6Yw9dkhr"},"service":"Access + Manager","status":200}' + headers: + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - GET + Access-Control-Allow-Origin: + - '*' + Cache-Control: + - no-cache, no-store, must-revalidate + Connection: + - keep-alive + Content-Length: + - '281' + Content-Type: + - text/javascript; charset=UTF-8 + Date: + - Fri, 05 Nov 2021 15:34:52 GMT + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - PubNub-Python/5.4.0 + method: DELETE + uri: https://ps.pndsn.com/v3/pam/sub-c-stub/grant/qEF2AkF0GmGFTxxDdHRsGDxDcmVzpURjaGFuoWx0ZXN0X2NoYW5uZWwYz0NncnCgQ3VzcqBDc3BjoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDdXNyoENzcGOgRHV1aWSgRG1ldGGgRHV1aWRkdGVzdENzaWdYIMD7y8nuLytwo00ZNv2Dv9_nQU46Zg5f7qql6Yw9dkhr + response: + body: + string: '{"data":{"message":"Success"},"service":"Access Manager","status":200}' + headers: + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - GET + Access-Control-Allow-Origin: + - '*' + Cache-Control: + - no-cache, no-store, must-revalidate + Connection: + - keep-alive + Content-Length: + - '70' + Content-Type: + - text/javascript; charset=UTF-8 + Date: + - Fri, 05 Nov 2021 15:34:52 GMT + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integrational/native_sync/test_revoke_v3.py b/tests/integrational/native_sync/test_revoke_v3.py new file mode 100644 index 00000000..3f983ce5 --- /dev/null +++ b/tests/integrational/native_sync/test_revoke_v3.py @@ -0,0 +1,29 @@ +from pubnub.pubnub import PubNub +from pubnub.models.consumer.v3.channel import Channel +from tests.integrational.vcr_helper import pn_vcr +from tests.helper import pnconf_pam_stub_copy +from pubnub.models.consumer.v3.access_manager import PNGrantTokenResult, PNRevokeTokenResult + +pubnub = PubNub(pnconf_pam_stub_copy()) +pubnub.config.uuid = "test_revoke" + + +@pn_vcr.use_cassette( + 'tests/integrational/fixtures/native_sync/pam/revoke_token.yaml', + filter_query_parameters=['uuid', 'seqn', 'pnsdk', 'timestamp', 'signature'] +) +def test_grant_and_revoke_token(): + + grant_envelope = pubnub.grant_token()\ + .channels([Channel.id("test_channel").read().write().manage().update().join().delete()])\ + .authorized_uuid("test")\ + .ttl(60)\ + .sync() + + assert isinstance(grant_envelope.result, PNGrantTokenResult) + token = grant_envelope.result.get_token() + assert token + + revoke_envelope = pubnub.revoke_token(token).sync() + assert isinstance(revoke_envelope.result, PNRevokeTokenResult) + assert revoke_envelope.result.status == 200