From a97d36196f1b34e14ac27f88251675db615ebef9 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 16 Sep 2026 12:59:22 -0500 Subject: [PATCH 1/4] feat(security): added temp_tokens --- CONTRIBUTING.md | 1 + src/britive/security/__init__.py | 2 ++ src/britive/security/temp_tokens.py | 54 ++++++++++++++++++++++++++++ tests/400-security-04-temp_tokens.py | 53 +++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 src/britive/security/temp_tokens.py create mode 100644 tests/400-security-04-temp_tokens.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e201f5..1392091 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,6 +155,7 @@ pytest tests/350-access_broker-08-permissions.py -v pytest tests/400-security-01-policies.py -v pytest tests/400-security-02-saml.py -v pytest tests/400-security-03-api_tokens.py -v +pytest tests/400-security-04-temp_tokens.py -v pytest tests/500-audit_logs-01-logs.py -v pytest tests/500-audit_logs-02-webhooks.py -v pytest tests/550-reports-01-reports.py -v diff --git a/src/britive/security/__init__.py b/src/britive/security/__init__.py index cb68c4f..d7da5a6 100644 --- a/src/britive/security/__init__.py +++ b/src/britive/security/__init__.py @@ -3,12 +3,14 @@ from .policies import SecurityPolicies from .saml import Saml from .step_up import StepUpAuth +from .temp_tokens import TempTokens class Security: def __init__(self, britive) -> None: self.active_sessions = ActiveSessions(britive) self.api_tokens = ApiTokens(britive) + self.temp_tokens = TempTokens(britive) self.saml = Saml(britive) self.security_policies = SecurityPolicies(britive) self.step_up_auth = StepUpAuth(britive) diff --git a/src/britive/security/temp_tokens.py b/src/britive/security/temp_tokens.py new file mode 100644 index 0000000..0ac6a31 --- /dev/null +++ b/src/britive/security/temp_tokens.py @@ -0,0 +1,54 @@ +class TempTokens: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/tokens/temp' + + def create(self, duration_seconds: int = None) -> dict: + """ + Create a temporary bearer token for the caller's identity. + + Requires the securityadmin.temptoken.create permission. Requests made with the token use the caller's + permissions at the time of each request. A temporary token cannot be used to create another temporary token. + + :param duration_seconds: Requested lifetime in seconds. Must be a positive integer within the tenant's + configured maximum and the 86400-second platform limit. If omitted, the API uses the tenant's configured + lifetime, bounded by the platform limit. The API validates the duration and rejects requests above the + effective maximum. Expiration enforcement can lag by about a minute. + :return: Response dictionary containing accessToken and expiresOn, an expiration date-time string. + The token is returned once and cannot be retrieved afterwards. + """ + + data = {} + if duration_seconds is not None: + data['durationSeconds'] = duration_seconds + + return self.britive.post(self.base_url, json=data) + + def list(self) -> list: + """ + Placeholder for listing temporary tokens. Makes no HTTP request. + + :raises NotImplementedError: This SDK operation awaits the API contract. + """ + + raise NotImplementedError('Listing temporary tokens is not implemented in the SDK; awaiting the API contract.') + + def get(self, token_id: str) -> dict: + """ + Placeholder for viewing a temporary token. Makes no HTTP request. + + :param token_id: Provisional token identifier, subject to the future API contract. + :raises NotImplementedError: This SDK operation awaits the API contract. + """ + + raise NotImplementedError('Viewing temporary tokens is not implemented in the SDK; awaiting the API contract.') + + def revoke(self, token_id: str) -> None: + """ + Placeholder for revoking a temporary token. Makes no HTTP request. + + :param token_id: Provisional token identifier, subject to the future API contract. + :raises NotImplementedError: This SDK operation awaits the API contract. + """ + + raise NotImplementedError('Revoking temporary tokens is not implemented in the SDK; awaiting the API contract.') diff --git a/tests/400-security-04-temp_tokens.py b/tests/400-security-04-temp_tokens.py new file mode 100644 index 0000000..7a8afb1 --- /dev/null +++ b/tests/400-security-04-temp_tokens.py @@ -0,0 +1,53 @@ +from datetime import datetime, timezone + +from britive.exceptions import BritiveException + +from .cache import * # will also import some globals like `britive` + + +@pytest.fixture(scope='module') +def temp_token(): + # Keep this short-lived credential in memory rather than the persistent pytest cache. + return britive.security.temp_tokens.create() + + +@pytest.fixture(scope='module') +def temp_client(temp_token): + client = Britive(tenant=britive.tenant, token=temp_token['accessToken'], query_features=False) + yield client + client.session.close() + + +def assert_token_details(token): + assert isinstance(token, dict) + assert isinstance(token['accessToken'], str) + assert token['accessToken'] + assert isinstance(token['expiresOn'], str) + expires_on = datetime.fromisoformat(token['expiresOn'].replace('Z', '+00:00')) + assert expires_on > datetime.now(timezone.utc) + + +def test_create(temp_token): + assert_token_details(temp_token) + + +def test_create_with_duration(): + token = britive.security.temp_tokens.create(duration_seconds=900) + assert_token_details(token) + + +def test_authenticate(temp_client): + identity = temp_client.my_access.whoami() + assert isinstance(identity, dict) + assert identity['userId'] == britive.my_access.whoami()['userId'] + + +def test_cannot_create_from_temp_token(temp_client): + with pytest.raises(BritiveException, match=r'^403 - .*temporary token'): + temp_client.security.temp_tokens.create() + + +@pytest.mark.parametrize('duration_seconds', [0, -1, 86401]) +def test_invalid_duration(duration_seconds): + with pytest.raises(BritiveException, match=r'^400 - '): + britive.security.temp_tokens.create(duration_seconds=duration_seconds) From e645576b1b77c18ff05d8d65939dea44eb6ea9f7 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 16 Sep 2026 18:03:21 -0500 Subject: [PATCH 2/4] chore: bump workflows python version --- .../pypi-publish-on-push-on-new-release.yml | 33 +++++++++-------- ...test-publish-on-push-to-develop-branch.yml | 35 +++++++++---------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/.github/workflows/pypi-publish-on-push-on-new-release.yml b/.github/workflows/pypi-publish-on-push-on-new-release.yml index 4bba4d5..468b618 100644 --- a/.github/workflows/pypi-publish-on-push-on-new-release.yml +++ b/.github/workflows/pypi-publish-on-push-on-new-release.yml @@ -11,23 +11,22 @@ permissions: jobs: deploy: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.9' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/pypi-test-publish-on-push-to-develop-branch.yml b/.github/workflows/pypi-test-publish-on-push-to-develop-branch.yml index 68a15b8..1f7e69e 100644 --- a/.github/workflows/pypi-test-publish-on-push-to-develop-branch.yml +++ b/.github/workflows/pypi-test-publish-on-push-to-develop-branch.yml @@ -12,24 +12,23 @@ permissions: jobs: deploy: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.9' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_TEST_API_TOKEN }} - repository_url: https://test.pypi.org/legacy/ + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_TEST_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ From 7e2f6e9f201a0c878ea251c9eb24aaedefd52e30 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 16 Sep 2026 18:03:42 -0500 Subject: [PATCH 3/4] docs(CONTRIBUTING): outdated minimum python version --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1392091..cd7c894 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ can consume a native Python library. ## Python Version Support -_CURRENT SUPPORTED VERSION(S):_ `>= 3.9` +_CURRENT SUPPORTED VERSION(S):_ `>= 3.10` We use [typing](https://docs.python.org/3/library/typing.html) and dictionary unpacking, e.g. `{**dict1, **dict2}`, which requires Python 3.5 or greater. From 492334a90899176c656840dd950c0051f4dd4bbf Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 16 Sep 2026 13:05:13 -0500 Subject: [PATCH 4/4] v4.8.0b0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ src/britive/__init__.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa35c0..5eab4ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log (v2.8.1+) +## v4.8.0b0 [2026-09-16] + +__What's New:__ + +* Added `security.temp_tokens` for temporary bearer tokens. + +__Enhancements:__ + +* None + +__Bug Fixes:__ + +* None + +__Dependencies:__ + +* None + +__Other:__ + +* Updated github workflows python version to `python3.11` +* Updated CONTRIBUTING python version to match project minimum. + ## v4.7.0 [2026-08-24] __What's New:__ diff --git a/src/britive/__init__.py b/src/britive/__init__.py index 372a507..cbaaca2 100644 --- a/src/britive/__init__.py +++ b/src/britive/__init__.py @@ -1 +1 @@ -__version__ = '4.7.0' +__version__ = '4.8.0b0'