From 35cc6541e4dce175a4fd1ccdd44c07d7f70da786 Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Thu, 4 Apr 2024 20:42:24 +0200 Subject: [PATCH] remove Python2 crumbs (#262) * use new unittest.mock everywhere, not only in accountAdmin/base.py * remove old "from __future__ import" statements * remove six, pyflakes --- .github/workflows/publish.yml | 2 +- duo_client/__init__.py | 1 - duo_client/accounts.py | 1 - duo_client/admin.py | 105 +++++++++--------- duo_client/auth.py | 1 - duo_client/auth_v1.py | 2 - duo_client/client.py | 38 +++---- duo_client/https_wrapper.py | 24 ++-- duo_client/logs/__init__.py | 1 - examples/Accounts/create_child_account.py | 2 +- examples/Accounts/delete_child_account.py | 1 - .../get_billing_and_telephony_credits.py | 3 - examples/Accounts/retrieve_account_list.py | 1 - .../Admin/create_integration_sso_generic.py | 3 - examples/Admin/create_user_and_phone.py | 3 - examples/Admin/log_examples.py | 5 - examples/Admin/policies.py | 1 - examples/Admin/policies_advanced.py | 1 - examples/Admin/report_auths_by_country.py | 4 - examples/Admin/report_user_activity.py | 3 - examples/Admin/report_user_by_email.py | 2 - examples/Admin/report_users_and_phones.py | 3 - examples/Admin/trust_monitor_events.py | 4 - examples/Admin/update_phone_names.py | 1 - examples/Auth/async_basic_user_mfa.py | 1 - examples/Auth/basic_user_mfa.py | 1 - examples/Auth/basic_user_mfa_token.py | 1 - examples/splunk/splunk.py | 9 +- requirements-dev.txt | 1 - requirements.txt | 1 - setup.cfg | 1 - setup.py | 1 - tests/admin/test_endpoints.py | 2 + tests/admin/test_integration.py | 4 +- tests/admin/test_logo.py | 4 +- tests/admin/test_user_groups.py | 2 + tests/admin/test_user_phones.py | 2 + tests/admin/test_user_tokens.py | 2 + tests/admin/test_user_u2f.py | 2 + tests/admin/test_user_webauthn.py | 2 + tests/test_client.py | 12 +- tests/test_https_wrapper.py | 3 +- tests/util.py | 14 +-- 43 files changed, 114 insertions(+), 163 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 808f64cd..d5556f10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine six + pip install setuptools wheel twine - name: Generate SBOM run: | pip install cyclonedx-bom==3.11.7 diff --git a/duo_client/__init__.py b/duo_client/__init__.py index 16b1a253..3fb5d01a 100644 --- a/duo_client/__init__.py +++ b/duo_client/__init__.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from .accounts import Accounts from .admin import Admin from .auth import Auth diff --git a/duo_client/accounts.py b/duo_client/accounts.py index 0af5d450..555dcfa1 100644 --- a/duo_client/accounts.py +++ b/duo_client/accounts.py @@ -3,7 +3,6 @@ """ -from __future__ import absolute_import from . import client class Accounts(client.Client): diff --git a/duo_client/admin.py b/duo_client/admin.py index f27bf855..1549fdda 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -173,17 +173,14 @@ data - Detailed error code such as {"code": 40401, "message": "Resource not found", "stat": "FAIL"} """ -from __future__ import absolute_import - -import six.moves.urllib from . import client, Accounts from .logs.telephony import Telephony -import six import warnings import json import time import base64 +import urllib.parse from datetime import datetime, timedelta, timezone USER_STATUS_ACTIVE = "active" @@ -232,7 +229,7 @@ def api_call(self, method, path, params): @classmethod def _canonicalize_ip_whitelist(klass, ip_whitelist): - if isinstance(ip_whitelist, six.string_types): + if isinstance(ip_whitelist, str): return ip_whitelist else: return ','.join(ip_whitelist) @@ -240,7 +237,7 @@ def _canonicalize_ip_whitelist(klass, ip_whitelist): @staticmethod def _canonicalize_bypass_codes(codes): - if isinstance(codes, six.string_types): + if isinstance(codes, str): return codes else: return ','.join([str(int(code)) for code in codes]) @@ -727,7 +724,7 @@ def get_user_by_id(self, user_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id response = self.json_api_call('GET', path, {}) return response @@ -879,7 +876,7 @@ def update_user(self, user_id, username=None, realname=None, status=None, Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id params = {} if username is not None: @@ -917,7 +914,7 @@ def delete_user(self, user_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id return self.json_api_call('DELETE', path, {}) @@ -960,7 +957,7 @@ def add_user_bypass_codes(self, user_id, count=None, valid_secs=None, remaining_ Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/bypass_codes' params = {} @@ -975,7 +972,7 @@ def add_user_bypass_codes(self, user_id, count=None, valid_secs=None, remaining_ if codes is not None: params['codes'] = self._canonicalize_bypass_codes(codes) - + if preserve_existing is not None: params['preserve_existing'] = preserve_existing @@ -993,7 +990,7 @@ def get_user_bypass_codes_iterator(self, user_id): Notes: Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/bypass_codes' return self.json_paging_api_call('GET', path, {}) @@ -1014,7 +1011,7 @@ def get_user_bypass_codes(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/bypass_codes' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1031,7 +1028,7 @@ def get_user_phones_iterator(self, user_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/phones' return self.json_paging_api_call('GET', path, {}) @@ -1049,7 +1046,7 @@ def get_user_phones(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/phones' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1067,7 +1064,7 @@ def add_user_phone(self, user_id, phone_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/phones' params = { 'phone_id': phone_id, @@ -1085,7 +1082,7 @@ def delete_user_phone(self, user_id, phone_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/phones/' + phone_id params = {} return self.json_api_call('DELETE', path, @@ -1101,7 +1098,7 @@ def get_user_tokens_iterator(self, user_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/tokens' return self.json_paging_api_call('GET', path, {}) @@ -1119,7 +1116,7 @@ def get_user_tokens(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/tokens' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1137,7 +1134,7 @@ def add_user_token(self, user_id, token_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/tokens' params = { 'token_id': token_id, @@ -1155,8 +1152,8 @@ def delete_user_token(self, user_id, token_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) - token_id = six.moves.urllib.parse.quote_plus(str(token_id)) + user_id = urllib.parse.quote_plus(str(user_id)) + token_id = urllib.parse.quote_plus(str(token_id)) path = '/admin/v1/users/' + user_id + '/tokens/' + token_id return self.json_api_call('DELETE', path, {}) @@ -1172,7 +1169,7 @@ def get_user_u2ftokens_iterator(self, user_id): Notes: Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/u2ftokens' return self.json_paging_api_call('GET', path, {}) @@ -1192,7 +1189,7 @@ def get_user_u2ftokens(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/u2ftokens' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1211,7 +1208,7 @@ def get_user_webauthncredentials_iterator(self, user_id): Notes: Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/webauthncredentials' return self.json_paging_api_call('GET', path, {}) @@ -1232,7 +1229,7 @@ def get_user_webauthncredentials(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/webauthncredentials' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1249,7 +1246,7 @@ def get_user_groups_iterator(self, user_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/groups' return self.json_paging_api_call('GET', path, {}) @@ -1267,7 +1264,7 @@ def get_user_groups(self, user_id, limit=None, offset=0): """ (limit, offset) = self.normalize_paging_args(limit, offset) if limit: - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/groups' return self.json_api_call( 'GET', path, {'limit': limit, 'offset': offset}) @@ -1285,7 +1282,7 @@ def add_user_group(self, user_id, group_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/groups' params = {'group_id': group_id} return self.json_api_call('POST', path, params) @@ -1301,7 +1298,7 @@ def delete_user_group(self, user_id, group_id): Raises RuntimeError on error. """ - user_id = six.moves.urllib.parse.quote_plus(str(user_id)) + user_id = urllib.parse.quote_plus(str(user_id)) path = '/admin/v1/users/' + user_id + '/groups/' + group_id params = {} return self.json_api_call('DELETE', path, params) @@ -1317,7 +1314,7 @@ def get_endpoint(self, epkey): Raises: RuntimeError: if the request returns a non-200 status response. """ - escaped_epkey = six.moves.urllib.parse.quote_plus(str(epkey)) + escaped_epkey = urllib.parse.quote_plus(str(epkey)) path = '/admin/v1/endpoints/' + escaped_epkey return self.json_api_call('GET', path, {}) @@ -1481,7 +1478,7 @@ def update_phone(self, phone_id, Raises RuntimeError on error. """ - phone_id = six.moves.urllib.parse.quote_plus(str(phone_id)) + phone_id = urllib.parse.quote_plus(str(phone_id)) path = '/admin/v1/phones/' + phone_id params = {} if number is not None: @@ -1690,7 +1687,7 @@ def delete_desktoptoken(self, desktoptoken_id): Raises RuntimeError on error. """ - path = '/admin/v1/desktoptokens/' + six.moves.urllib.parse.quote_plus(desktoptoken_id) + path = '/admin/v1/desktoptokens/' + urllib.parse.quote_plus(desktoptoken_id) params = {} return self.json_api_call('DELETE', path, params) @@ -1708,7 +1705,7 @@ def update_desktoptoken(self, Raises RuntimeError on error. """ - desktoptoken_id = six.moves.urllib.parse.quote_plus(str(desktoptoken_id)) + desktoptoken_id = urllib.parse.quote_plus(str(desktoptoken_id)) path = '/admin/v1/desktoptokens/' + desktoptoken_id params = {} if platform is not None: @@ -1736,7 +1733,7 @@ def activate_desktoptoken(self, desktoptoken_id, valid_secs=None): params = {} if valid_secs: params['valid_secs'] = str(valid_secs) - quoted_id = six.moves.urllib.parse.quote_plus(desktoptoken_id) + quoted_id = urllib.parse.quote_plus(desktoptoken_id) response = self.json_api_call('POST', '/admin/v1/desktoptokens/%s/activate' % quoted_id, params) @@ -1782,7 +1779,7 @@ def get_token_by_id(self, token_id): Returns a token object. """ - token_id = six.moves.urllib.parse.quote_plus(str(token_id)) + token_id = urllib.parse.quote_plus(str(token_id)) path = '/admin/v1/tokens/' + token_id params = {} response = self.json_api_call('GET', path, @@ -1811,7 +1808,7 @@ def delete_token(self, token_id): token_id - Token ID """ - token_id = six.moves.urllib.parse.quote_plus(str(token_id)) + token_id = urllib.parse.quote_plus(str(token_id)) path = '/admin/v1/tokens/' + token_id return self.json_api_call('DELETE', path, {}) @@ -1897,7 +1894,7 @@ def update_token(self, token_id, totp_step=None): Raises RuntimeError on error. """ - token_id = six.moves.urllib.parse.quote_plus(str(token_id)) + token_id = urllib.parse.quote_plus(str(token_id)) path = '/admin/v1/tokens/' + token_id params = {} if totp_step is not None: @@ -1936,7 +1933,7 @@ def resync_hotp_token(self, token_id, code1, code2, code3): Returns nothing on success. """ - token_id = six.moves.urllib.parse.quote_plus(str(token_id)) + token_id = urllib.parse.quote_plus(str(token_id)) path = '/admin/v1/tokens/' + token_id + '/resync' params = {'code1': code1, 'code2': code2, 'code3': code3} return self.json_api_call('POST', path, params) @@ -2663,7 +2660,7 @@ def delete_integration(self, integration_key): Raises RuntimeError on error. """ - integration_key = six.moves.urllib.parse.quote_plus(str(integration_key)) + integration_key = urllib.parse.quote_plus(str(integration_key)) path = '/admin/v2/integrations/%s' % integration_key return self.json_api_call( 'DELETE', @@ -2730,7 +2727,7 @@ def update_integration(self, Raises RuntimeError on error. """ - integration_key = six.moves.urllib.parse.quote_plus(str(integration_key)) + integration_key = urllib.parse.quote_plus(str(integration_key)) path = '/admin/v2/integrations/%s' % integration_key params = {} if name is not None: @@ -2834,7 +2831,7 @@ def get_admin(self, admin_id): Raises RuntimeError on error. """ - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/%s' % admin_id response = self.json_api_call('GET', path, {}) return response @@ -2886,7 +2883,7 @@ def update_admin(self, admin_id, Raises RuntimeError on error. """ - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/%s' % admin_id params = {} if name is not None: @@ -2908,7 +2905,7 @@ def delete_admin(self, admin_id): Raises RuntimeError on error. """ - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/%s' % admin_id return self.json_api_call('DELETE', path, {}) @@ -2920,7 +2917,7 @@ def reset_admin(self, admin_id): Raises RuntimeError on error. """ - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/%s/reset' % admin_id return self.json_api_call('POST', path, {}) @@ -3009,7 +3006,7 @@ def get_external_password_mgmt_status_for_admin(self, admin_id): Raises RuntimeError on error. """ - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/{}/password_mgmt'.format(admin_id) response = self.json_api_call('GET', path, {}) return response @@ -3038,7 +3035,7 @@ def update_admin_password_mgmt_status( if has_external_password_mgmt is not None: params['has_external_password_mgmt'] = str(has_external_password_mgmt) - admin_id = six.moves.urllib.parse.quote_plus(str(admin_id)) + admin_id = urllib.parse.quote_plus(str(admin_id)) path = '/admin/v1/admins/{}/password_mgmt'.format(admin_id) response = self.json_api_call('POST', path, params) return response @@ -3155,7 +3152,7 @@ def get_u2ftoken_by_id(self, registration_id): Notes: Raises RuntimeError on error. """ - registration_id = six.moves.urllib.parse.quote_plus(str(registration_id)) + registration_id = urllib.parse.quote_plus(str(registration_id)) path = '/admin/v1/u2ftokens/' + registration_id response = self.json_api_call('GET', path, {}) return response @@ -3171,7 +3168,7 @@ def delete_u2ftoken(self, registration_id): Raises RuntimeError on error. """ registration_id = \ - six.moves.urllib.parse.quote_plus(str(registration_id)) + urllib.parse.quote_plus(str(registration_id)) path = '/admin/v1/u2ftokens/' + registration_id return self.json_api_call('DELETE', path, {}) @@ -3189,7 +3186,7 @@ def get_webauthncredential_by_id(self, webauthnkey): Raises RuntimeError on error. """ webauthnkey = \ - six.moves.urllib.parse.quote_plus(str(webauthnkey)) + urllib.parse.quote_plus(str(webauthnkey)) path = '/admin/v1/webauthncredentials/' + webauthnkey response = self.json_api_call('GET', path, {}) return response @@ -3206,7 +3203,7 @@ def delete_webauthncredential(self, webauthnkey): Raises RuntimeError on error. """ webauthnkey = \ - six.moves.urllib.parse.quote_plus(str(webauthnkey)) + urllib.parse.quote_plus(str(webauthnkey)) path = '/admin/v1/webauthncredentials/' + webauthnkey response = self.json_api_call('DELETE', path, {}) return response @@ -3255,7 +3252,7 @@ def delete_bypass_code_by_id(self, bypass_code_id): Raises RuntimeError on error. """ bypass_code_id = \ - six.moves.urllib.parse.quote_plus(str(bypass_code_id)) + urllib.parse.quote_plus(str(bypass_code_id)) path = '/admin/v1/bypass_codes/' + bypass_code_id response = self.json_api_call('DELETE', path, {}) return response @@ -3273,7 +3270,7 @@ def sync_user(self, username, directory_key): params = { 'username': username, } - directory_key = six.moves.urllib.parse.quote_plus(directory_key) + directory_key = urllib.parse.quote_plus(directory_key) path = ( '/admin/v1/users/directorysync/{directory_key}/syncuser').format( directory_key=directory_key) @@ -3386,7 +3383,7 @@ def get_trust_monitor_events_by_offset( ) def _quote_policy_id(self, policy_key): - return six.moves.urllib.parse.quote_plus("{}".format(policy_key)) + return urllib.parse.quote_plus("{}".format(policy_key)) def get_policies_v2_iterator(self): """ diff --git a/duo_client/auth.py b/duo_client/auth.py index 8397ffc8..984d4c4a 100644 --- a/duo_client/auth.py +++ b/duo_client/auth.py @@ -3,7 +3,6 @@ """ -from __future__ import absolute_import from . import client class Auth(client.Client): diff --git a/duo_client/auth_v1.py b/duo_client/auth_v1.py index a622296f..2f969d67 100644 --- a/duo_client/auth_v1.py +++ b/duo_client/auth_v1.py @@ -3,8 +3,6 @@ """ -from __future__ import absolute_import - from . import client diff --git a/duo_client/client.py b/duo_client/client.py index 75554f69..7af805da 100644 --- a/duo_client/client.py +++ b/duo_client/client.py @@ -1,10 +1,6 @@ """ Low level functions for generating Duo Web API calls and parsing results. """ -from __future__ import absolute_import -from __future__ import print_function -import six - __version__ = '5.3.0' import base64 @@ -13,6 +9,7 @@ import email.utils import hashlib import hmac +import http.client import json import os import random @@ -20,6 +17,7 @@ import socket import ssl import sys +import urllib.parse try: # For the optional demonstration CLI program. @@ -48,8 +46,8 @@ def canon_params(params): # http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 args = [] for (key, vals) in sorted( - (six.moves.urllib.parse.quote(key, '~'), vals) for (key, vals) in list(params.items())): - for val in sorted(six.moves.urllib.parse.quote(val, '~') for val in vals): + (urllib.parse.quote(key, '~'), vals) for (key, vals) in list(params.items())): + for val in sorted(urllib.parse.quote(val, '~') for val in vals): args.append('%s=%s' % (key, val)) return '&'.join(args) @@ -168,18 +166,18 @@ def sign(ikey, skey, method, host, uri, date, sig_version, params, body=None, Return basic authorization header line with a Duo Web API signature. """ canonical = canonicalize(method, host, uri, params, date, sig_version, body=body, additional_headers=additional_headers) - if isinstance(skey, six.text_type): + if isinstance(skey, str): skey = skey.encode('utf-8') - if isinstance(canonical, six.text_type): + if isinstance(canonical, str): canonical = canonical.encode('utf-8') sig = hmac.new(skey, canonical, digestmod) auth = '%s:%s' % (ikey, sig.hexdigest()) - if isinstance(auth, six.text_type): + if isinstance(auth, str): auth = auth.encode('utf-8') b64 = base64.b64encode(auth) - if not isinstance(b64, six.text_type): + if not isinstance(b64, str): b64 = b64.decode('utf-8') return 'Basic %s' % b64 @@ -199,11 +197,11 @@ def encode(value): value = 'false' elif isinstance(value, int): value = str(value) - if isinstance(value, six.text_type): + if isinstance(value, str): return value.encode("utf-8") return value def to_list(value): - if value is None or isinstance(value, six.string_types): + if value is None or isinstance(value, str): return [value] return value return dict( @@ -348,17 +346,17 @@ def api_call( headers['Content-type'] = 'application/json' else: headers['Content-type'] = 'application/x-www-form-urlencoded' - body = six.moves.urllib.parse.urlencode(params, doseq=True) + body = urllib.parse.urlencode(params, doseq=True) uri = path else: body = None - uri = path + '?' + six.moves.urllib.parse.urlencode(params, doseq=True) + uri = path + '?' + urllib.parse.urlencode(params, doseq=True) encoded_headers = {} for k, v in headers.items(): - if isinstance(k, six.text_type): + if isinstance(k, str): k = k.encode('ascii') - if isinstance(v, six.text_type): + if isinstance(v, str): v = v.encode('ascii') encoded_headers[k] = v @@ -385,14 +383,14 @@ def _connect(self): # Create outer HTTP(S) connection. if self.ca_certs == 'HTTP': - conn = six.moves.http_client.HTTPConnection(host, port) + conn = http.client.HTTPConnection(host, port) elif self.ca_certs == 'DISABLE': kwargs = {} if hasattr(ssl, '_create_unverified_context'): # httplib.HTTPSConnection validates certificates by # default in Python 2.7.9+. kwargs['context'] = ssl._create_unverified_context() # noqa: DUO122, explicitly disabled for testing scenarios - conn = six.moves.http_client.HTTPSConnection(host, port, **kwargs) + conn = http.client.HTTPSConnection(host, port, **kwargs) else: conn = CertValidatingHTTPSConnection(host, port, @@ -556,7 +554,7 @@ def raise_error(msg): error.reason = response.reason error.data = data raise error - if not isinstance(data, six.text_type): + if not isinstance(data, str): data = data.decode('utf-8') if response.status != 200: try: @@ -611,7 +609,7 @@ def output_response(response, data, headers=None): if val is not None: print('%s: %s' % (header, val)) try: - if not isinstance(data, six.text_type): + if not isinstance(data, str): data = data.decode('utf-8') data = json.loads(data) data = json.dumps(data, sort_keys=True, indent=4) diff --git a/duo_client/https_wrapper.py b/duo_client/https_wrapper.py index 6a58f09c..c64b5d7b 100644 --- a/duo_client/https_wrapper.py +++ b/duo_client/https_wrapper.py @@ -17,17 +17,15 @@ # """Extensions to allow HTTPS requests with SSL certificate validation.""" -from __future__ import absolute_import - -import six.moves.http_client +import http.client import re import socket -import six.moves.urllib import ssl +import urllib.error +import urllib.request - -class InvalidCertificateException(six.moves.http_client.HTTPException): +class InvalidCertificateException(http.client.HTTPException): """Raised when a certificate is provided with an invalid hostname.""" def __init__(self, host, cert, reason): @@ -37,7 +35,7 @@ def __init__(self, host, cert, reason): host: The hostname the connection was made to. cert: The SSL certificate (as a dictionary) the host returned. """ - six.moves.http_client.HTTPException.__init__(self) + http.client.HTTPException.__init__(self) self.host = host self.cert = cert self.reason = reason @@ -49,10 +47,10 @@ def __str__(self): (self.host, self.reason, self.cert)) -class CertValidatingHTTPSConnection(six.moves.http_client.HTTPConnection): +class CertValidatingHTTPSConnection(http.client.HTTPConnection): """An HTTPConnection that connects over SSL and validates certificates.""" - default_port = six.moves.http_client.HTTPS_PORT + default_port = http.client.HTTPS_PORT def __init__(self, host, port=None, key_file=None, cert_file=None, ca_certs=None, strict=None, **kwargs): @@ -68,7 +66,7 @@ def __init__(self, host, port=None, key_file=None, cert_file=None, strict: When true, causes BadStatusLine to be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1 status line. """ - six.moves.http_client.HTTPConnection.__init__(self, host, port, strict, **kwargs) + http.client.HTTPConnection.__init__(self, host, port, strict, **kwargs) context = ssl.SSLContext(ssl.PROTOCOL_TLS) if cert_file: context.load_cert_chain(cert_file, key_file) @@ -128,7 +126,7 @@ def connect(self): raise InvalidCertificateException(hostname, cert, 'hostname mismatch') -class CertValidatingHTTPSHandler(six.moves.urllib.request.HTTPSHandler): +class CertValidatingHTTPSHandler(urllib.request.HTTPSHandler): """An HTTPHandler that validates SSL certificates.""" def __init__(self, **kwargs): @@ -143,10 +141,10 @@ def http_class_wrapper(host, **kwargs): return CertValidatingHTTPSConnection(host, **full_kwargs) try: return self.do_open(http_class_wrapper, req) - except six.moves.urllib.error.URLError as e: + except urllib.error.URLError as e: if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1: raise InvalidCertificateException(req.host, '', e.reason.args[1]) raise - https_request = six.moves.urllib.request.HTTPSHandler.do_request_ + https_request = urllib.request.HTTPSHandler.do_request_ diff --git a/duo_client/logs/__init__.py b/duo_client/logs/__init__.py index 14066716..730060e1 100644 --- a/duo_client/logs/__init__.py +++ b/duo_client/logs/__init__.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from .telephony import Telephony __all__ = [ diff --git a/examples/Accounts/create_child_account.py b/examples/Accounts/create_child_account.py index 38e69667..e66f5520 100644 --- a/examples/Accounts/create_child_account.py +++ b/examples/Accounts/create_child_account.py @@ -3,7 +3,7 @@ """ import duo_client -import os + import sys import getpass diff --git a/examples/Accounts/delete_child_account.py b/examples/Accounts/delete_child_account.py index 07ea663b..16f3416d 100644 --- a/examples/Accounts/delete_child_account.py +++ b/examples/Accounts/delete_child_account.py @@ -3,7 +3,6 @@ """ import duo_client -import os import sys import getpass diff --git a/examples/Accounts/get_billing_and_telephony_credits.py b/examples/Accounts/get_billing_and_telephony_credits.py index dc8a2387..35f09f82 100644 --- a/examples/Accounts/get_billing_and_telephony_credits.py +++ b/examples/Accounts/get_billing_and_telephony_credits.py @@ -1,10 +1,7 @@ #!/usr/bin/env python -from __future__ import absolute_import -from __future__ import print_function import sys import duo_client -from six.moves import input EDITIONS = { "ENTERPRISE": "Duo Essentials", diff --git a/examples/Accounts/retrieve_account_list.py b/examples/Accounts/retrieve_account_list.py index b8f1474e..7118b8d9 100644 --- a/examples/Accounts/retrieve_account_list.py +++ b/examples/Accounts/retrieve_account_list.py @@ -3,7 +3,6 @@ """ import duo_client -import os import sys import getpass diff --git a/examples/Admin/create_integration_sso_generic.py b/examples/Admin/create_integration_sso_generic.py index b0c9313f..642f7394 100644 --- a/examples/Admin/create_integration_sso_generic.py +++ b/examples/Admin/create_integration_sso_generic.py @@ -1,11 +1,8 @@ #!/usr/bin/python -from __future__ import absolute_import -from __future__ import print_function import pprint import sys import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/create_user_and_phone.py b/examples/Admin/create_user_and_phone.py index b5cb1364..c82d38f7 100755 --- a/examples/Admin/create_user_and_phone.py +++ b/examples/Admin/create_user_and_phone.py @@ -1,11 +1,8 @@ #!/usr/bin/python -from __future__ import absolute_import -from __future__ import print_function import pprint import sys import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) def get_next_arg(prompt): diff --git a/examples/Admin/log_examples.py b/examples/Admin/log_examples.py index d9621b77..e467d04c 100644 --- a/examples/Admin/log_examples.py +++ b/examples/Admin/log_examples.py @@ -1,13 +1,8 @@ #!/usr/bin/env python -from __future__ import absolute_import, print_function - import csv -import json import sys from datetime import datetime, timedelta, timezone -from six.moves import input - import duo_client argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/policies.py b/examples/Admin/policies.py index 55bde725..031720f1 100755 --- a/examples/Admin/policies.py +++ b/examples/Admin/policies.py @@ -2,7 +2,6 @@ import sys import json import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/policies_advanced.py b/examples/Admin/policies_advanced.py index cea77126..82bb30d3 100755 --- a/examples/Admin/policies_advanced.py +++ b/examples/Admin/policies_advanced.py @@ -1,7 +1,6 @@ """ Example of Duo Admin API policies operations """ -import sys import json import duo_client from getpass import getpass diff --git a/examples/Admin/report_auths_by_country.py b/examples/Admin/report_auths_by_country.py index f3d2ecd5..a6e0a04e 100755 --- a/examples/Admin/report_auths_by_country.py +++ b/examples/Admin/report_auths_by_country.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -from __future__ import print_function -from __future__ import absolute_import import csv import sys import duo_client -import json -from six.moves import input argv_iter = iter(sys.argv[1:]) def get_next_arg(prompt): diff --git a/examples/Admin/report_user_activity.py b/examples/Admin/report_user_activity.py index d5e61a3b..9a6c9391 100755 --- a/examples/Admin/report_user_activity.py +++ b/examples/Admin/report_user_activity.py @@ -1,11 +1,8 @@ #!/usr/bin/env python -from __future__ import absolute_import -from __future__ import print_function import sys from datetime import datetime, timezone import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/report_user_by_email.py b/examples/Admin/report_user_by_email.py index 6c971a3e..9fd709ad 100755 --- a/examples/Admin/report_user_by_email.py +++ b/examples/Admin/report_user_by_email.py @@ -2,12 +2,10 @@ """ Script to illustrate how to retrieve a user from the Duo Admin API using the associated email address""" -from __future__ import absolute_import, print_function import sys import getpass import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/report_users_and_phones.py b/examples/Admin/report_users_and_phones.py index 10b32e55..591c6d22 100755 --- a/examples/Admin/report_users_and_phones.py +++ b/examples/Admin/report_users_and_phones.py @@ -1,11 +1,8 @@ #!/usr/bin/env python -from __future__ import absolute_import -from __future__ import print_function import csv import sys import duo_client -from six.moves import input argv_iter = iter(sys.argv[1:]) def get_next_arg(prompt): diff --git a/examples/Admin/trust_monitor_events.py b/examples/Admin/trust_monitor_events.py index 12a8662c..33ad8e9a 100755 --- a/examples/Admin/trust_monitor_events.py +++ b/examples/Admin/trust_monitor_events.py @@ -1,14 +1,10 @@ #!/usr/bin/env python """Print Duo Trust Monitor Events which surfaced within the past two weeks.""" -from __future__ import absolute_import, print_function - import json import sys from datetime import datetime, timedelta, timezone -from six.moves import input - from duo_client import Admin argv_iter = iter(sys.argv[1:]) diff --git a/examples/Admin/update_phone_names.py b/examples/Admin/update_phone_names.py index e073cd71..0e093499 100755 --- a/examples/Admin/update_phone_names.py +++ b/examples/Admin/update_phone_names.py @@ -2,7 +2,6 @@ Script to pull list of all phones and modify the name of each """ -from __future__ import absolute_import, print_function import sys import getpass diff --git a/examples/Auth/async_basic_user_mfa.py b/examples/Auth/async_basic_user_mfa.py index 10b7106c..87b5627e 100644 --- a/examples/Auth/async_basic_user_mfa.py +++ b/examples/Auth/async_basic_user_mfa.py @@ -3,7 +3,6 @@ """ import duo_client -import os import sys import getpass diff --git a/examples/Auth/basic_user_mfa.py b/examples/Auth/basic_user_mfa.py index 31604cc6..b8c007b4 100644 --- a/examples/Auth/basic_user_mfa.py +++ b/examples/Auth/basic_user_mfa.py @@ -3,7 +3,6 @@ """ import duo_client -import os import sys import getpass diff --git a/examples/Auth/basic_user_mfa_token.py b/examples/Auth/basic_user_mfa_token.py index 86320855..36cfb702 100644 --- a/examples/Auth/basic_user_mfa_token.py +++ b/examples/Auth/basic_user_mfa_token.py @@ -4,7 +4,6 @@ """ import duo_client -import os import sys import getpass diff --git a/examples/splunk/splunk.py b/examples/splunk/splunk.py index 4bc80031..5e744002 100755 --- a/examples/splunk/splunk.py +++ b/examples/splunk/splunk.py @@ -1,17 +1,14 @@ #!/usr/bin/python -from __future__ import absolute_import -from __future__ import print_function - import os import sys import time import duo_client -from six.moves.configparser import ConfigParser -from six.moves.urllib.parse import urlparse +from configparser import ConfigParser +from urllib.parse import urlparse -class BaseLog(object): +class BaseLog: def __init__(self, admin_api, path, logname): self.admin_api = admin_api diff --git a/requirements-dev.txt b/requirements-dev.txt index 69551138..1aebdd6e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,5 @@ nose2 flake8 pytz>=2022.1 -mock dlint freezegun diff --git a/requirements.txt b/requirements.txt index 5c847809..49fe098d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -six setuptools diff --git a/setup.cfg b/setup.cfg index fb2cc3e3..68822221 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,2 @@ [bdist_rpm] release=1%%{?dist} -requires=python-six diff --git a/setup.py b/setup.py index 236dcf8c..6747dffe 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from setuptools import setup import os.path diff --git a/tests/admin/test_endpoints.py b/tests/admin/test_endpoints.py index 8a041701..84bea675 100644 --- a/tests/admin/test_endpoints.py +++ b/tests/admin/test_endpoints.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/admin/test_integration.py b/tests/admin/test_integration.py index 77dcc6b7..4a63528b 100644 --- a/tests/admin/test_integration.py +++ b/tests/admin/test_integration.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import json import duo_client.admin @@ -75,4 +77,4 @@ def test_update_integration_success(self): ) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/admin/test_logo.py b/tests/admin/test_logo.py index 5834ff53..7771771c 100644 --- a/tests/admin/test_logo.py +++ b/tests/admin/test_logo.py @@ -2,7 +2,7 @@ from .base import TestAdmin import os import base64 -import six +import urllib.parse class TestLogo(TestAdmin): @@ -18,7 +18,7 @@ def test_update_logo(self): # Prep validation text: base64_logo = base64.b64encode(logo_file) - base64_logo = six.moves.urllib.parse.quote_plus(base64_logo) + base64_logo = urllib.parse.quote_plus(base64_logo) # Validate response: self.assertTrue( diff --git a/tests/admin/test_user_groups.py b/tests/admin/test_user_groups.py index 0a68f2e0..eff9f485 100644 --- a/tests/admin/test_user_groups.py +++ b/tests/admin/test_user_groups.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/admin/test_user_phones.py b/tests/admin/test_user_phones.py index 4863e39e..61d4ed59 100644 --- a/tests/admin/test_user_phones.py +++ b/tests/admin/test_user_phones.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/admin/test_user_tokens.py b/tests/admin/test_user_tokens.py index 39bd72c3..8acf8d90 100644 --- a/tests/admin/test_user_tokens.py +++ b/tests/admin/test_user_tokens.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/admin/test_user_u2f.py b/tests/admin/test_user_u2f.py index 0e590eb8..8936fe9b 100644 --- a/tests/admin/test_user_u2f.py +++ b/tests/admin/test_user_u2f.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/admin/test_user_webauthn.py b/tests/admin/test_user_webauthn.py index b5482821..4f74867e 100644 --- a/tests/admin/test_user_webauthn.py +++ b/tests/admin/test_user_webauthn.py @@ -1,3 +1,5 @@ +import unittest + from .. import util import duo_client.admin from .base import TestAdmin diff --git a/tests/test_client.py b/tests/test_client.py index e88c762a..d0ccd767 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,8 +1,6 @@ -from __future__ import absolute_import import hashlib -import mock +from unittest import mock import unittest -import six.moves.urllib import duo_client.client from . import util import base64 @@ -272,10 +270,10 @@ def test_hmac_sha1(self): ) expected = 'f01811cbbf9561623ab45b893096267fd46a5178' expected = ikey + ':' + expected - if isinstance(expected, six.text_type): + if isinstance(expected, str): expected = expected.encode('utf-8') expected = base64.b64encode(expected).strip() - if not isinstance(expected, six.text_type): + if not isinstance(expected, str): expected = expected.decode('utf-8') expected = 'Basic ' + expected self.assertEqual(actual, @@ -302,10 +300,10 @@ def test_hmac_sha512(self): ) expected = '0508065035a03b2a1de2f453e629e791d180329e157f65df6b3e0f08299d4321e1c5c7a7c7ee6b9e5fc80d1fb6fbf3ad5eb7c44dd3b3985a02c37aca53ec3698' expected = ikey + ':' + expected - if isinstance(expected, six.text_type): + if isinstance(expected, str): expected = expected.encode('utf-8') expected = base64.b64encode(expected).strip() - if not isinstance(expected, six.text_type): + if not isinstance(expected, str): expected = expected.decode('utf-8') expected = 'Basic ' + expected self.assertEqual(actual, diff --git a/tests/test_https_wrapper.py b/tests/test_https_wrapper.py index ef667adf..fc24e322 100644 --- a/tests/test_https_wrapper.py +++ b/tests/test_https_wrapper.py @@ -1,7 +1,6 @@ -from __future__ import absolute_import from duo_client.https_wrapper import CertValidatingHTTPSConnection import unittest -import mock +from unittest import mock import ssl class TestSSLContextCreation(unittest.TestCase): diff --git a/tests/util.py b/tests/util.py index c1bddbc4..3dc4944e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,11 +1,9 @@ -from __future__ import absolute_import import json import collections -import urllib +import urllib.parse from json import JSONEncoder import duo_client -import six class MockObjectJsonEncoder(json.JSONEncoder): def default(self, obj): @@ -15,7 +13,7 @@ def default(self, obj): def params_to_dict(param_str): param_dict = collections.defaultdict(list) for (key, val) in (param.split('=') for param in param_str.split('&')): - param_dict[key].append(six.moves.urllib.parse.unquote(val)) + param_dict[key].append(urllib.parse.unquote(val)) return param_dict @@ -69,9 +67,9 @@ def request(self, method, uri, body, headers): self.headers = {} for k, v in headers.items(): - if isinstance(k, six.binary_type): + if isinstance(k, bytes): k = k.decode('ascii') - if isinstance(v, six.binary_type): + if isinstance(v, bytes): v = v.decode('ascii') self.headers[k] = v @@ -119,8 +117,8 @@ def request(self, method, uri, body, headers): self.uri = uri self.body = body self.headers = headers - parsed = six.moves.urllib.parse.urlparse(uri) - params = six.moves.urllib.parse.parse_qs(parsed.query) + parsed = urllib.parse.urlparse(uri) + params = urllib.parse.parse_qs(parsed.query) self.limit = int(params['limit'][0])