From 384db20d55cb8adedcfc12963bd9649426e2756c Mon Sep 17 00:00:00 2001 From: Edward Delaporte Date: Mon, 5 Aug 2024 14:11:46 -0500 Subject: [PATCH] Fixes for enable/disable mac (#14) Add tests for enable/disable MAC Co-authored-by: Edward Delaporte Co-authored-by: Michelle Pitcel Co-authored-by: Tyler Turner Co-authored-by: Zach Carrington --- CHANGELOG.md | 5 + cassettes/test_404_disable_mac.yaml | 100 ++++++++++++++++++ cassettes/test_404_enable_mac.yaml | 100 ++++++++++++++++++ cassettes/test_disable_mac.yaml | 154 ++++++++++++++++++++++++++++ cassettes/test_enable_mac.yaml | 153 +++++++++++++++++++++++++++ src/clearpass/client.py | 35 +++++-- tests/conftest.py | 24 +++-- tests/test_connector.py | 29 ++++++ 8 files changed, 578 insertions(+), 22 deletions(-) create mode 100644 cassettes/test_404_disable_mac.yaml create mode 100644 cassettes/test_404_enable_mac.yaml create mode 100644 cassettes/test_disable_mac.yaml create mode 100644 cassettes/test_enable_mac.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9f93b..3f352f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased + ### Added + +- Add `normalize_mac_address`, `hyphenate_mac` utilities +- Add functions: `test_connectivity`, `get_info_for_mac_address`, `get_mac_id`, `set_mac_address`, `enable_mac_address`, `disable_mac_address` + ### Changed ### Removed diff --git a/cassettes/test_404_disable_mac.yaml b/cassettes/test_404_disable_mac.yaml new file mode 100644 index 0000000..30a3f2a --- /dev/null +++ b/cassettes/test_404_disable_mac.yaml @@ -0,0 +1,100 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://notauri.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Mon, 05 Aug 2024 16:40:43 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://notauri.edu/api/endpoint/mac-address/deadbeef1234 + response: + body: + string: '{"type":"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html","title":"Not + Found","status":404,"detail":"Object not found"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '126' + Content-Type: + - application/problem+json + Date: + - Mon, 05 Aug 2024 16:40:43 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 404 + message: Not Found +version: 1 diff --git a/cassettes/test_404_enable_mac.yaml b/cassettes/test_404_enable_mac.yaml new file mode 100644 index 0000000..4ea1f5c --- /dev/null +++ b/cassettes/test_404_enable_mac.yaml @@ -0,0 +1,100 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://notauri.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Mon, 05 Aug 2024 16:40:44 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://notauri.edu/api/endpoint/mac-address/deadbeef1234 + response: + body: + string: '{"type":"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html","title":"Not + Found","status":404,"detail":"Object not found"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '126' + Content-Type: + - application/problem+json + Date: + - Mon, 05 Aug 2024 16:40:44 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 404 + message: Not Found +version: 1 diff --git a/cassettes/test_disable_mac.yaml b/cassettes/test_disable_mac.yaml new file mode 100644 index 0000000..8b7eefe --- /dev/null +++ b/cassettes/test_disable_mac.yaml @@ -0,0 +1,154 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://notauri.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Mon, 05 Aug 2024 16:31:44 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://notauri.edu/api/endpoint/mac-address/123123123123 + response: + body: + string: '{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov + 15, 2023 14:52:34 CST","updated_at":"Aug 05, 2024 11:27:32 CDT","_stuff":"deleted"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '267' + Content-Type: + - application/hal+json + Date: + - Mon, 05 Aug 2024 16:31:45 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: '{"id": 4524, "mac_address": "123123123123", "status": "Disabled", "attributes": + {"Disabled By": "TESTING", "Disabled Reason": "Still testing..."}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + Content-Length: + - '146' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://notauri.edu/api/endpoint/4524 + response: + body: + string: '{"id":4524,"mac_address":"123123123123","status":"Disabled","randomized_mac":true,"attributes":{"Disabled + By":"TESTING","Disabled Reason":"Still testing..."},"added_at":"Nov 15, 2023 + 14:52:34 CST","updated_at":"Aug 05, 2024 11:31:45 CDT","stuff":"deleted"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '330' + Content-Type: + - application/hal+json + Date: + - Mon, 05 Aug 2024 16:31:45 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/cassettes/test_enable_mac.yaml b/cassettes/test_enable_mac.yaml new file mode 100644 index 0000000..686eea1 --- /dev/null +++ b/cassettes/test_enable_mac.yaml @@ -0,0 +1,153 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://notauri.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Mon, 05 Aug 2024 16:27:31 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://notauri.edu/api/endpoint/mac-address/123123123123 + response: + body: + string: '{"id":4524,"mac_address":"123123123123","status":"Disabled","randomized_mac":true,"attributes":{"Disabled + By":"automation","Disabled Reason":"Disabled"},"added_at":"Nov 15, 2023 14:52:34 + CST","updated_at":"Mar 28, 2024 15:31:54 CDT","stuff":"deleted"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '325' + Content-Type: + - application/hal+json + Date: + - Mon, 05 Aug 2024 16:27:31 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: '{"id": 4524, "mac_address": "123123123123", "status": "Known"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + Content-Length: + - '62' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://notauri.edu/api/endpoint/4524 + response: + body: + string: '{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov + 15, 2023 14:52:34 CST","updated_at":"Aug 05, 2024 11:27:32 CDT","stuff":"deleted"}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '267' + Content-Type: + - application/hal+json + Date: + - Mon, 05 Aug 2024 16:27:32 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + X-XSS-Protection: + - 1;mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/src/clearpass/client.py b/src/clearpass/client.py index c332ea4..59dd2c3 100644 --- a/src/clearpass/client.py +++ b/src/clearpass/client.py @@ -63,6 +63,8 @@ def __init__(self, username, password, endpoint, client_id, client_secret): "client_id": client_id, "client_secret": client_secret, } + self._getheaders = None + self._postheaders = None self._macinfo = {} @@ -97,7 +99,8 @@ def getheaders(self): self._getheaders = { 'Accept': 'application/json', 'Authorization': f'Bearer {token}', - } + } + return self._getheaders @property def postheaders(self): @@ -106,6 +109,7 @@ def postheaders(self): self._postheaders = {'Content-Type': 'application/json'} self._postheaders.update(self.getheaders) + return self._postheaders def _put_api(self, resource, payload): return requests.put( @@ -158,7 +162,7 @@ def get_mac_id(self, mac): try: return mac_info["id"] except KeyError: - raise Exception(f"MAC address {mac} not found on server") + raise ValueError(f"MAC address {mac} not found on server") def set_mac_address( self, mac_id, mac, status, description=None, attributes=None): @@ -174,12 +178,21 @@ def set_mac_address( data["attributes"] = attributes return self._put_api(f"endpoint/{mac_id}", data) - def enable_mac_address(self, mac, mac_id): - return self.set_mac_address(mac_id, mac, status="Known") - - def disable_mac_address(self, mac, mac_id, disabled_by, reason): - return self.set_mac_address(mac_id, mac, status="Disabled", - attributes={ - "Disabled By": disabled_by, - "Disabled Reason": reason - }) + def enable_mac_address(self, mac): + mac_id = self.get_mac_id(mac) + res = self.set_mac_address(mac_id, mac, status="Known") + if res.status_code == 404: + raise ValueError(f"{mac} not found.") + return res + + def disable_mac_address(self, mac, disabled_by, reason): + mac_id = self.get_mac_id(mac) + res = self.set_mac_address( + mac_id, mac, status="Disabled", + attributes={ + "Disabled By": disabled_by, + "Disabled Reason": reason + }) + if res.status_code == 404: + raise ValueError(f"{mac} not found.") + return res diff --git a/tests/conftest.py b/tests/conftest.py index c7eb336..617bc56 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ import datetime import json -import jwt import os import pytest @@ -23,6 +22,11 @@ # To record, `export VCR_RECORD=True` VCR_RECORD = "VCR_RECORD" in os.environ +MAC_404 = 'deadbeef1234' # pragma: allowlist secret +TEST_DATA = {'mac': '123123123123', # pragma: allowlist secret + 'disabled_by': 'TESTING', + 'reason': 'Still testing...' + } @pytest.fixture @@ -49,18 +53,15 @@ def clearpass_client(monkeypatch) -> APIConnection: return APIConnection(**kwargs) -@clean_if(uri=f"{URL}/api/oauth") -def clean_auth(request, response): - clean_token(request, response) +def clean_cookie(request: dict, response: dict): + response['headers']['Set-Cookie'] = 'NO-COOKIE-FOR-YOU' +@clean_if(uri=f"{URL}/api/oauth") def clean_token(request: dict, response: dict): - '''Clean a JWT token.''' - jwt_token = jwt.encode(CLEANER_JWT_TOKEN, CLEANER_SALT, algorithm='HS256') - if 'Content-Type' in response['headers'].keys() and \ - response['headers']['Content-Type'] == ['application/json']: - token = {'access_token': jwt_token} - response['body']['string'] = json.dumps(token) + '''Clean a JSON token.''' + token = {'access_token': 'NOTASECRET'} + response['body']['string'] = json.dumps(token) def remove_creds(request): @@ -103,7 +104,8 @@ def cassette(request) -> vcr.cassette.Cassette: my_vcr.register_serializer("cleanyaml", yaml_cleaner) # TODO: Register cleaner functions here: yaml_cleaner.register_cleaner(clean_uri) - yaml_cleaner.register_cleaner(clean_auth) + yaml_cleaner.register_cleaner(clean_token) + yaml_cleaner.register_cleaner(clean_cookie) with my_vcr.use_cassette(f'{request.function.__name__}.yaml', serializer='cleanyaml') as tape: diff --git a/tests/test_connector.py b/tests/test_connector.py index cbc6eef..7e3157c 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -1,3 +1,7 @@ +import pytest +from conftest import TEST_DATA, MAC_404 + + def test_connectivity(cassette, clearpass_client): assert clearpass_client.test_connectivity() @@ -5,3 +9,28 @@ def test_connectivity(cassette, clearpass_client): def test_failed_connectivity(cassette, clearpass_client): result = clearpass_client.test_connectivity() assert result is False + + +def test_disable_mac(cassette, clearpass_client): + result = clearpass_client.disable_mac_address(**TEST_DATA) + assert result + + +def test_404_disable_mac(cassette, clearpass_client): + '''Test disable on a MAC that is not found.''' + with pytest.raises(ValueError): + clearpass_client.disable_mac_address( + mac=MAC_404, + disabled_by="TEST", + reason="THE BEST TEST REASON") + + +def test_enable_mac(cassette, clearpass_client): + result = clearpass_client.enable_mac_address(mac=TEST_DATA['mac']) + assert result + + +def test_404_enable_mac(cassette, clearpass_client): + '''Test enable on a MAC that is not found.''' + with pytest.raises(ValueError): + clearpass_client.enable_mac_address(mac=MAC_404)