Skip to content

Commit 28ad928

Browse files
authored
Merge pull request #215 from britive/develop
v2.3.0
2 parents ed639f0 + 74bc6c5 commit 28ad928

File tree

10 files changed

+185
-97
lines changed

10 files changed

+185
-97
lines changed

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,32 @@
33
> As of v1.4.0, release candidates will be published in an effort to get new features out faster while still allowing
44
> time for full QA testing before moving the release candidate to a full release.
55
6+
## v2.3.0 [2025-10-16]
7+
8+
__What's New:__
9+
10+
* Applications, Environments, Profiles, Resources, and Secrets have a new `--search-text|-S` flag for listing with `ls`.
11+
12+
__Enhancements:__
13+
14+
* Added `search_text` parameter to `list_[applications|environments|profiles|resources|secrets]`
15+
* Added `list_requests` functionality.
16+
17+
__Bug Fixes:__
18+
19+
* Added `PYBRITIVE_ENCRYPTED_CREDENTIAL_PASSPHRASE` env var for AWS/k8s helpers.
20+
* Refactored default `passphrase` as `uuid.getnode` doesn't afford repeatability in sandboxed environments, e.g. `uv`.
21+
* Corrected `my_access` profiles with empty description getting errant `Resource` default value.
22+
* Updated `list_[applications|environments]` to exclude unrelated `my-resources` profiles and replace `null` with `''`.
23+
24+
__Dependencies:__
25+
26+
* None
27+
28+
__Other:__
29+
30+
* Dropped temporary `_get_missing_session_attributes` method, API has been corrected.
31+
632
## v2.2.3 [2025-08-06]
733

834
__What's New:__

src/pybritive/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.2.3'
1+
__version__ = '2.3.0'

src/pybritive/britive_cli.py

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,9 @@ def user(self):
314314
output += f' (alias: {alias})'
315315
self.print(output, ignore_silent=True)
316316

317-
def list_secrets(self):
317+
def list_secrets(self, search_text: Optional[str] = None):
318318
self.login()
319-
self.print(self.b.my_secrets.list(), ignore_silent=True)
319+
self.print(self.b.my_secrets.list(search=search_text), ignore_silent=True)
320320

321321
def list_approvals(self):
322322
self.login()
@@ -338,14 +338,34 @@ def list_approvals(self):
338338
approvals.reverse()
339339
self.print(approvals, ignore_silent=True)
340340

341-
def list_resources(self):
341+
def list_requests(self):
342+
self.login()
343+
requests = []
344+
for request in self.b.my_requests.list():
345+
request.pop('resource', None)
346+
request.pop('consumer', None)
347+
request.pop('timeToApprove', None)
348+
request.pop('validFor', None)
349+
request.pop('action', None)
350+
request.pop('approvers', None)
351+
request.pop('expirationTimeApproval', None)
352+
request.pop('updatedAt', None)
353+
request.pop('actionBy', None)
354+
request.pop('validForInDays', None)
355+
requests.append(request)
356+
357+
requests = sorted(requests, key=lambda x: x['createdAt'])
358+
requests.reverse()
359+
self.print(requests, ignore_silent=True)
360+
361+
def list_resources(self, search_text: Optional[str] = None):
342362
self.login()
343363
found_resource_names = []
344364
resources = []
345-
if resource_limit := int(self.config.my_resources_retrieval_limit):
346-
profiles = self.b.my_resources.list(size=resource_limit)['data']
347-
else:
348-
profiles = self.b.my_resources.list_profiles()
365+
resource_limit = int(self.config.my_resources_retrieval_limit)
366+
profiles = self.b.my_resources.list(search_text=search_text, size=resource_limit)
367+
if resource_limit:
368+
profiles = profiles['data']
349369
for item in profiles:
350370
name = item['resourceName']
351371
if name not in found_resource_names:
@@ -360,9 +380,14 @@ def list_resources(self):
360380
found_resource_names.append(name)
361381
self.print(resources, ignore_silent=True)
362382

