Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions .github/workflows/pypi-publish-on-push-on-new-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
35 changes: 17 additions & 18 deletions .github/workflows/pypi-test-publish-on-push-to-develop-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:__
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/britive/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '4.7.0'
__version__ = '4.8.0b0'
2 changes: 2 additions & 0 deletions src/britive/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
54 changes: 54 additions & 0 deletions src/britive/security/temp_tokens.py
Original file line number Diff line number Diff line change
@@ -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.')
53 changes: 53 additions & 0 deletions tests/400-security-04-temp_tokens.py
Original file line number Diff line number Diff line change
@@ -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)
Loading