Skip to content

Commit 73717d0

Browse files
authored
Merge pull request #98 from britive/develop
v1.5.0rc1
2 parents 888f08f + 68d32b1 commit 73717d0

File tree

10 files changed

+92
-28
lines changed

10 files changed

+92
-28
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
* As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing time for full QA testing before moving the release candidate to a full release.
44

5+
## v1.5.0rc1 [2023-09-18]
6+
#### What's New
7+
* None
8+
9+
#### Enhancements
10+
* Enrich shell completion results for the `api` command
11+
12+
#### Bug Fixes
13+
* Fixes an issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack
14+
* Fixes an issue with `ssh-add` and temporary keys filling up the `ssh-agent` due to the order of command flags
15+
* Fixes and issue with `checkin` checking in the wrong profile type (programmatic vs console)
16+
17+
#### Dependencies
18+
* `britive>=2.21.0`
19+
20+
#### Other
21+
* None
22+
23+
524
## v1.4.0 [2023-07-25]
625
#### What's New
726
* `pybritive ssh gcp identity-aware-proxy` command - supports OS Login and SSH Instance Metadata

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
britive>=2.20.1
1+
britive>=2.21.0
22
certifi>=2022.12.7
33
charset-normalizer==2.1.0
44
click==8.1.3

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pybritive
3-
version = 1.4.0
3+
version = 1.5.0rc1
44
author = Britive Inc.
55
author_email = support@britive.com
66
description = A pure Python CLI for Britive
@@ -26,7 +26,7 @@ install_requires =
2626
toml
2727
cryptography>=41.0.0
2828
python-dateutil
29-
britive>=2.20.1
29+
britive>=2.21.0
3030
jmespath
3131
pyjwt
3232

src/pybritive/britive_cli.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen
377377
cli=self
378378
)
379379

380-
def checkin(self, profile):
380+
def checkin(self, profile, console):
381381
self.login()
382382
self._set_available_profiles()
383383
parts = self._split_profile_into_parts(profile)
@@ -388,18 +388,21 @@ def checkin(self, profile):
388388
application_name=parts['app']
389389
)
390390

391+
access_type = 'CONSOLE' if console else 'PROGRAMMATIC'
392+
391393
transaction_id = None
392394
application_type = None
393395
for checked_out_profile in self.b.my_access.list_checked_out_profiles():
394396
same_env = checked_out_profile['environmentId'] == ids['environment_id']
395397
same_profile = checked_out_profile['papId'] == ids['profile_id']
396-
if all([same_env, same_profile]):
398+
same_access_type = checked_out_profile['accessType'] == access_type
399+
if all([same_env, same_profile, same_access_type]):
397400
transaction_id = checked_out_profile['transactionId']
398401

399402
for available_profile in self.available_profiles:
400403
same_env_2 = checked_out_profile['environmentId'] == available_profile['env_id']
401404
same_profile_2 = checked_out_profile['papId'] == available_profile['profile_id']
402-
if all([same_env_2, same_profile_2]):
405+
if all([same_env_2, same_profile_2, access_type == 'PROGRAMMATIC']):
403406
application_type = available_profile['app_type'].lower()
404407
break
405408
break
@@ -937,7 +940,7 @@ def _ssh_generate_key(self, username, hostname, key_source):
937940

938941
# and if we are using ssh-agent we need to add the private key via ssh-add
939942
if key_source == 'ssh-agent':
940-
subprocess.run(['ssh-add', str(pem_file), '-t', '60', '-q'])
943+
subprocess.run(['ssh-add', '-t', '60', '-q', str(pem_file)])
941944