363-
def list_profiles(self, checked_out: bool = False, profile_type: Optional[str] = None):
383+
def list_profiles(
384+
self,
385+
checked_out: bool = False,
386+
profile_type: Optional[str] = None,
387+
search_text: Optional[str] = None,
388+
):
364389
self.login()
365-
self._set_available_profiles(profile_type=profile_type)
390+
self._set_available_profiles(profile_type=profile_type, search_text=search_text)
366391
data = []
367392
checked_out_profiles = {}
368393
if checked_out: # only make this call if we have to
@@ -390,7 +415,7 @@ def list_profiles(self, checked_out: bool = False, profile_type: Optional[str] =
390415
'Application': profile['app_name'] or 'Resources',
391416
'Environment': profile['env_name'],
392417
'Profile': profile['profile_name'],
393-
'Description': profile['profile_description'] or 'Resource',
418+
'Description': profile['profile_description'] if profile['app_name'] else 'Resource',
394419
'Type': profile['app_type'],
395420
}
396421

@@ -427,9 +452,9 @@ def list_profiles(self, checked_out: bool = False, profile_type: Optional[str] =
427452
if self.output_format == 'list-profiles':
428453
self.output_format = 'list'
429454

430-
def list_applications(self):
455+
def list_applications(self, search_text: Optional[str] = None):
431456
self.login()
432-
self._set_available_profiles()
457+
self._set_available_profiles(profile_type='my-access', search_text=search_text)
433458
keys = ['app_name', 'app_type', 'app_description']
434459
apps = []
435460
for profile in self.available_profiles:
@@ -440,14 +465,14 @@ def list_applications(self):
440465
row = {
441466
'Application': app['app_name'],
442467
'Type': app['app_type'],
443-
'Description': app['app_description'],
468+
'Description': app['app_description'] or '',
444469
}
445470
data.append(row)
446471
self.print(data, ignore_silent=True)
447472

448-
def list_environments(self):
473+
def list_environments(self, search_text: Optional[str] = None):
449474
self.login()
450-
self._set_available_profiles()
475+
self._set_available_profiles(profile_type='my-access', search_text=search_text)
451476
envs = []
452477
keys = ['app_name', 'app_type', 'env_name', 'env_description']
453478
for profile in self.available_profiles:
@@ -459,37 +484,27 @@ def list_environments(self):
459484
row = {
460485
'Application': env['app_name'],
461486
'Environment': env['env_name'],
462-
'Description': env['env_description'],
487+
'Description': env['env_description'] or '',
463488
'Type': env['app_type'],
464489
}
465490
data.append(row)
466491
self.print(data, ignore_silent=True)
467492

468-
# temporary fix till the new API is updated to return `sessionAttributes`
469-
def _get_missing_session_attributes(self, app_id: str, profile_id: str) -> dict:
470-
if not self.listed_profiles:
471-
self.listed_profiles = self.b.my_access.list_profiles()
472-
return next(
473-
(
474-
profile['sessionAttributes']
475-
for app in self.listed_profiles
476-
if app['appContainerId'] == app_id
477-
for profile in app.get('profiles', [])
478-
if profile['profileId'] == profile_id
479-
),
480-
[],
481-
)
482-
483-
def _set_available_profiles(self, from_cache_command=False, profile_type: Optional[str] = None):
493+
def _set_available_profiles(
494+
self,
495+
from_cache_command=False,
496+
profile_type: Optional[str] = None,
497+
search_text: Optional[str] = None,
498+
):
484499
if not self.available_profiles:
485500
data = []
486501
if not profile_type or profile_type == 'my-access':
487502
self.listed_profiles = None
488503
access_limit = int(self.config.my_access_retrieval_limit)
489504
increase = 0
490-
while (access_data := self.b.my_access.list(size=access_limit + increase))['count'] > len(
491-
access_data['accesses']
492-
) and len({a['papId'] for a in access_data['accesses']}) < access_limit:
505+
while (access_data := self.b.my_access.list(search_text=search_text, size=access_limit + increase))[
506+
'count'
507+
] > len(access_data['accesses']) and len({a['papId'] for a in access_data['accesses']}) < access_limit:
493508
increase += max(25, round(access_data['count'] * 0.25))
494509
apps = {a['appContainerId']: a for a in access_data.get('apps', [])}
495510
envs = {e['environmentId']: e for e in access_data.get('environments', [])}
@@ -518,18 +533,15 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option
518533
'profile_description': profile['papDescription'],
519534
'profile_id': profile_id,
520535
'profile_name': profile['papName'],
521-
'session_attributes': profile.get(
522-
'sessionAttributes', self._get_missing_session_attributes(app_id, profile_id)
523-
),
536+
'session_attributes': profile['sessionAttributes'],
524537
}
525538
if row not in access_output:
526539
access_output.append(row)
527540
data += access_output[:access_limit] if access_limit else access_output
528541
if self.b.feature_flags.get('server-access') and (not profile_type or profile_type == 'my-resources'):
529-
if not (resource_limit := int(self.config.my_resources_retrieval_limit)):
530-
profiles = self.b.my_resources.list_profiles()
531-
else:
532-
profiles = self.b.my_resources.list(size=resource_limit)
542+
resource_limit = int(self.config.my_resources_retrieval_limit)
543+
profiles = self.b.my_resources.list(search_text=search_text, size=resource_limit)
544+
if resource_limit:
533545
profiles = profiles['data']
534546
for item in profiles:
535547
row = {

src/pybritive/commands/ls.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,68 @@ def ls():
1212

1313
@ls.command()
1414
@build_britive
15-
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
16-
def applications(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
15+
@britive_options(names='search_text,format,tenant,token,silent,passphrase,federation_provider')
16+
def applications(ctx, search_text, output_format, tenant, token, silent, passphrase, federation_provider):
1717
"""List applications for the currently authenticated identity."""
18-
ctx.obj.britive.list_applications()
18+
ctx.obj.britive.list_applications(search_text=search_text)
1919

2020

2121
@ls.command()
2222
@build_britive
2323
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
24-
def environments(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
24+
def approvals(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
25+
"""List approvals for the currently authenticated identity."""
26+
ctx.obj.britive.list_approvals()
27+
28+
29+
@ls.command()
30+
@build_britive
31+
@britive_options(names='search_text,format,tenant,token,silent,passphrase,federation_provider')
32+
def environments(ctx, search_text, output_format, tenant, token, silent, passphrase, federation_provider):
2533
"""List environments for the currently authenticated identity."""
26-
ctx.obj.britive.list_environments()
34+
ctx.obj.britive.list_environments(search_text=search_text)
2735

2836

2937
@ls.command()
3038
@build_britive
31-
@britive_options(names='checked_out,profile_type,output_format,tenant,token,silent,passphrase,federation_provider')
32-
def profiles(ctx, checked_out, profile_type, output_format, tenant, token, silent, passphrase, federation_provider):
39+
@britive_options(
40+
names='checked_out,profile_type,search_text,output_format,tenant,token,silent,passphrase,federation_provider'
41+
)
42+
def profiles(
43+
ctx,
44+
checked_out,
45+
profile_type,
46+
search_text,
47+
output_format,
48+
tenant,
49+
token,
50+
silent,
51+
passphrase,
52+
federation_provider,
53+
):
3354
"""List profiles for the currently authenticated identity."""
34-
ctx.obj.britive.list_profiles(checked_out=checked_out, profile_type=profile_type)
55+
ctx.obj.britive.list_profiles(checked_out=checked_out, profile_type=profile_type, search_text=search_text)
3556

3657

3758
@ls.command()
3859
@build_britive
3960
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
40-
def secrets(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
41-
"""List secrets for the currently authenticated identity."""
42-
ctx.obj.britive.list_secrets()
61+
def requests(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
62+
"""List requests for the currently authenticated identity."""
63+
ctx.obj.britive.list_requests()
4364

4465

4566
@ls.command()
4667
@build_britive
47-
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
48-
def approvals(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
49-
"""List approvals for the currently authenticated identity."""
50-
ctx.obj.britive.list_approvals()
68+
@britive_options(names='search_text,format,tenant,token,silent,passphrase,federation_provider')
69+
def resources(ctx, search_text, output_format, tenant, token, silent, passphrase, federation_provider):
70+
"""List resources for the currently authenticated identity."""
71+
ctx.obj.britive.list_resources(search_text=search_text)
5172

5273

5374
@ls.command()
5475
@build_britive
55-
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
56-
def resources(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
57-
"""List resources for the currently authenticated identity."""
58-
ctx.obj.britive.list_resources()
76+
@britive_options(names='search_text,format,tenant,token,silent,passphrase,federation_provider')
77+
def secrets(ctx, search_text, output_format, tenant, token, silent, passphrase, federation_provider):
78+
"""List secrets for the currently authenticated identity."""
79+
ctx.obj.britive.list_secrets(search_text=search_text)

src/pybritive/helpers/aws_credential_process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def get_args():
128128
args = {
129129
'tenant': None,
130130
'token': None,
131-
'passphrase': None,
131+
'passphrase': os.getenv('PYBRITIVE_ENCRYPTED_CREDENTIAL_PASSPHRASE'),
132132
'force_renew': None,
133133
'profile': None,
134134
'federation_provider': None,

src/pybritive/helpers/credentials.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import random
77
import time
8+
import uuid
89
import webbrowser
910
from pathlib import Path
1011
from typing import Optional
@@ -345,10 +346,15 @@ def decrypt(self, encrypted_access_token: str):
345346
try:
346347
return self.string_encryptor.decrypt(ciphertext=encrypted_access_token)
347348
except InvalidPassphraseException:
348-
self.cli.print('invalid passphrase provided - wiping credentials and forcing a re-authentication.')
349-
self.delete()
350-
self.credentials = self.load() or {}
351-
return self.get_token()
349+
try:
350+
self.passphrase = str(uuid.getnode())
351+
self.string_encryptor = StringEncryption(passphrase=self.passphrase)
352+
return self.string_encryptor.decrypt(ciphertext=encrypted_access_token)
353+
except InvalidPassphraseException:
354+
self.cli.print('invalid passphrase provided - wiping credentials and forcing a re-authentication.')
355+
self.delete()
356+
self.credentials = self.load() or {}
357+
return self.get_token()
352358

353359
def encrypt(self, decrypted_access_token: str):
354360
return self.string_encryptor.encrypt(plaintext=decrypted_access_token)

src/pybritive/helpers/encryption.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import base64
2+
import hashlib
23
import os
3-
import uuid
4+
import platform
5+
from getpass import getuser
46
from typing import Optional
57

68
from cryptography.fernet import Fernet, InvalidToken
@@ -15,7 +17,12 @@ class InvalidPassphraseException(Exception):
1517

1618
class StringEncryption:
1719
def __init__(self, passphrase: Optional[str] = None):
18-
self.passphrase = passphrase or str(uuid.getnode()) # TODO change?
20+
self.passphrase = (
21+
passphrase
22+
or hashlib.sha256(
23+
'|'.join([getuser(), *platform.uname()._asdict().values()]).replace(' ', '').encode('utf-8')
24+
).hexdigest()
25+
)
1926

2027
@staticmethod
2128
def _salt():

src/pybritive/helpers/k8s_exec.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
from sys import argv, exit
23

34

@@ -8,7 +9,12 @@ def get_args():
89
argv[1:], 't:T:p:F:hv', ['tenant=', 'token=', 'passphrase=', 'federation-provider=', 'help', 'version']
910
)[0]
1011

11-
args = {'tenant': None, 'token': None, 'passphrase': None, 'federation_provider': None}
12+
args = {
13+
'tenant': None,
14+
'token': None,
15+
'passphrase': os.getenv('PYBRITIVE_ENCRYPTED_CREDENTIAL_PASSPHRASE'),
16+
'federation_provider': None,
17+
}
1218

1319
for opt, arg in options:
1420
if opt in ('-t', '--tenant'):

0 commit comments

Comments
 (0)