diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ee0ac..79a8767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log (v2.8.1+) +## v4.5.0 [2026-01-09] + +__What's New:__ + +* Added support for dynamic resources. +* Added support for custom headers for my resources and my secrets. + +__Enhancements:__ + +* None + +__Bug Fixes:__ + +* Fixed trailing slash in url bug which affects python 3.14. + +__Dependencies:__ + +* None + +__Other:__ + +* None + ## v4.4.0 [2025-10-24] __What's New:__ diff --git a/src/britive/access_broker/pools.py b/src/britive/access_broker/pools.py index 0692c94..309c659 100644 --- a/src/britive/access_broker/pools.py +++ b/src/britive/access_broker/pools.py @@ -174,7 +174,7 @@ def add_label(self, pool_id: str, key: str, values: list) -> dict: params = {'key': key, 'label-values': values} - return self.britive.post(f'{self.base_url}/{pool_id}/labels/', json=params) + return self.britive.post(f'{self.base_url}/{pool_id}/labels', json=params) def list_labels(self, pool_id: str) -> list: """ diff --git a/src/britive/access_broker/resources/types.py b/src/britive/access_broker/resources/types.py index 61d1b5f..f5eca63 100644 --- a/src/britive/access_broker/resources/types.py +++ b/src/britive/access_broker/resources/types.py @@ -13,7 +13,7 @@ def create(self, name: str, description: str = '', fields: list = None) -> dict: Example: [ { 'name': 'string', - 'paramType': 'string'|'multiline'|'password', + 'paramType': 'string'|'multiline'|'password'|'ip-cidr'|'regex'|'list', 'isMandatory': True|False }, ... @@ -58,7 +58,7 @@ def update(self, resource_type_id: str, description: str = None, fields: list = Example: [ { 'name': 'string', - 'paramType': 'string'|'multiline'|'password', + 'paramType': 'string'|'multiline'|'password'|'ip-cidr'|'regex'|'list', 'isMandatory': True|False }, ... diff --git a/src/britive/helpers/methods.py b/src/britive/helpers/methods.py index 0fcf58d..e9176dc 100644 --- a/src/britive/helpers/methods.py +++ b/src/britive/helpers/methods.py @@ -34,13 +34,15 @@ def get_profile_and_environment_ids_given_names( raise ValueError(f'profile `{profile_name}` found but not in environment `{environment_name}`.') return ids - def get_profile_and_resource_ids_given_names(self, profile_name: str, resource_name: str) -> dict: + def get_profile_and_resource_ids_given_names( + self, profile_name: str, resource_name: str, headers: dict = None + ) -> dict: resource_profile_map = { f'{item["resourceName"].lower()}|{item["profileName"].lower()}': { 'profile_id': item['profileId'], 'resource_id': item['resourceId'], } - for item in self.britive.get(f'{self.britive.base_url}/resource-manager/my-resources') + for item in self.britive.get(f'{self.britive.base_url}/resource-manager/my-resources', headers=headers) } item = resource_profile_map.get(f'{resource_name.lower()}|{profile_name.lower()}') diff --git a/src/britive/my_approvals.py b/src/britive/my_approvals.py index 62fae38..d06b20c 100644 --- a/src/britive/my_approvals.py +++ b/src/britive/my_approvals.py @@ -52,4 +52,4 @@ def list(self) -> dict: params = {'requestType': 'myApprovals'} - return self.britive.get(f'{self.base_url}/', params=params) + return self.britive.get(f'{self.base_url}', params=params) diff --git a/src/britive/my_requests.py b/src/britive/my_requests.py index 657c64d..851a1cc 100644 --- a/src/britive/my_requests.py +++ b/src/britive/my_requests.py @@ -34,7 +34,7 @@ def list(self) -> list: :return: List of My Requests. """ - return self.britive.get(f'{self.base_url}/', params={'requestType': 'myRequests'}) + return self.britive.get(f'{self.base_url}', params={'requestType': 'myRequests'}) def approval_request_status(self, request_id: str) -> dict: """ diff --git a/src/britive/my_resources.py b/src/britive/my_resources.py index 30ac51e..95e5dea 100644 --- a/src/britive/my_resources.py +++ b/src/britive/my_resources.py @@ -51,7 +51,14 @@ def __init__(self, britive) -> None: self.withdraw_approval_request_by_name = __my_requests.withdraw_approval_request_by_name # Let's just mimic my_access.list functionality for now. - def list(self, filter_text: str = None, list_type: str = None, search_text: str = None, size: int = None) -> dict: + def list( + self, + filter_text: str = None, + list_type: str = None, + search_text: str = None, + size: int = None, + headers: dict = None, + ) -> dict: """ List the resource details for the current user. @@ -59,6 +66,12 @@ def list(self, filter_text: str = None, list_type: str = None, search_text: str :param list_type: filter resources by type, e.g. `list_type='frequently-used'` :param search_text: filter resources by search text. :param size: reduce the size of the response to the specified limit. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Dict of resource details. """ @@ -72,15 +85,23 @@ def list(self, filter_text: str = None, list_type: str = None, search_text: str if size: params.update(page=0, size=size) - return self.britive.get(self.base_url, params=params) + return self.britive.get(self.base_url, params=params, headers=headers) - def list_profiles(self, filter_text: str = None, list_type: str = None, search_text: str = None) -> list: + def list_profiles( + self, filter_text: str = None, list_type: str = None, search_text: str = None, headers: dict = None + ) -> list: """ List the profiles for which the user has access. :param filter_text: filter resource by key, e.g. `filter_text='key eq env'` :param list_type: filter resources by type, e.g. `list_type='frequently-used'` :param search_text: filter resources by search text. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of profiles. """ @@ -92,46 +113,70 @@ def list_profiles(self, filter_text: str = None, list_type: str = None, search_t if search_text: params['searchText'] = search_text - return self.britive.get(self.base_url, params=params) + return self.britive.get(self.base_url, params=params, headers=headers) - def search(self, search_text: str) -> list: + def search(self, search_text: str, headers: dict = None) -> list: """ Search the list of resources/profiles for which the user has access. :param search_text: The text to search. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of profiles. """ - return self.list_profiles(search_text=search_text) + return self.list_profiles(search_text=search_text, headers=headers) - def list_checked_out_profiles(self) -> list: + def list_checked_out_profiles(self, headers: dict = None) -> list: """ Return list of details on currently checked out profiles for the user. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of checked out profiles. """ - return [i for i in self.list_profiles() if i['transactionId']] + return [i for i in self.list_profiles(headers=headers) if i['transactionId']] - def list_response_templates(self, transaction_id: str) -> list: + def list_response_templates(self, transaction_id: str, headers: dict = None) -> list: """ List the Response Templates for a checked out profile. :param transaction_id: Transaction ID of the checked out profile. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of response templates. """ - return self.britive.get(f'{self.base_url}/{transaction_id}/templates') + return self.britive.get(f'{self.base_url}/{transaction_id}/templates', headers=headers) - def get_checked_out_profile(self, transaction_id: str) -> dict: + def get_checked_out_profile(self, transaction_id: str, headers: dict = None) -> dict: """ Retrieve details of a given checked out profile. :param transaction_id: The ID of the transaction. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details of the given profile/transaction. """ - for t in self.list_checked_out_profiles(): + for t in self.list_checked_out_profiles(headers=headers): if t['transactionId'] == transaction_id: return t raise TransactionNotFound @@ -149,6 +194,7 @@ def _checkout( ticket_id: str = None, ticket_type: str = None, wait_time: int = 60, + headers: dict = None, ) -> dict: data = {} if justification: @@ -167,7 +213,7 @@ def _checkout( if progress_func and not progress_pending_checked_out_profiles_sent: progress_func('reviewing currently checked out profiles') progress_pending_checked_out_profiles_sent = True - for p in self.list_checked_out_profiles(): + for p in self.list_checked_out_profiles(headers=headers): right_profile = p['profileId'] == profile_id right_resource = p['resourceId'] == resource_id if all([right_profile, right_resource]): @@ -192,7 +238,9 @@ def _checkout( try: transaction = self.britive.post( - f'{self.base_url}/profiles/{profile_id}/resources/{resource_id}/checkout', json=data + f'{self.base_url}/profiles/{profile_id}/resources/{resource_id}/checkout', + json=data, + headers=headers, ) except StepUpAuthenticationRequiredError as e: raise StepUpAuthRequiredButNotProvided(e) from e @@ -234,6 +282,7 @@ def _checkout( transaction_id=transaction_id, transaction=transaction, progress_func=progress_func, + headers=headers, ) transaction['credentials'] = credentials @@ -254,6 +303,7 @@ def checkout( ticket_id: str = None, ticket_type: str = None, wait_time: int = 60, + headers: dict = None, ) -> dict: """ Checkout a profile. @@ -281,6 +331,12 @@ def checkout( :param ticket_type: Optional ITSM ticket type or category :param wait_time: The number of seconds to sleep/wait between polling to check if the profile checkout was approved. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details about the checked out profile, and optionally the credentials generated by the checkout. :raises ApprovalRequiredButNoJustificationProvided: if approval is required but no justification is provided. :raises ProfileApprovalRejected: if the approval request was rejected by the approver. @@ -301,6 +357,7 @@ def checkout( ticket_id=ticket_id, ticket_type=ticket_type, wait_time=wait_time, + headers=headers, ) def checkout_by_name( @@ -316,6 +373,7 @@ def checkout_by_name( ticket_id: str = None, ticket_type: str = None, wait_time: int = 60, + headers: dict = None, ) -> dict: """ Checkout a profile by supplying the names of entities vs. the IDs of those entities. @@ -341,6 +399,12 @@ def checkout_by_name( :param ticket_type: Optional ITSM ticket type or category :param wait_time: The number of seconds to sleep/wait between polling to check if the profile checkout was approved. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details about the checked out profile, and optionally the credentials generated by the checkout. :raises ApprovalRequiredButNoJustificationProvided: if approval is required but no justification is provided. :raises ProfileApprovalRejected: if the approval request was rejected by the approver. @@ -349,7 +413,7 @@ def checkout_by_name( :raises ProfileApprovalWithdrawn: if the approval request was withdrawn by the requester. """ - ids = self._get_profile_and_resource_ids_given_names(profile_name, resource_name) + ids = self._get_profile_and_resource_ids_given_names(profile_name, resource_name, headers=headers) return self._checkout( profile_id=ids['profile_id'], @@ -363,6 +427,7 @@ def checkout_by_name( ticket_id=ticket_id, ticket_type=ticket_type, wait_time=wait_time, + headers=headers, ) def credentials( @@ -372,6 +437,7 @@ def credentials( response_template: str = None, return_transaction_details: bool = False, transaction: dict = None, + headers: dict = None, ) -> Any: """ Return credentials of a checked out profile given the transaction ID. @@ -382,6 +448,12 @@ def credentials( :param return_transaction_details: Optional - whether to return the details of the transaction. Primary use is for internal purposes. :param transaction: Optional - the details of the transaction. Primary use is for internal purposes. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Credentials associated with the checked out profile represented by the specified transaction. """ @@ -390,7 +462,7 @@ def credentials( # or the transaction is not in the state of checkedOut if not transaction or transaction['status'] != 'checkedOut': while True: - transaction = self.get_checked_out_profile(transaction_id=transaction_id) + transaction = self.get_checked_out_profile(transaction_id=transaction_id, headers=headers) if transaction['status'] == 'checkOutSubmitted': # async checkout process if progress_func: progress_func('credential creation') @@ -403,130 +475,257 @@ def credentials( creds = self.britive.post( f'{self.base_url}/{transaction_id}/credentials', params={'templateName': response_template} if response_template else {}, + headers=headers, ) if return_transaction_details: return creds, transaction return creds - def checkin(self, transaction_id: str) -> dict: + def checkin(self, transaction_id: str, headers: dict = None) -> dict: """ Check in a checked out profile. :param transaction_id: The ID of the transaction. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details of the checked in profile. """ - return self.britive.post(f'{self.base_url}/{transaction_id}/check-in') + return self.britive.post(f'{self.base_url}/{transaction_id}/check-in', headers=headers) - def checkin_by_name(self, profile_name: str, resource_name: str) -> dict: + def checkin_by_name(self, profile_name: str, resource_name: str, headers: dict = None) -> dict: """ Check in a checked out profile by supplying the names of entities vs. the IDs of those entities :param profile_name: The name of the profile. Use `list_profiles()` to obtain the eligible profiles. :param resource_name: The name of the environment. Use `list_profiles()` to obtain the eligible resources. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details of the checked in profile. """ - ids = self._get_profile_and_resource_ids_given_names(profile_name, resource_name) + ids = self._get_profile_and_resource_ids_given_names(profile_name, resource_name, headers=headers) transaction_id = None - for profile in self.list_checked_out_profiles(): + for profile in self.list_checked_out_profiles(headers=headers): if profile['resourceId'] == ids['resource_id'] and profile['profileId'] == ids['profile_id']: transaction_id = profile['transactionId'] break if not transaction_id: raise ValueError('no checked out profile found for the given profile_name and resource_name') - return self.checkin(transaction_id=transaction_id) + return self.checkin(transaction_id=transaction_id, headers=headers) - def frequents(self) -> list: + def frequents(self, headers: dict = None) -> list: """ Return list of frequently used profiles for the user. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of profiles. """ - return self.list_profiles(list_type='frequently-used') + return self.list_profiles(list_type='frequently-used', headers=headers) - def favorites(self) -> list: + def favorites(self, headers: dict = None) -> list: """ Return list of favorite profiles for the user. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of profiles. """ - return self.list_profiles(list_type='favorites') + return self.list_profiles(list_type='favorites', headers=headers) - def add_favorite(self, resource_id: str, profile_id: str) -> dict: + def add_favorite(self, resource_id: str, profile_id: str, headers: dict = None) -> dict: """ Add a resource favorite. :param resource_id: The resource ID of the resource favorite to add. :param profile_id: The profile ID of the resource favorite to add. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details of the favorite resource. """ data = {'resource-id': resource_id, 'profile-id': profile_id} - return self.post(f'{self.base_url}/favorites', json=data) + return self.post(f'{self.base_url}/favorites', json=data, headers=headers) - def delete_favorite(self, favorite_id: str) -> None: + def delete_favorite(self, favorite_id: str, headers: dict = None) -> None: """ Delete a resource favorite. :param favorite_id: The ID of the resource favorite to delete. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: None """ - return self.delete(f'{self.base_url}/favorites/{favorite_id}') + return self.delete(f'{self.base_url}/favorites/{favorite_id}', headers=headers) - def get_profile_settings(self, profile_id: str, resource_id: str) -> dict: + def get_profile_settings(self, profile_id: str, resource_id: str, headers: dict = None) -> dict: """ Retrieve settings of a profile. :param profile_id: The ID of the profile. :param resource_id: The ID of the resource. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Dict of the profile settings. """ - return self.britive.get(f'{self.base_url}/{profile_id}/resources/{resource_id}/settings') + return self.britive.get(f'{self.base_url}/{profile_id}/resources/{resource_id}/settings', headers=headers) - def get_profile_settings_by_name(self, profile_name: str, resource_name: str) -> dict: + def get_profile_settings_by_name(self, profile_name: str, resource_name: str, headers: dict = None) -> dict: """ Retrieve settings of a profile by name. :param profile_name: The name of the profile. :param resource_name: The name of the resource. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Dict of the profile settings. """ - ids = self._get_profile_and_resource_ids_given_names(profile_name=profile_name, resource_name=resource_name) + ids = self._get_profile_and_resource_ids_given_names( + profile_name=profile_name, resource_name=resource_name, headers=headers + ) - return self.get_profile_settings(profile_id=ids['profile_id'], resource_id=ids['resource_id']) + return self.get_profile_settings(profile_id=ids['profile_id'], resource_id=ids['resource_id'], headers=headers) - def search_tickets(self, profile_id: str, ticket_type: str, search_text: str = '') -> dict: + def search_tickets(self, profile_id: str, ticket_type: str, search_text: str = '', headers: dict = None) -> dict: """ Search ITSM tickets for a profile. :param profile_id: The ID of the profile. :param ticket_type: The type of ITSM ticket. :param search_text: Optional text to search for in tickets. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Dict of the search results. """ params = {'searchText': search_text} if search_text else {} - return self.britive.get(f'{self.base_url}/profiles/{profile_id}/itsm/{ticket_type}/search', params=params) + return self.britive.get( + f'{self.base_url}/profiles/{profile_id}/itsm/{ticket_type}/search', params=params, headers=headers + ) - def validate_ticket(self, profile_id: str, ticket_type: str, ticket_id: str) -> dict: + def validate_ticket(self, profile_id: str, ticket_type: str, ticket_id: str, headers: dict = None) -> dict: """ Validate an ITSM ticket using the ITSM integration settings for a profile. :param profile_id: The ID of the profile. :param ticket_type: The type of ticket to validate. :param ticket_id: The ID of the ticket to validate. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Dict of the validation results. """ - return self.britive.get(f'{self.base_url}/profiles/{profile_id}/itsm/{ticket_type}/validate/{ticket_id}') + return self.britive.get( + f'{self.base_url}/profiles/{profile_id}/itsm/{ticket_type}/validate/{ticket_id}', headers=headers + ) + + def build( + self, profile_id: str, parent_resource_id: str, name: str, resource_parameters: dict, headers: dict = None + ) -> dict: + """ + Build a dynamic resource. + + :param profile_id: The ID of the profile. + :param parent_resource_id: The ID of the dynamic parent resource + :param name: The name of the resource + :resource_parameters: The parameters to resolve the dynamic resource + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } + :return: Dict of the resource results. + """ + + data = { + 'parentResourceId': parent_resource_id, + 'profileId': profile_id, + 'resourceName': name, + 'resourceParameters': resource_parameters, + } + + return self.britive.post(f'{self.base_url}/user-resources', json=data, headers=headers) + + def delete(self, resource_id: str, headers: dict = None) -> None: + """ + Delete a dynamic resource. + + :param resource_id: The ID of the resource to delete. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } + :return: None + """ + + return self.britive.delete(f'{self.base_url}/user-resources/{resource_id}', headers=headers) + + def list_dynamic_parameters(self, resource_id: str, headers: dict = None): + """ + Lists resource parameters for a dynamic resource + + :param resource_id: The ID of the resource. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } + :return: List of parameters + """ + + return self.britive.get(f'{self.base_url}/resource-templates/{resource_id}/dynamic-parameters', headers=headers) diff --git a/src/britive/my_secrets.py b/src/britive/my_secrets.py index 062891a..fea518a 100644 --- a/src/britive/my_secrets.py +++ b/src/britive/my_secrets.py @@ -44,7 +44,7 @@ def __get_vault_id(self) -> str: if 'id' in str(e): raise NoSecretsVaultFound from e - def list(self, path: str = '/', search: str = None) -> list: + def list(self, path: str = '/', search: str = None, headers: dict = None) -> list: """ Recursively list all secrets under the given path. @@ -53,6 +53,12 @@ def list(self, path: str = '/', search: str = None) -> list: :param path: Optional argument used to specify where in the hierarchy to begin listing secrets. Include leading '/' if provided. :param search: Optional argument used to filter the list of returned secrets by searching on the secret name. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: List of secrets for which the provided identity has access to view. """ @@ -61,10 +67,18 @@ def list(self, path: str = '/', search: str = None) -> list: if search: params['filter'] = f"name co '{search}'" - return self.britive.get(f'{self.base_url}/vault/{self.__get_vault_id()}/secrets', params=params) + return self.britive.get( + f'{self.base_url}/vault/{self.__get_vault_id()}/secrets', params=params, headers=headers + ) def view( - self, path: str, justification: str = None, otp: str = None, wait_time: int = 60, max_wait_time: int = 600 + self, + path: str, + justification: str = None, + otp: str = None, + wait_time: int = 60, + max_wait_time: int = 600, + headers: dict = None, ) -> dict: """ Retrieve the decrypted secret value. @@ -75,6 +89,12 @@ def view( :param wait_time: The number of seconds to sleep/wait between polling to check if the secret request was approved. :param max_wait_time: The maximum number of seconds to wait for an approval before throwing an exception. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } :return: Details of the decrypted secret. :raises AccessDenied: if the caller does not have access to the secret being requested. @@ -105,7 +125,10 @@ def view( # attempt to get the secret value and return it return self.britive.post( - f'{self.base_url}/vault/{vault_id}/accesssecrets', params=params, json=data if first else None + f'{self.base_url}/vault/{vault_id}/accesssecrets', + params=params, + json=data if first else None, + headers=headers, )['value'] except EvaluationError as e: raise AccessDenied(e) from e @@ -123,7 +146,13 @@ def view( raise e def download( - self, path: str, justification: str = None, otp: str = None, wait_time: int = 60, max_wait_time: int = 600 + self, + path: str, + justification: str = None, + otp: str = None, + wait_time: int = 60, + max_wait_time: int = 600, + headers: dict = None, ) -> dict: """ Retrieve the decrypted secret file. @@ -141,6 +170,13 @@ def download( request was approved. :param max_wait_time: The maximum number of seconds to wait for an approval before throwing an exception. + :param headers: Any additional headers + Example: + { + "X-On-Behalf-Of": "Bearer ... | user@... | username", + ... + } + :return: Dict containing the filename of the downloaded file and the content of the file as bytes. :raises AccessDenied: if the caller does not have access to the secret being requested. :raises ApprovalRequiredButNoJustificationProvided: if approval is required but no justification is provided. @@ -159,7 +195,7 @@ def download( raise StepUpAuthFailed # attempt to get the secret file and return it - return self.britive.get(f'{self.base_url}/vault/{vault_id}/downloadfile', params=params) + return self.britive.get(f'{self.base_url}/vault/{vault_id}/downloadfile', params=params, headers=headers) # 403 will be returned when approval is required or access is denied except EvaluationError as e: raise AccessDenied(e) from e @@ -168,7 +204,7 @@ def download( # lets call view so we can go through the full approval process self.view(path=path, justification=justification, otp=otp, wait_time=wait_time, max_wait_time=max_wait_time) # and then we can get the file again - return self.britive.get(f'{self.base_url}/vault/{vault_id}/downloadfile', params=params) + return self.britive.get(f'{self.base_url}/vault/{vault_id}/downloadfile', params=params, headers=headers) except StepUpAuthenticationRequiredError as e: raise StepUpAuthRequiredButNotProvided(e) from e except ForbiddenRequest as e: