Skip to content

Commit

Permalink
additional changes to support CLI login changes
Browse files Browse the repository at this point in the history
  • Loading branch information
twratl committed Dec 15, 2023
1 parent 8e4c900 commit a5ed30c
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 21 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

* 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.

## v1.6.1rc5 [2023-12-15]
#### What's New
* None

#### Enhancements
* None

#### Bug Fixes
* Switch to extracting expiration time from the JWT instead of calculating based on auth time + session duration

#### Dependencies
* None

#### Other
* Additional debug logging related to the authentication process

## v1.6.1rc4 [2023-12-14]
#### What's New
* None
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pybritive
version = 1.6.1rc4
version = 1.6.1rc5
author = Britive Inc.
author_email = [email protected]
description = A pure Python CLI for Britive
Expand Down
1 change: 1 addition & 0 deletions src/pybritive/britive_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def login(self, explicit: bool = False, browser: str = None):
except exceptions.UnauthorizedRequest as e:
if '401 - e0000' in str(e).lower():
self.print(f'attempt {counter} of 3 - login failed')
self.debug(f'login error message was {str(e)}')
self.logout()
else:
raise e
Expand Down
64 changes: 44 additions & 20 deletions src/pybritive/helpers/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@
]


# the credentials should expire sooner than the true expiration date
# in case we need to do things like polling for credentials during
# an approval process
credential_expiration_safe_zone_minutes = 0
federation_provider_default_expiration_seconds = 900


Expand All @@ -45,6 +41,10 @@ def b64_encode_url_safe(value: bytes):
return base64.urlsafe_b64encode(value).decode('utf-8').replace('=', '')


class CouldNotExtractExpirationTimeFromJwtException(Exception):
pass


# this base class expects self.credentials to be a dict - so sub classes need to convert to dict
class CredentialManager:
def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None,
Expand Down Expand Up @@ -119,11 +119,23 @@ def perform_interactive_login(self):
else:
credentials = response.json()['authenticationResult']

# calculate a safe expiration time
auth_time = int(credentials.get('authTime', 0))
session_time = int(credentials.get('maxSessionTimeout', 0))
creds_expire_after = auth_time + session_time - (credential_expiration_safe_zone_minutes * 60 * 1000)
credentials['safeExpirationTime'] = creds_expire_after
try:
# attempt to pull the expiration time from the jwt
expiration_time_ms = self._extract_exp_from_jwt(
token=credentials['accessToken'],
verify=False,
convert_to_ms=True
)
self.cli.debug(f'found expiration time {expiration_time_ms} from jwt')
except CouldNotExtractExpirationTimeFromJwtException:
# calculate from other fields in the authentication result
self.cli.debug('could not extract token expiration time from jwt - dropping to use other fields')
auth_time = int(credentials.get('authTime', 0))
session_time = int(credentials.get('maxSessionTimeout', 0))
expiration_time_ms = auth_time + session_time
self.cli.debug(f'found expiration time {expiration_time_ms} from authTime + maxSessionTimeout')

credentials['safeExpirationTime'] = expiration_time_ms

# drop a bunch of unnecessary fields
for field in interactive_login_fields_to_pop:
Expand All @@ -133,6 +145,24 @@ def perform_interactive_login(self):
self.cli.print(f'Authenticated to tenant {self.tenant} via interactive login.')
break

@staticmethod
def _extract_exp_from_jwt(token: str, verify: bool = False, convert_to_ms: bool = False):
try:
expiration_time = jwt.decode(
token,
# validation of the token will occur on the Britive backend
# so not verifying everything here is okay since we are just
# trying to extract the token expiration time so we can store
# it in the ~/.britive/pybritive.credentials[.encrypted] file
options={
'verify_signature': verify,
'verify_aud': verify
}
)['exp']
return expiration_time * (1000 if convert_to_ms else 1)
except Exception:
raise CouldNotExtractExpirationTimeFromJwtException

def perform_federation_provider_authentication(self):
self.cli.print(f'Performing {self.federation_provider} federation provider authentication '
f'against tenant {self.tenant}.')
Expand Down Expand Up @@ -168,17 +198,11 @@ def perform_federation_provider_authentication(self):
token_expires = json.loads(token)['iam_request_headers']['x-britive-expires']
expiration_time = int(parser.parse(token_expires).timestamp() * 1000)
if provider == 'oidc':
expiration_time = jwt.decode(
token,
# validation of the token will occur on the Britive backend
# so not verifying everything here is okay since we are just
# trying to extract the token expiration time so we can store
# it in the ~/.britive/pybritive.credentials[.encrypted] file
options={
'verify_signature': False,
'verify_aud': False
}
)['exp'] * 1000
expiration_time = self._extract_exp_from_jwt(
token=token,
verify=False,
convert_to_ms=True
)
except Exception:
self.cli.print(f'Cannot obtain token expiration time for {self.federation_provider}. Defaulting to '
f'{federation_provider_default_expiration_seconds} seconds.')
Expand Down

0 comments on commit a5ed30c

Please sign in to comment.