942945
return {
943946
'private_key_filename': pem_file,

src/pybritive/commands/checkin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@
66

77
@click.command()
88
@build_britive
9-
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
9+
@britive_options(names='console,tenant,token,silent,passphrase,federation_provider')
1010
@click_smart_profile_argument
11-
def checkin(ctx, tenant, token, silent, passphrase, federation_provider, profile):
11+
def checkin(ctx, console, tenant, token, silent, passphrase, federation_provider, profile):
1212
"""Checkin a profile.
1313
1414
This command takes 1 required argument `PROFILE`. This should be a string representation of the profile
1515
that should be checked in. Format is `application name/environment name/profile name`.
1616
"""
1717
ctx.obj.britive.checkin(
18-
profile=profile
18+
profile=profile,
19+
console=console
1920
)
2021

2122

src/pybritive/completers/api.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from britive.britive import Britive
2-
import json
2+
from click.shell_completion import CompletionItem
3+
import inspect
34

45

56
def api_completer(ctx, param, incomplete):
@@ -16,17 +17,46 @@ def api_completer(ctx, param, incomplete):
1617
for part in parts:
1718
b = getattr(b, part)
1819

20+
existing = '.'.join(parts)
21+
1922
options = []
2023

2124
# vars happen at all levels
22-
options += [var for var, value in vars(b).items() if str(value).startswith('<britive.') and var != 'britive']
25+
for var, value in vars(b).items():
26+
# filter out things which should not show as completion items
27+
if not str(value).startswith('<britive.') or var == 'britive':
28+
continue
29+
30+
method = f'{existing}.{var}' if not_base_level else var
31+
32+
if method.lower().startswith(incomplete.lower()):
33+
doc_line = f'methods related to {var}'
34+
try:
35+
doc_line = inspect.getdoc(getattr(b, var)).split('\n')[0]
36+
except:
37+
pass
38+
options.append(CompletionItem(method, help=doc_line))
2339

2440
# dir only happens at non "base" levels
2541
if not_base_level:
26-
options += [func for func in dir(b) if callable(getattr(b, func)) and not func.startswith("_")]
42+
# for each method in the class
43+
for func in dir(b):
44+
# filter out methods which are not callable and methods which are not "public"
45+
if not callable(getattr(b, func)) or func.startswith("_"):
46+
continue
2747

28-
# pull it all back together and make it look nice
29-
existing = '.'.join(parts)
30-
options = [f'{existing}.{o}' if not_base_level else o for o in options]
48+
method = f'{existing}.{func}'
49+
50+
# if this method is a potential match add it to the completion list
51+
if method.lower().startswith(incomplete.lower()):
52+
# grab the doc string if present
53+
doc_line = f'no docs found for {method}'
54+
55+
try:
56+
doc_line = inspect.getdoc(getattr(b, func)).split('\n')[0]
57+
except:
58+
pass
59+
60+
options.append(CompletionItem(method, help=doc_line))
61+
return options
3162

32-
return [o for o in options if o.lower().startswith(incomplete.lower())]

src/pybritive/completers/api_command.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,27 @@ def get_dynamic_method_parameters(method):
1414

1515
# parse the method, so we can determine where in the "hierarchy" we are
1616
# and what commands/subcommands the user should be presented with
17+
1718
for part in method.split('.'):
1819
b = getattr(b, part)
1920

2021
params = {}
2122
spec = inspect.getfullargspec(b)
22-
2323
# reformat parameters into a more consumable dict while holds all the required details
2424
helper = spec[6]
2525
helper.pop('return', None)
26+
2627
for param, param_type in helper.items():
27-
params[param] = {
28-
'type': str(param_type).split("'")[1]
29-
}
28+
params[param] = {}
3029

31-
defaults = list(spec[3])
32-
names = list(spec[0])
30+
defaults = [] if spec[3] is None else list(spec[3])
31+
names = [] if spec[0] is None else list(spec[0])
3332

3433
if len(defaults) > 0:
3534
for i in range(1, len(defaults) + 1):
3635
name = names[-1 * i]
3736
default = defaults[-1 * i]
38-
params[name]['default'] = default
37+
params[name]['default'] = '<empty string>' if default == '' else default
3938

4039
try: # we don't REALLY need the doc string so if there are errors just eat them and move on
4140
doc_lines = inspect.getdoc(b)
@@ -70,6 +69,11 @@ def get_dynamic_method_parameters(method):
7069

7170
param_list.append(param)
7271

72+
param_list.append({
73+
'flag': '---------------------',
74+
'help': 'separator between sdk parameters and cli parameters'
75+
})
76+
7377
return param_list
7478
except Exception as e:
7579
return []

src/pybritive/helpers/api_method_argument_dectorator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import click
22
import pkg_resources
3-
from ..completers.api import api_completer
3+
44

55
click_major_version = int(pkg_resources.get_distribution('click').version.split('.')[0])
66

77

88
def click_smart_api_method_argument(func):
99
if click_major_version >= 8:
10+
from ..completers.api import api_completer
1011
dec = click.argument('method', shell_complete=api_completer)
1112
else:
1213
dec = click.argument('method')

src/pybritive/helpers/credentials.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_pro
5757
# not sure if we really need 32 random bytes or if any random string would work
5858
# but the current britive-cli in node.js does it this way so it will be done the same
5959
# way in python
60-
self.verifier = b64_encode_url_safe(bytes([random.getrandbits(8) for _ in range(0, 32)]))
61-
self.auth_token = b64_encode_url_safe(bytes(hashlib.sha512(self.verifier.encode('utf-8')).digest()))
60+
while True: # will break eventually when we get values that do not include --
61+
self.verifier = b64_encode_url_safe(bytes([random.getrandbits(8) for _ in range(0, 32)]))
62+
self.auth_token = b64_encode_url_safe(bytes(hashlib.sha512(self.verifier.encode('utf-8')).digest()))
63+
64+
# WAF doesn't like to see `--` as it thinks it is a sql injection attempt
65+
if '--' not in self.verifier and '--' not in self.auth_token:
66+
break
67+
6268
self.credentials = self.load() or {}
6369

6470
def _setup_requests_session(self):

src/pybritive/options/console.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
default=False,
77
is_flag=True,
88
show_default=True,
9-
help='Checkout the console access for the profile instead of programmatic access.'
9+
help='Checkout/checkin the console access for the profile instead of programmatic access.'
1010
)
1111

0 commit comments

Comments
 (0)