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/
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## v2.4.0-rc.2 [2026-09-16]

__What's New:__

* Added `tokens [create|get|list|revoke]` command to manage temporary bearer tokens for the authenticated user.

__Enhancements:__

* None

__Bug Fixes:__

* None

__Dependencies:__

* Bumped the `britive` SDK minimum to `4.8.0b0`.
* Dropped `python3.9` support, EOL 2025-10-31, was dropped by `britive` dependency in `v4.5.0`.
* Updated github workflows python version to `python3.11`

__Other:__

* Python lint related changes.

## v2.4.0-rc.1 [2026-06-08]

__What's New:__
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Full Britive documentation is available here: [docs.britive.com](https://docs.br

## Requirements

* Python 3.9 or higher
* Python 3.10 or higher
* Active Britive tenant (or nothing is really going to work)

## Installation
Expand Down
31 changes: 14 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "pybritive"
authors = [{name = "Britive Inc.", email = "support@britive.com"}]
authors = [{ name = "Britive Inc.", email = "support@britive.com" }]
description = "A pure Python CLI for Britive"
readme = "README.md"
classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Internet",
"Topic :: Security",
"Topic :: Utilities",
]
license = {file = "LICENSE"}
requires-python = ">= 3.9"
license = { file = "LICENSE" }
requires-python = ">= 3.10"
dependencies = [
"britive>=4.6.0,<5.0",
"britive>=4.8.0b0,<5.0",
"click>=8.1.7",
"colored>=2.2.5",
"cryptography",
Expand All @@ -37,7 +34,7 @@ dependencies = [
"PyYAML",
"requests>=2.31.0",
"tabulate",
"toml"
"toml",
]
dynamic = ["version"]
keywords = ["britive", "cpam", "identity", "jit", "cli"]
Expand All @@ -59,10 +56,10 @@ Issues = "https://github.com/britive/python-cli/issues"
Changelog = "https://github.com/britive/python-cli/blob/main/CHANGELOG.md"

[tool.setuptools]
package-dir = {"" = "src"}
package-dir = { "" = "src" }

[tool.setuptools.dynamic]
version = {attr = "pybritive.__version__"}
version = { attr = "pybritive.__version__" }

[tool.setuptools.packages.find]
where = ["src"]
Expand All @@ -83,10 +80,10 @@ line-ending = "auto"

[tool.ruff.lint]
select = [
"E", # pycodestyle
"F", # Pyflakes
"I", # isort
"PL", #pylint
"E", # pycodestyle
"F", # Pyflakes
"I", # isort
"PL", #pylint
"SIM", # flake8-simplify
]

Expand All @@ -95,4 +92,4 @@ allow-magic-value-types = ["int", "str"]
max-args = 18
max-branches = 30
max-returns = 8
max-statements = 72
max-statements = 72
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
beautifulsoup4
boto3
britive>=4.1.2,<5.0
britive>=4.8.0b0,<5.0
certifi
charset-normalizer
click>=8.1.7
Expand Down
2 changes: 1 addition & 1 deletion src/pybritive/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.4.0-rc.1'
__version__ = '2.4.0-rc.2'
23 changes: 23 additions & 0 deletions src/pybritive/britive_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,29 @@ def user(self):
output += f' (alias: {alias})'
self.print(output, ignore_silent=True)

def create_temp_token(self, duration_seconds: Optional[int] = None):
self.login()
token = self.b.security.temp_tokens.create(duration_seconds=duration_seconds)
output = [token] if self.output_format in ['csv', 'list'] else token
self.print(output, ignore_silent=True)

def list_temp_tokens(self):
self.login()
tokens = self.b.security.temp_tokens.list()
if not tokens and self.output_format == 'csv':
return # There are no fields from which to build a CSV header.
self.print(tokens, ignore_silent=True)

def get_temp_token(self, token_id: str):
self.login()
token = self.b.security.temp_tokens.get(token_id=token_id)
output = [token] if self.output_format in ['csv', 'list'] else token
self.print(output, ignore_silent=True)

def revoke_temp_token(self, token_id: str):
self.login()
self.b.security.temp_tokens.revoke(token_id=token_id)

def list_secrets(self, search_text: Optional[str] = None):
self.login()
self.print(self.b.my_secrets.list(search=search_text), ignore_silent=True)
Expand Down
2 changes: 2 additions & 0 deletions src/pybritive/cli_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pybritive.commands.request import request as group_request
from pybritive.commands.secret import secret as group_secret
from pybritive.commands.ssh import ssh as group_ssh
from pybritive.commands.tokens import tokens as group_tokens
from pybritive.commands.user import user as command_user
from pybritive.options.britive_options import britive_options

Expand Down Expand Up @@ -55,6 +56,7 @@ def cli(version):
cli.add_command(command_api)
cli.add_command(group_ssh)
cli.add_command(group_aws)
cli.add_command(group_tokens)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion src/pybritive/commands/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'gcloud_key_file,verbose,extend,profile_type,tenant,token,passphrase,federation_provider'
)
@click_smart_profile_argument
def checkout( # noqa: PLR0913
def checkout( # noqa: PLR0913, PLR0917
ctx,
alias,
blocktime,
Expand Down
66 changes: 66 additions & 0 deletions src/pybritive/commands/tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import click

from pybritive.helpers.build_britive import build_britive
from pybritive.options.britive_options import britive_options


@click.group()
def tokens():
"""Manage temporary tokens for your authenticated identity."""


@tokens.command()
@build_britive
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
@click.option(
'--duration-seconds',
type=click.IntRange(1, 86400),
default=None,
help='Requested lifetime in seconds. Omit to use the tenant default. The tenant maximum also applies.',
)
def create(ctx, output_format, tenant, token, silent, passphrase, federation_provider, duration_seconds):
"""Create a temporary bearer token for your authenticated identity.

Requires securityadmin.temptoken.create. A temporary token cannot create another temporary token.
Prints accessToken and expiresOn, including when --silent is set. The token is returned only once
and is not saved as your CLI login credential.
"""
ctx.obj.britive.create_temp_token(duration_seconds=duration_seconds)


@tokens.command()
@build_britive
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
def list(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
"""List temporary tokens (not yet implemented)."""
# Remove this guard when the SDK implements the operation.
raise click.ClickException('Listing temporary tokens is not yet implemented.')
ctx.obj.britive.list_temp_tokens()


@tokens.command()
@build_britive
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
@click.argument('token_id')
def get(ctx, output_format, tenant, token, silent, passphrase, federation_provider, token_id):
"""View a temporary token (not yet implemented).

TOKEN_ID is provisional, pending the API contract.
"""
# Remove this guard when the SDK implements the operation.
raise click.ClickException('Viewing temporary tokens is not yet implemented.')
ctx.obj.britive.get_temp_token(token_id=token_id)


@tokens.command()
@build_britive
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
@click.argument('token_id')
def revoke(ctx, tenant, token, silent, passphrase, federation_provider, token_id):
"""Revoke a temporary token (not yet implemented).

TOKEN_ID is provisional, pending the API contract.
"""
# Remove this guard when the SDK implements the operation.
raise click.ClickException('Revoking temporary tokens is not yet implemented.')
ctx.obj.britive.revoke_temp_token(token_id=token_id)
9 changes: 3 additions & 6 deletions src/pybritive/helpers/checkout_lock.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import hashlib
import os
import time
Expand Down Expand Up @@ -36,10 +37,8 @@ def _lock_fd(fd: int) -> None:
raise _WouldBlock()

def _unlock_fd(fd: int) -> None:
try:
with contextlib.suppress(OSError, IOError):
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
except (OSError, IOError):
pass


class CheckoutLock:
Expand All @@ -66,9 +65,7 @@ def acquire(self) -> None:
if time.monotonic() >= deadline:
os.close(self._fd)
self._fd = None
raise CheckoutLockTimeout(
f'Timed out after {self.timeout}s waiting for checkout lock'
)
raise CheckoutLockTimeout(f'Timed out after {self.timeout}s waiting for checkout lock')
time.sleep(self.poll_interval)

def release(self) -> None:
Expand Down
Loading
Loading