From 14d4195339cf0c4ab31ecd8f19b2ad63daad9a2c Mon Sep 17 00:00:00 2001 From: Brandon Wang Date: Tue, 13 Jan 2026 12:00:01 -0600 Subject: [PATCH 1/7] not currently working v3 changes --- .gitignore | 3 +++ bluebutton-sample-config.json | 8 ++++---- cms_bluebutton/auth.py | 25 ++++++++++++++++++++----- cms_bluebutton/cms_bluebutton.py | 6 ++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 7a4c993..78595df 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ bb2_venv/ # BB2 ignores .bluebutton-config.json .bluebutton-config.yaml + +# Snyk Security Extension - AI Rules (auto-generated) +.github/instructions/snyk_rules.instructions.md diff --git a/bluebutton-sample-config.json b/bluebutton-sample-config.json index e00dc79..3c51db7 100644 --- a/bluebutton-sample-config.json +++ b/bluebutton-sample-config.json @@ -1,7 +1,7 @@ { "environment": "SANDBOX", - "client_id": "", - "client_secret": "", - "callback_url": "https://www.fake.com/your/callback/here", - "version": 2 + "client_id": "Lb0hAgLi7rtyZvJMucZDDj0i0IKQWqiQrWSVZCZ7", + "client_secret": "JsLVxfaugDqUgowsXNXgoHRroWakt9QFFzYVhaK7Db0aN6DegK9Kn4a56awCOLWkxGiq6dRqgSLX4jAzBFxZNk3XJFERUTHbH1TMvpEsNtP76nrF9ZdkyB3PZT9GFaxr", + "callback_url": "http://localhost:3001/api/bluebutton/callback/", + "version": 3 } diff --git a/cms_bluebutton/auth.py b/cms_bluebutton/auth.py index ac29df2..120c191 100755 --- a/cms_bluebutton/auth.py +++ b/cms_bluebutton/auth.py @@ -95,7 +95,7 @@ def generate_pkce_data() -> dict: code_challenge = base64.urlsafe_b64encode( hashlib.sha256(verifier.encode("ASCII")).digest() ) - return {"code_challenge": code_challenge.decode("utf-8"), "verifier": verifier} + return {"code_challenge": code_challenge.decode("utf-8"), "code_challenge_method": "S256", "verifier": verifier} def generate_random_state(num) -> str: @@ -108,7 +108,7 @@ def generate_auth_data() -> dict: return auth_data -def get_access_token_from_code(bb, auth_data, callback_code) -> dict: +def get_access_token_from_code(bb, auth_data, callback_code, callback_state) -> dict: data = { "client_id": bb.client_id, "client_secret": bb.client_secret, @@ -117,10 +117,20 @@ def get_access_token_from_code(bb, auth_data, callback_code) -> dict: "redirect_uri": bb.callback_url, "code_verifier": auth_data["verifier"], "code_challenge": auth_data["code_challenge"], + "code_challenge_method": "S256", + "state": callback_state, } token_response = _do_post(data, bb, None) - token_response.raise_for_status() + try: + token_response.raise_for_status() + except requests.exceptions.HTTPError as e: + print(f'Error obtaining access token: {e}') + print(f'Response content: {token_response.text}') + print(f'Request data: {data}') + print(f'Request headers: {SDK_HEADERS}') + print(f'Request URL: {bb.auth_token_url}') + raise token_dict = token_response.json() token_dict["expires_at"] = datetime.datetime.now( datetime.timezone.utc @@ -139,17 +149,22 @@ def get_authorization_token(bb, auth_data, callback_code, callback_state): if callback_state != auth_data["state"]: raise ValueError("Provided callback state does not match.") - return AuthorizationToken(get_access_token_from_code(bb, auth_data, callback_code)) + return AuthorizationToken(get_access_token_from_code(bb, auth_data, callback_code, callback_state)) def _do_post(data, bb, auth): mp_encoder = MultipartEncoder(data) headers = SDK_HEADERS headers["content-type"] = mp_encoder.content_type + print(f'headers: {headers}') + print(f'url: {bb.auth_token_url}') + print(f'data: {data}') + print(f'auth: {auth}') + return requests.post( url=bb.auth_token_url, data=mp_encoder, - headers=headers + headers=headers, ) if not auth else requests.post( url=bb.auth_token_url, data=mp_encoder, diff --git a/cms_bluebutton/cms_bluebutton.py b/cms_bluebutton/cms_bluebutton.py index b6c9bf5..db785a9 100644 --- a/cms_bluebutton/cms_bluebutton.py +++ b/cms_bluebutton/cms_bluebutton.py @@ -18,7 +18,9 @@ ROOT_DIR = os.path.abspath(os.curdir) + "/" +print("ROOT_DIR:", ROOT_DIR) DEFAULT_CONFIG_FILE_LOCATION = ROOT_DIR + "./.bluebutton-config.json" +print("DEFAULT_CONFIG_FILE_LOCATION:", DEFAULT_CONFIG_FILE_LOCATION) class BlueButton: @@ -154,4 +156,8 @@ def generate_authorize_url(self, auth_data): return generate_authorize_url(self, auth_data) def get_authorization_token(self, auth_data, callback_code, callback_state): + print("Getting authorization token...") + print("Auth Data:", auth_data) + print("Callback Code:", callback_code) + print("Callback State:", callback_state) return get_authorization_token(self, auth_data, callback_code, callback_state) From 49bcc930ba67e7dbf3392add346e40fcaaefdf80 Mon Sep 17 00:00:00 2001 From: Brandon Wang Date: Wed, 14 Jan 2026 10:11:59 -0600 Subject: [PATCH 2/7] removing pkce for token endpoint --- cms_bluebutton/auth.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cms_bluebutton/auth.py b/cms_bluebutton/auth.py index 120c191..31cd6a1 100755 --- a/cms_bluebutton/auth.py +++ b/cms_bluebutton/auth.py @@ -116,9 +116,6 @@ def get_access_token_from_code(bb, auth_data, callback_code, callback_state) -> "grant_type": "authorization_code", "redirect_uri": bb.callback_url, "code_verifier": auth_data["verifier"], - "code_challenge": auth_data["code_challenge"], - "code_challenge_method": "S256", - "state": callback_state, } token_response = _do_post(data, bb, None) From b5721b3ecfcb946c95f1c9b82e2babceb37a7f8c Mon Sep 17 00:00:00 2001 From: Brandon Wang Date: Wed, 14 Jan 2026 13:39:47 -0600 Subject: [PATCH 3/7] getting rid of other print statements --- cms_bluebutton/auth.py | 15 ++------------- cms_bluebutton/cms_bluebutton.py | 7 ------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/cms_bluebutton/auth.py b/cms_bluebutton/auth.py index 31cd6a1..f6a5be9 100755 --- a/cms_bluebutton/auth.py +++ b/cms_bluebutton/auth.py @@ -5,6 +5,7 @@ import string import datetime import urllib +from cms_bluebutton.tests.fixtures import token_response from requests_toolbelt.multipart.encoder import MultipartEncoder from .constants import SDK_HEADERS @@ -119,15 +120,7 @@ def get_access_token_from_code(bb, auth_data, callback_code, callback_state) -> } token_response = _do_post(data, bb, None) - try: - token_response.raise_for_status() - except requests.exceptions.HTTPError as e: - print(f'Error obtaining access token: {e}') - print(f'Response content: {token_response.text}') - print(f'Request data: {data}') - print(f'Request headers: {SDK_HEADERS}') - print(f'Request URL: {bb.auth_token_url}') - raise + token_response.raise_for_status() token_dict = token_response.json() token_dict["expires_at"] = datetime.datetime.now( datetime.timezone.utc @@ -153,10 +146,6 @@ def _do_post(data, bb, auth): mp_encoder = MultipartEncoder(data) headers = SDK_HEADERS headers["content-type"] = mp_encoder.content_type - print(f'headers: {headers}') - print(f'url: {bb.auth_token_url}') - print(f'data: {data}') - print(f'auth: {auth}') return requests.post( url=bb.auth_token_url, diff --git a/cms_bluebutton/cms_bluebutton.py b/cms_bluebutton/cms_bluebutton.py index db785a9..714e70f 100644 --- a/cms_bluebutton/cms_bluebutton.py +++ b/cms_bluebutton/cms_bluebutton.py @@ -18,10 +18,7 @@ ROOT_DIR = os.path.abspath(os.curdir) + "/" -print("ROOT_DIR:", ROOT_DIR) DEFAULT_CONFIG_FILE_LOCATION = ROOT_DIR + "./.bluebutton-config.json" -print("DEFAULT_CONFIG_FILE_LOCATION:", DEFAULT_CONFIG_FILE_LOCATION) - class BlueButton: @@ -156,8 +153,4 @@ def generate_authorize_url(self, auth_data): return generate_authorize_url(self, auth_data) def get_authorization_token(self, auth_data, callback_code, callback_state): - print("Getting authorization token...") - print("Auth Data:", auth_data) - print("Callback Code:", callback_code) - print("Callback State:", callback_state) return get_authorization_token(self, auth_data, callback_code, callback_state) From f0325497075da379fda98bc497f40b0752449ca1 Mon Sep 17 00:00:00 2001 From: bwang-icf Date: Tue, 20 Jan 2026 08:51:57 -0600 Subject: [PATCH 4/7] Reverting to sample configuration --- bluebutton-sample-config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bluebutton-sample-config.json b/bluebutton-sample-config.json index 3c51db7..929cf07 100644 --- a/bluebutton-sample-config.json +++ b/bluebutton-sample-config.json @@ -1,7 +1,7 @@ { "environment": "SANDBOX", - "client_id": "Lb0hAgLi7rtyZvJMucZDDj0i0IKQWqiQrWSVZCZ7", - "client_secret": "JsLVxfaugDqUgowsXNXgoHRroWakt9QFFzYVhaK7Db0aN6DegK9Kn4a56awCOLWkxGiq6dRqgSLX4jAzBFxZNk3XJFERUTHbH1TMvpEsNtP76nrF9ZdkyB3PZT9GFaxr", - "callback_url": "http://localhost:3001/api/bluebutton/callback/", + "client_id": "", + "client_secret": "", + "callback_url": "https://www.fake.com/your/callback/here", "version": 3 } From 07c1dc0e313a9cf775056e28e0e412ea123f2b22 Mon Sep 17 00:00:00 2001 From: bwang-icf Date: Tue, 20 Jan 2026 08:55:17 -0600 Subject: [PATCH 5/7] Reverting to previous state (pun intended) --- cms_bluebutton/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms_bluebutton/auth.py b/cms_bluebutton/auth.py index f6a5be9..bf2cb19 100755 --- a/cms_bluebutton/auth.py +++ b/cms_bluebutton/auth.py @@ -109,7 +109,7 @@ def generate_auth_data() -> dict: return auth_data -def get_access_token_from_code(bb, auth_data, callback_code, callback_state) -> dict: +def get_access_token_from_code(bb, auth_data, callback_code) -> dict: data = { "client_id": bb.client_id, "client_secret": bb.client_secret, @@ -139,7 +139,7 @@ def get_authorization_token(bb, auth_data, callback_code, callback_state): if callback_state != auth_data["state"]: raise ValueError("Provided callback state does not match.") - return AuthorizationToken(get_access_token_from_code(bb, auth_data, callback_code, callback_state)) + return AuthorizationToken(get_access_token_from_code(bb, auth_data, callback_code)) def _do_post(data, bb, auth): From 7f6d40c0e9e68cf2f7bb48701d34bb680cbf522c Mon Sep 17 00:00:00 2001 From: James Demery Date: Tue, 27 Jan 2026 11:12:57 -0500 Subject: [PATCH 6/7] Remove import --- cms_bluebutton/auth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cms_bluebutton/auth.py b/cms_bluebutton/auth.py index bf2cb19..6811b92 100755 --- a/cms_bluebutton/auth.py +++ b/cms_bluebutton/auth.py @@ -5,7 +5,6 @@ import string import datetime import urllib -from cms_bluebutton.tests.fixtures import token_response from requests_toolbelt.multipart.encoder import MultipartEncoder from .constants import SDK_HEADERS From b291354eb143687fc8b0881ce93f01789bef3d39 Mon Sep 17 00:00:00 2001 From: Brandon Wang Date: Tue, 27 Jan 2026 12:38:27 -0600 Subject: [PATCH 7/7] adding documentation for local running --- README-sdk-dev.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README-sdk-dev.md b/README-sdk-dev.md index f8b56de..805256c 100644 --- a/README-sdk-dev.md +++ b/README-sdk-dev.md @@ -315,3 +315,50 @@ To do this, edit the following line in the `./cms_bluebutton/version.py` file wi __version__ = "1.0.0", ``` +### Testing Locally + +The current method for seeing the SDK in action is fairly complex, as it requires also setting up the Python sample client (https://github.com/CMSgov/bluebutton-sample-client-python-react/tree/main). These both, of course, depend upon the web-server repo for most of their logic. It is possible that in order to fully understand an issue that arises within the SDK or the sample client, a developer would have to track changes across 3 separate projects. There should be some future work to simplify this process as it is very manual and laborious. + +The steps listed here are listed elsewhere in the documentation but for the sake of convenience, they are partially repeated here +and written together so that a developer should be able to follow this step by step. + +The overall goals are to: + + - Build a local version of the SDK + - Run a local version of sample client that consumes a local version of the SDK + + ### Building a local version of the SDK + + Run the following commands in the base of this SDK repository. The commands suppose that you have the Python sample client cloned in the same folder as this SDK repo. Do not be in a virtualenv while running these commands. + + ``` + rm -rf build/ + python -m build --wheel --o ../bluebutton-sample-client-python-react/server + ``` + + The --o (or outdir) command should effectively 'copy paste' the built version of the .whl file into where it would be needed for the sample client. If you do not want it in the sample client, omit the --o and file path. + + ### Run a local version of sample client that consumes a local version of the SDK + + Ensure that in bluebutton-sample-client-python-react/server/Dockerfile, uncomment the following line. Replace the version number (1.0.4 in the example) of the .whl file with what has been generated from the previous build command. + + ``` + RUN pip install cms_bluebutton_sdk-1.0.4-py3-none-any.whl + ``` + + In bluebutton-sample-client-python-react/server/Pipfile, add this line: + + ``` + cms-bluebutton-sdk = {file = "./cms_bluebutton_sdk-1.0.4-py3-none-any.whl"} + ``` + + In the base repository of bluebutton-sample-client-python-react, run the following commands. Ensure that you have no currently running containers or images of the sample client. + + ``` + cd server + unzip -l cms_bluebutton_sdk-1.0.4-py3-none-any.whl + pip install cms_bluebutton_sdk-1.0.4-py3-none-any.whl + docker compose up + ``` + + Each time a change is made in the SDK, you must repeat all of the previous steps of building and re-running a local sample client. You must also ensure that the containers and images are removed each time. \ No newline at end of file