From 878a0d0e99eeca3bb57a1165b7f8882437dfdea7 Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:44:14 -0500 Subject: [PATCH 1/7] test:adjustments and uniformity --- tests/100-identity_management-02-tags.py | 4 +- tests/100-identity_management-06-workload.py | 4 +- .../150-secrets_manager-01-secrets_manager.py | 3 +- tests/250-system-01-policies.py | 22 ++--- tests/250-system-04-roles.py | 10 +- tests/250-system-05-permissions.py | 10 +- tests/500-audit_logs-02-webhooks.py | 2 +- tests/cache.py | 99 ++++++++++--------- 8 files changed, 79 insertions(+), 75 deletions(-) diff --git a/tests/100-identity_management-02-tags.py b/tests/100-identity_management-02-tags.py index b8d829e..8644164 100644 --- a/tests/100-identity_management-02-tags.py +++ b/tests/100-identity_management-02-tags.py @@ -78,10 +78,10 @@ def test_disable(cached_tag): def test_update(cached_tag): r = str(random.randint(0, 1000000)) - tag = britive.identity_management.tags.update(cached_tag['userTagId'], name=f'testpythonapteiwrappertag-{r}') + tag = britive.identity_management.tags.update(cached_tag['userTagId'], name=f'pysdktest-tag-{r}') assert isinstance(tag, dict) assert set(tag_keys).issubset(tag.keys()) - assert tag['name'] == f'testpythonapteiwrappertag-{r}' + assert tag['name'] == f'pysdktest-tag-{r}' # set it back for downstream processes britive.identity_management.tags.update(cached_tag['userTagId'], name=cached_tag['name']) diff --git a/tests/100-identity_management-06-workload.py b/tests/100-identity_management-06-workload.py index 19815c0..4ba5db8 100644 --- a/tests/100-identity_management-06-workload.py +++ b/tests/100-identity_management-06-workload.py @@ -18,7 +18,7 @@ def test_identity_provider_create_aws(cached_workload_identity_provider_aws): def test_identity_provider_create_oidc(cached_workload_identity_provider_oidc): assert isinstance(cached_workload_identity_provider_oidc, dict) assert 'id' in cached_workload_identity_provider_oidc - assert cached_workload_identity_provider_oidc['name'].startswith('python-sdk-oidc') + assert cached_workload_identity_provider_oidc['name'].startswith('pysdktest-oidc') assert isinstance(cached_workload_identity_provider_oidc['attributesMap'], list) assert len(cached_workload_identity_provider_oidc['attributesMap']) == 1 @@ -153,7 +153,7 @@ def test_service_identity_assign_and_unassign( def test_identity_provider_delete(cached_workload_identity_provider_oidc, cached_workload_identity_provider_aws): try: # we do not want to delete the pre-existing aws provider - if cached_workload_identity_provider_aws['name'].startswith('python-sdk-aws'): + if cached_workload_identity_provider_aws['name'].startswith('pysdktest-aws'): aws = britive.identity_management.workload.identity_providers.delete( workload_identity_provider_id=cached_workload_identity_provider_aws['id'] ) diff --git a/tests/150-secrets_manager-01-secrets_manager.py b/tests/150-secrets_manager-01-secrets_manager.py index 88c5510..4c22d4a 100644 --- a/tests/150-secrets_manager-01-secrets_manager.py +++ b/tests/150-secrets_manager-01-secrets_manager.py @@ -22,7 +22,7 @@ def test_get_vault(cached_vault): def test_update_vault(cached_vault): - britive.secrets_manager.vaults.update(cached_vault['id'], description='12345') + britive.secrets_manager.vaults.update(vault_id=cached_vault['id'], name=cached_vault['name'], description='12345') vault = britive.secrets_manager.vaults.get_vault_by_id(cached_vault['id']) assert vault['description'] == '12345' @@ -143,6 +143,7 @@ def test_resources_get(): def test_rotate_keys(cached_vault): initial_time = britive.secrets_manager.vaults.get_vault_by_id(cached_vault['id'])['lastRotation'] britive.secrets_manager.vaults.rotate_keys() + sleep(3) current_time = britive.secrets_manager.vaults.get_vault_by_id(cached_vault['id'])['lastRotation'] assert initial_time != current_time tries = 0 diff --git a/tests/250-system-01-policies.py b/tests/250-system-01-policies.py index f273f57..1a26e87 100644 --- a/tests/250-system-01-policies.py +++ b/tests/250-system-01-policies.py @@ -16,14 +16,14 @@ def test_create(cached_system_level_policy): assert isinstance(cached_system_level_policy, dict) assert 'id' in cached_system_level_policy assert 'name' in cached_system_level_policy - assert cached_system_level_policy['name'].startswith('python-sdk') + assert cached_system_level_policy['name'].startswith('pysdktest') def test_create_default(cached_system_level_policy_condition_as_default_json_str): assert isinstance(cached_system_level_policy_condition_as_default_json_str, dict) assert 'id' in cached_system_level_policy_condition_as_default_json_str assert 'name' in cached_system_level_policy_condition_as_default_json_str - assert cached_system_level_policy_condition_as_default_json_str['name'].startswith('python-sdk-') + assert cached_system_level_policy_condition_as_default_json_str['name'].startswith('pysdktest') assert 'condition' in cached_system_level_policy_condition_as_default_json_str assert isinstance(cached_system_level_policy_condition_as_default_json_str['condition'], str) @@ -32,14 +32,14 @@ def test_create_condition_dictionary(cached_system_level_policy_condition_as_dic assert isinstance(cached_system_level_policy_condition_as_dictionary, dict) assert 'id' in cached_system_level_policy_condition_as_dictionary assert 'name' in cached_system_level_policy_condition_as_dictionary - assert cached_system_level_policy_condition_as_dictionary['name'].startswith('python-sdk-') + assert cached_system_level_policy_condition_as_dictionary['name'].startswith('pysdktest') assert 'condition' in cached_system_level_policy_condition_as_dictionary assert isinstance(cached_system_level_policy_condition_as_dictionary['condition'], dict) def test_create_single_nm(cached_tag): policy = britive.system.policies.build( - name='python-sdk', + name='pysdktest', tags=[cached_tag['name']], roles=['UserViewRole'], approval_notification_medium='Email', @@ -48,12 +48,12 @@ def test_create_single_nm(cached_tag): assert isinstance(policy, dict) assert 'name' in policy - assert policy['name'].startswith('python-sdk') + assert policy['name'].startswith('pysdktest') def test_create_multiple_nm(cached_tag): policy = britive.system.policies.build( - name='python-sdk', + name='pysdktest', tags=[cached_tag['name']], roles=['UserViewRole'], approval_notification_medium=['Email'], @@ -62,14 +62,14 @@ def test_create_multiple_nm(cached_tag): assert isinstance(policy, dict) assert 'name' in policy - assert policy['name'].startswith('python-sdk') + assert policy['name'].startswith('pysdktest') def test_get_id(cached_system_level_policy): response = britive.system.policies.get(policy_identifier=cached_system_level_policy['id'], identifier_type='id') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdktest') def test_policies_condition_created_as_str_get_formatted_json(cached_system_level_policy_condition_as_default_json_str): @@ -120,7 +120,7 @@ def test_get_name(cached_system_level_policy): response = britive.system.policies.get(policy_identifier=cached_system_level_policy['name']) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdktest') def test_update_id(cached_system_level_policy, cached_tag): @@ -143,7 +143,7 @@ def test_update_id(cached_system_level_policy, cached_tag): response = britive.system.policies.get(policy_identifier=cached_system_level_policy['id'], identifier_type='id') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdktest') assert len(response['roles']) == 2 @@ -162,7 +162,7 @@ def test_update_name(cached_system_level_policy, cached_tag): response = britive.system.policies.get(policy_identifier=cached_system_level_policy['name'], identifier_type='name') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdktest') assert len(response['roles']) == 3 diff --git a/tests/250-system-04-roles.py b/tests/250-system-04-roles.py index 81ea514..ce86635 100644 --- a/tests/250-system-04-roles.py +++ b/tests/250-system-04-roles.py @@ -16,21 +16,21 @@ def test_create(cached_system_level_role): assert isinstance(cached_system_level_role, dict) assert 'id' in cached_system_level_role assert 'name' in cached_system_level_role - assert cached_system_level_role['name'].startswith('python-sdk') + assert cached_system_level_role['name'].startswith('pysdk') def test_get_id(cached_system_level_role): response = britive.system.roles.get(role_identifier=cached_system_level_role['id'], identifier_type='id') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') def test_get_name(cached_system_level_role): response = britive.system.roles.get(role_identifier=cached_system_level_role['name']) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') def test_update_id(cached_system_level_role): @@ -53,7 +53,7 @@ def test_update_id(cached_system_level_role): response = britive.system.roles.get(role_identifier=cached_system_level_role['id'], identifier_type='id') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') assert len(response['permissions']) == 2 @@ -72,7 +72,7 @@ def test_update_name(cached_system_level_role): response = britive.system.roles.get(role_identifier=cached_system_level_role['name'], identifier_type='name') assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') assert len(response['permissions']) == 3 diff --git a/tests/250-system-05-permissions.py b/tests/250-system-05-permissions.py index df3452e..b51732a 100644 --- a/tests/250-system-05-permissions.py +++ b/tests/250-system-05-permissions.py @@ -16,7 +16,7 @@ def test_create(cached_system_level_permission): assert isinstance(cached_system_level_permission, dict) assert 'id' in cached_system_level_permission assert 'name' in cached_system_level_permission - assert cached_system_level_permission['name'].startswith('python-sdk') + assert cached_system_level_permission['name'].startswith('pysdk') def test_get_id(cached_system_level_permission): @@ -25,14 +25,14 @@ def test_get_id(cached_system_level_permission): ) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') def test_get_name(cached_system_level_permission): response = britive.system.permissions.get(permission_identifier=cached_system_level_permission['name']) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') def test_update_id(cached_system_level_permission): @@ -50,7 +50,7 @@ def test_update_id(cached_system_level_permission): ) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') assert len(response['actions']) == 2 @@ -71,7 +71,7 @@ def test_update_name(cached_system_level_permission): ) assert 'id' in response assert 'name' in response - assert response['name'].startswith('python-sdk') + assert response['name'].startswith('pysdk') assert len(response['actions']) == 3 diff --git a/tests/500-audit_logs-02-webhooks.py b/tests/500-audit_logs-02-webhooks.py index 3666974..ad12b4b 100644 --- a/tests/500-audit_logs-02-webhooks.py +++ b/tests/500-audit_logs-02-webhooks.py @@ -5,4 +5,4 @@ def test_audit_logs_webhook_create(cached_audit_logs_webhook_create, cached_noti assert isinstance(cached_audit_logs_webhook_create, dict) assert cached_audit_logs_webhook_create['notificationMediumId'] == cached_notification_medium_webhook['id'] assert "contains('event.eventType', 'checkout')" in cached_audit_logs_webhook_create['filter'] - assert 'python-sdk-aws-audit-log-webhook' in cached_audit_logs_webhook_create['description'] + assert 'pysdktest-aws-audit-log-webhook' in cached_audit_logs_webhook_create['description'] diff --git a/tests/cache.py b/tests/cache.py index b79c1cb..cf6d4bd 100644 --- a/tests/cache.py +++ b/tests/cache.py @@ -9,7 +9,7 @@ # don't worry about these invalid references - it will be fixed up if we are running local tests # vs running it through tox from britive.britive import Britive -from britive.exceptions import InternalServerError +from britive.exceptions import Conflict, InternalServerError from britive.exceptions.badrequest import UserCreationError britive = Britive() # source details from environment variables @@ -68,8 +68,8 @@ def timestamp(pytestconfig): @cached_resource(name='user') def cached_user(pytestconfig, timestamp): user_to_create = { - 'username': f'testpythonapiwrapper{timestamp}', - 'email': f'testpythonapiwrapper{timestamp}@britive.com', + 'username': f'pysdktest-{timestamp}', + 'email': f'pysdktest.{timestamp}@britive.com', 'firstName': 'TestPython', 'lastName': timestamp, 'password': generate_random_password(), @@ -81,7 +81,7 @@ def cached_user(pytestconfig, timestamp): @pytest.fixture(scope='session') @cached_resource(name='tag') def cached_tag(pytestconfig, timestamp): - tag_to_create = {'name': f'testpythonapiwrappertag-{timestamp}'} + tag_to_create = {'name': f'pysdktest-tag-{timestamp}'} return britive.identity_management.tags.create(**tag_to_create) @@ -89,7 +89,7 @@ def cached_tag(pytestconfig, timestamp): @cached_resource(name='service-identity') def cached_service_identity(pytestconfig, timestamp): service_identity_to_create = { - 'name': f'testpythonapiwrapperserviceidentity{timestamp}', + 'name': f'pysdktest-serviceidentity{timestamp}', 'status': 'active', } try: @@ -102,7 +102,7 @@ def cached_service_identity(pytestconfig, timestamp): @cached_resource(name='service-identity-federated') def cached_service_identity_federated(pytestconfig, timestamp): service_identity_to_create = { - 'name': f'testpythonapiwrapperfederated{timestamp}', + 'name': f'pysdktest-federated{timestamp}', 'status': 'active', } try: @@ -138,7 +138,7 @@ def cached_catalog(pytestconfig): def cached_application(pytestconfig, timestamp, cached_catalog): aws_standalone_catalog_id = cached_catalog['AWS Standalone-1.0']['catalogAppId'] return britive.application_management.applications.create( - catalog_id=aws_standalone_catalog_id, application_name=f'aws-pythonapiwrapper-test-{timestamp}' + catalog_id=aws_standalone_catalog_id, application_name=f'pysdktest-aws-{timestamp}' ) @@ -211,7 +211,7 @@ def cached_group(pytestconfig, cached_application, cached_environment): @cached_resource(name='identity-attribute') def cached_identity_attribute(pytestconfig, timestamp): return britive.identity_management.identity_attributes.create( - name=f'python-sdk-test-{timestamp}', description='test', data_type='String', multi_valued=False + name=f'pysdktest-{timestamp}', description='test', data_type='String', multi_valued=False ) @@ -240,7 +240,7 @@ def cached_profile_policy(pytestconfig, cached_profile, cached_tag): @cached_resource(name='profile-policy-str') def cached_profile_policy_condition_as_json_str(pytestconfig, cached_profile, cached_tag): policy = britive.application_management.profiles.policies.build( - name=f"{cached_profile['papId']}_json", + name=f'{cached_profile["papId"]}_json', description=cached_tag['name'], tags=[cached_tag['name']], ips=['12.12.12.12', '13.13.13.13'], @@ -253,7 +253,7 @@ def cached_profile_policy_condition_as_json_str(pytestconfig, cached_profile, ca @cached_resource(name='profile-policy-dict') def cached_profile_policy_condition_as_dict(pytestconfig, cached_profile, cached_tag): policy = britive.application_management.profiles.policies.build( - name=f"{cached_profile['papId']}_dict", + name=f'{cached_profile["papId"]}_dict', description=cached_tag['name'], tags=[cached_tag['name']], ips=['12.12.12.12', '13.13.13.13'], @@ -266,7 +266,7 @@ def cached_profile_policy_condition_as_dict(pytestconfig, cached_profile, cached @cached_resource(name='profile-approval-policy') def cached_profile_approval_policy(pytestconfig, cached_profile, cached_service_identity, cached_user): policy = britive.application_management.profiles.policies.build( - name=f"{cached_profile['papId']}-2", + name=f'{cached_profile["papId"]}-2', description='', service_identities=[cached_service_identity['username']], approval_notification_medium='Email', @@ -288,7 +288,7 @@ def cached_profile_checkout_request(pytestconfig, cached_profile, cached_service @cached_resource(name='static-session-attribute') def cached_static_session_attribute(pytestconfig, cached_profile): return britive.application_management.profiles.session_attributes.add_static( - profile_id=cached_profile['papId'], tag_name='test-static', tag_value='test' + profile_id=cached_profile['papId'], tag_name='pysdktest-static', tag_value='test' ) @@ -303,7 +303,7 @@ def cached_dynamic_session_attribute(pytestconfig, cached_profile): break return britive.application_management.profiles.session_attributes.add_dynamic( - profile_id=cached_profile['papId'], identity_attribute_id=email_id, tag_name='test-dynamic' + profile_id=cached_profile['papId'], identity_attribute_id=email_id, tag_name='pysdktest-dynamic' ) @@ -334,7 +334,7 @@ def cached_task(pytestconfig, cached_task_service, cached_application, cached_en @cached_resource(name='security-policy') def cached_security_policy(pytestconfig, timestamp, cached_service_identity_token_updated): return britive.security.security_policies.create( - name=f'test-{timestamp}', + name=f'pysdktest-{timestamp}', description='test', ips=['1.1.1.1', '10.0.0.0/16'], effect='Allow', @@ -345,13 +345,13 @@ def cached_security_policy(pytestconfig, timestamp, cached_service_identity_toke @pytest.fixture(scope='session') @cached_resource(name='api-token') def cached_api_token(pytestconfig, timestamp): - return britive.api_tokens.create(name=f'test-{timestamp}', expiration_days=60) + return britive.api_tokens.create(name=f'pysdktest-{timestamp}', expiration_days=60) @pytest.fixture(scope='session') @cached_resource(name='identity-provider') def cached_identity_provider(pytestconfig, timestamp): - return britive.identity_management.identity_providers.create(name=f'pythonapiwrappertest-{timestamp}') + return britive.identity_management.identity_providers.create(name=f'pytsdktest-{timestamp}') @pytest.fixture(scope='session') @@ -413,7 +413,7 @@ def cached_checked_out_profile_by_name(pytestconfig, cached_profile, cached_envi @pytest.fixture(scope='session') @cached_resource(name='notification') def cached_notification(pytestconfig, timestamp): - return britive.workflows.notifications.create(name=f'pythonapiwrappertest-{timestamp}', description='test') + return britive.workflows.notifications.create(name=f'pysdktest-{timestamp}', description='test') @pytest.fixture(scope='session') @@ -443,40 +443,43 @@ def cached_notification_applications(pytestconfig, cached_notification): @pytest.fixture(scope='session') @cached_resource(name='vault') def cached_vault(pytestconfig, timestamp, cached_tag): - if (vault := britive.secrets_manager.vaults.create(name=f'vault-{timestamp}', tags=[cached_tag['userTagId']])).get( - 'errorCode' - ) == 'SM-0026': - vault = {'DONOTDELETE': True, **britive.secrets_manager.vaults.list()} + try: + vault = britive.secrets_manager.vaults.create( + name=f'pysdktestvault-{timestamp}', tags=[cached_tag['userTagId']] + ) + except Conflict as e: + if not (vault := {'DONOTDELETE': True, **britive.secrets_manager.vaults.list()}).get('id'): + raise e return vault @pytest.fixture(scope='session') @cached_resource(name='folder') def cached_folder(pytestconfig, timestamp, cached_vault): - return britive.secrets_manager.folders.create(name=f'folder-{timestamp}', vault_id=cached_vault['id']) + return britive.secrets_manager.folders.create(name=f'pysdktestfolder-{timestamp}', vault_id=cached_vault['id']) @pytest.fixture(scope='session') @cached_resource(name='password-policies') def cached_password_policies(pytestconfig, timestamp): - return britive.secrets_manager.password_policies.create(name=f'pytestpwdpolicy-{timestamp}') + return britive.secrets_manager.password_policies.create(name=f'pysdktestpwdpolicy-{timestamp}') @pytest.fixture(scope='session') @cached_resource(name='pin-policies') def cached_pin_policies(pytestconfig, timestamp): - return britive.secrets_manager.password_policies.create_pin(name=f'pytestpinpolicy-{timestamp}') + return britive.secrets_manager.password_policies.create_pin(name=f'pysdktestpinpolicy-{timestamp}') @pytest.fixture(scope='session') @cached_resource(name='static-secret-templates') def cached_static_secret_template(pytestconfig, timestamp, cached_password_policies): return britive.secrets_manager.static_secret_templates.create( - name=f'test_name-{timestamp}', + name=f'pysdktesttemplate-{timestamp}', password_policy_id=cached_password_policies['id'], + rotation_interval=7, parameters={ 'name': 'Note', - 'description': f'test_template-{timestamp}', 'mask': False, 'required': False, 'type': 'singleLine', @@ -488,7 +491,7 @@ def cached_static_secret_template(pytestconfig, timestamp, cached_password_polic @cached_resource(name='secret') def cached_secret(pytestconfig, timestamp, cached_vault, cached_static_secret_template): return britive.secrets_manager.secrets.create( - name=f'test_secret-{timestamp}', + name=f'pysdktestsecret-{timestamp}', vault_id=cached_vault['id'], static_secret_template_id=cached_static_secret_template['id'], ) @@ -497,7 +500,7 @@ def cached_secret(pytestconfig, timestamp, cached_vault, cached_static_secret_te @pytest.fixture(scope='session') @cached_resource(name='policy') def cached_policy(pytestconfig, timestamp): - policy = britive.secrets_manager.policies.build(f'pytestpolicy-{timestamp}', draft=True, active=False) + policy = britive.secrets_manager.policies.build(f'pysdktestpolicy-{timestamp}', draft=True, active=False) return britive.secrets_manager.policies.create(policy=policy, path='/') @@ -506,7 +509,7 @@ def cached_policy(pytestconfig, timestamp): def cached_notification_medium(pytestconfig, timestamp): return britive.global_settings.notification_mediums.create( notification_medium_type='teams', - name=f'pytest-nm-teams-{timestamp}', + name=f'pysdktest-nm-teams-{timestamp}', url='https://teams.microsoft.com', ) @@ -516,7 +519,7 @@ def cached_notification_medium(pytestconfig, timestamp): def cached_notification_medium_webhook(pytestconfig, timestamp): return britive.global_settings.notification_mediums.create( notification_medium_type='webhook', - name=f'pytest-nm-webhook-{timestamp}', + name=f'pysdktest-nm-wh-{timestamp}', url='https://www.britive.com', ) @@ -526,7 +529,7 @@ def cached_notification_medium_webhook(pytestconfig, timestamp): def cached_access_builder_approvers_groups(pytestconfig, timestamp, cached_application, cached_user): return britive.application_management.access_builder.approvers_groups.create( application_id=cached_application['appContainerId'], - name=f'python-sdk-access-builder-{timestamp}', + name=f'pysdktest-access-builder-{timestamp}', condition='Any', member_list=[{'id': cached_user['userId'], 'memberType': 'User'}], ) @@ -669,7 +672,7 @@ def cached_workload_identity_provider_aws(pytestconfig, timestamp, cached_identi try: return britive.identity_management.workload.identity_providers.create_aws( - name=f'python-sdk-aws-{timestamp}', attributes_map={'UserId': cached_identity_attribute['id']} + name=f'pysdktest-aws-{timestamp}', attributes_map={'UserId': cached_identity_attribute['id']} ) except InternalServerError as e: raise Exception('AWS provider could not be created and none found') from e @@ -679,7 +682,7 @@ def cached_workload_identity_provider_aws(pytestconfig, timestamp, cached_identi @cached_resource(name='workload-identity-provider-oidc') def cached_workload_identity_provider_oidc(pytestconfig, timestamp, cached_identity_attribute): return britive.identity_management.workload.identity_providers.create_oidc( - name=f'python-sdk-oidc-{timestamp}', + name=f'pysdktest-oidc-{timestamp}', attributes_map={'sub': cached_identity_attribute['name']}, issuer_url='https://id.fakedomain.com', ) @@ -689,7 +692,7 @@ def cached_workload_identity_provider_oidc(pytestconfig, timestamp, cached_ident @cached_resource(name='policy-system-level') def cached_system_level_policy(pytestconfig, timestamp, cached_tag): policy = britive.system.policies.build( - name=f'python-sdk-{timestamp}', tags=[cached_tag['name']], roles=['UserViewRole'] + name=f'pysdktest-{timestamp}', tags=[cached_tag['name']], roles=['UserViewRole'] ) return britive.system.policies.create(policy=policy) @@ -698,7 +701,7 @@ def cached_system_level_policy(pytestconfig, timestamp, cached_tag): @cached_resource(name='policy-system-level-condition-default-as-json-str') def cached_system_level_policy_condition_as_default_json_str(pytestconfig, timestamp, cached_tag): policy = britive.system.policies.build( - name=f'python-sdk-condition-default-{timestamp}', + name=f'pysdktest-condition-default-{timestamp}', tags=[cached_tag['name']], roles=['UserViewRole'], ips=['11.11.11.11', '12.12.12.12'], @@ -711,7 +714,7 @@ def cached_system_level_policy_condition_as_default_json_str(pytestconfig, times @cached_resource(name='policy-system-level-condition-as-dict') def cached_system_level_policy_condition_as_dictionary(pytestconfig, timestamp, cached_tag): policy = britive.system.policies.build( - name=f'python-sdk-condition-as-dict-{timestamp}', + name=f'pysdktest-condition-as-dict-{timestamp}', tags=[cached_tag['name']], roles=['UserViewRole'], ips=['11.11.11.11', '12.12.12.12'], @@ -723,7 +726,7 @@ def cached_system_level_policy_condition_as_dictionary(pytestconfig, timestamp, @pytest.fixture(scope='session') @cached_resource(name='role-system-level') def cached_system_level_role(pytestconfig, timestamp): - role = britive.system.roles.build(name=f'python-sdk-{timestamp}', permissions=['NMAdminPermission']) + role = britive.system.roles.build(name=f'pysdktest-{timestamp}', permissions=['NMAdminPermission']) return britive.system.roles.create(role=role) @@ -731,7 +734,7 @@ def cached_system_level_role(pytestconfig, timestamp): @cached_resource(name='permission-system-level') def cached_system_level_permission(pytestconfig, timestamp): permission = britive.system.permissions.build( - name=f'python-sdk-{timestamp}', consumer='apps', actions=['apps.app.view'] + name=f'pysdktest-{timestamp}', consumer='apps', actions=['apps.app.view'] ) return britive.system.permissions.create(permission=permission) @@ -741,7 +744,7 @@ def cached_system_level_permission(pytestconfig, timestamp): def cached_gcp_profile_big_query(pytestconfig, timestamp): response = britive.application_management.profiles.create( application_id=os.getenv('BRITIVE_GCP_TEST_APP_ID'), - name=f'test-bq-constraints-{timestamp}', + name=f'pysdktest-bq-constraints-{timestamp}', scope=[{'type': 'EnvironmentGroup', 'value': '881409387174'}], ) @@ -757,7 +760,7 @@ def cached_gcp_profile_big_query(pytestconfig, timestamp): def cached_gcp_profile_storage(pytestconfig, timestamp): response = britive.application_management.profiles.create( application_id=os.getenv('BRITIVE_GCP_TEST_APP_ID'), - name=f'test-storage-constraints-{timestamp}', + name=f'pysdktest-storage-constraints-{timestamp}', scope=[{'type': 'EnvironmentGroup', 'value': '881409387174'}], ) @@ -774,7 +777,7 @@ def cached_audit_logs_webhook_create(pytestconfig, timestamp, cached_notificatio return britive.audit_logs.webhooks.create_or_update( notification_medium_id=cached_notification_medium_webhook['id'], jmespath_filter="contains('event.eventType', 'checkout')", - description=f'python-sdk-aws-audit-log-webhook-{timestamp}', + description=f'pysdktest-aws-audit-log-webhook-{timestamp}', ) @@ -783,20 +786,20 @@ def cached_audit_logs_webhook_create(pytestconfig, timestamp, cached_notificatio def cached_access_broker_response_template(pytestconfig, timestamp): template_data = 'The user {{name}} has the role {{role}}.' return britive.access_broker.response_templates.create( - name=f'python-sdk-response-template-{timestamp}', template_data=template_data + name=f'pysdktest-response-template-{timestamp}', template_data=template_data ) @pytest.fixture(scope='session') @cached_resource(name='access-broker-profile') def cached_access_broker_profile(pytestconfig, timestamp): - return britive.access_broker.profiles.create(name=f'python-sdk-access-broker-profile-{timestamp}') + return britive.access_broker.profiles.create(name=f'pysdktest-access-broker-profile-{timestamp}') @pytest.fixture(scope='session') @cached_resource(name='access-broker-resource-type') def cached_access_broker_resource_type(pytestconfig, timestamp): - return britive.access_broker.resources.types.create(name=f'python-sdk-resource-type-{timestamp}') + return britive.access_broker.resources.types.create(name=f'pysdktest-resource-type-{timestamp}') @pytest.fixture(scope='session') @@ -806,7 +809,7 @@ def cached_access_broker_resource_permission(pytestconfig, timestamp, cached_acc checkout_file = bytes(f'checkout-testfile-{timestamp}', 'utf-8') return britive.access_broker.resources.permissions.create( resource_type_id=cached_access_broker_resource_type['resourceTypeId'], - name=f'python-sdk-resource-permission-{timestamp}', + name=f'pysdktest-resource-permission-{timestamp}', checkin_file=checkin_file, checkout_file=checkout_file, ) @@ -827,7 +830,7 @@ def cached_access_broker_resource_permission_id( @cached_resource(name='access-broker-resource') def cached_access_broker_resource(pytestconfig, timestamp, cached_access_broker_resource_type): return britive.access_broker.resources.create( - name=f'python-sdk-resource-{timestamp}', resource_type_id=cached_access_broker_resource_type['resourceTypeId'] + name=f'pysdktest-resource-{timestamp}', resource_type_id=cached_access_broker_resource_type['resourceTypeId'] ) @@ -836,7 +839,7 @@ def cached_access_broker_resource(pytestconfig, timestamp, cached_access_broker_ def cached_access_broker_profile_policy(pytestconfig, timestamp, cached_access_broker_profile, cached_user): return britive.access_broker.profiles.policies.create( profile_id=cached_access_broker_profile['profileId'], - name=f'python-sdk-access-broker-profile-policy-{timestamp}', + name=f'pysdktest-access-broker-profile-policy-{timestamp}', access_type='Allow', members={'users': [{'id': cached_user['userId']}]}, ) @@ -847,7 +850,7 @@ def cached_access_broker_profile_policy(pytestconfig, timestamp, cached_access_b def cached_access_broker_resource_label(pytestconfig, timestamp): while True: label = britive.access_broker.resources.labels.create( - name=f'python-sdk-resource-label-{timestamp}', + name=f'pysdktest-resource-label-{timestamp}', values=[{'name': f'{timestamp}-test-value', 'description': f'{timestamp}-test-description'}], ) if britive.access_broker.resources.labels.get(label_id=label['keyId']): From 98914ce7a9369909f24bc74377a6023b2a82abaa Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:45:01 -0500 Subject: [PATCH 2/7] refactor:drop unnecessary base_url --- src/britive/helpers/methods.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/britive/helpers/methods.py b/src/britive/helpers/methods.py index bdb75e7..28d0a32 100644 --- a/src/britive/helpers/methods.py +++ b/src/britive/helpers/methods.py @@ -1,7 +1,6 @@ class HelperMethods: def __init__(self, britive) -> None: self.britive = britive - self.base_url = f'{self.britive.base_url}/access' def get_profile_and_environment_ids_given_names( self, profile_name: str, environment_name: str, application_name: str = None @@ -9,7 +8,7 @@ def get_profile_and_environment_ids_given_names( ids = None environment_found = False profile_found = False - for app in self.britive.get(self.base_url): + for app in self.britive.get(f'{self.britive.base_url}/access'): if application_name and app['appName'].lower() != application_name.lower(): continue if not ( From 7d0beaca794876cd1dbf9d21ba62d84c0090b980 Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:45:43 -0500 Subject: [PATCH 3/7] feat(secrets_manager):missing params and file uploads --- src/britive/secrets_manager/secrets.py | 43 ++++++++++++++++++++------ src/britive/secrets_manager/vaults.py | 8 +++-- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/britive/secrets_manager/secrets.py b/src/britive/secrets_manager/secrets.py index 252355b..c6e3a16 100644 --- a/src/britive/secrets_manager/secrets.py +++ b/src/britive/secrets_manager/secrets.py @@ -10,12 +10,13 @@ def create( self, name: str, vault_id: str, + description: str = '', + file: bytes = None, path: str = '/', - static_secret_template_id: str = '7a5f41d8-f7af-46a0-88f7-edf0403607ae', secret_mode: str = 'shared', secret_nature: str = 'static', + static_secret_template_id: str = '7a5f41d8-f7af-46a0-88f7-edf0403607ae', value: dict = None, - file: bytes = None, ) -> dict: """ Creates a new secret in the vault. @@ -42,20 +43,22 @@ def create( return self.britive.post( f'{self.base_url}/{vault_id}/secrets?path={path}', json={ - 'name': name, + 'description': description, 'entityType': 'secret', - 'staticSecretTemplateId': static_secret_template_id, + 'name': name, 'secretMode': secret_mode, 'secretNature': secret_nature, + 'staticSecretTemplateId': static_secret_template_id, 'value': value, }, ) secret_data = { + 'description': description, 'entityType': 'secret', 'name': name, - 'staticSecretTemplateId': static_secret_template_id, 'secretMode': secret_mode, 'secretNature': secret_nature, + 'staticSecretTemplateId': static_secret_template_id, 'value': value, } return self.britive.post_upload( @@ -63,19 +66,39 @@ def create( files={'file': file, 'secretData': (None, json.dumps(secret_data))}, ) - def update(self, vault_id: str, path: str = '/', value: dict = None) -> None: + def update( + self, + vault_id: str, + path: str = '/', + name: str = None, + description: str = None, + value: dict = None, + file: bytes = None, + ) -> None: """ Updates a secret's value :param vault_id: ID of the vault to update the secret in :param path: path of the secret, include the / at the beginning :param value: value of the secret + :param file: file to upload as the secret :return: None """ - if value is None: - value = {} - - return self.britive.patch(f'{self.base_url}/{vault_id}/secrets?path={path}', json={'value': value}) + secret_data = { + **({'description': description} if description else {}), + **({'name': name} if name else {}), + } + if value: + return self.britive.patch( + f'{self.base_url}/{vault_id}/secrets?path={path}', + json={**secret_data, **{'value': value}}, + ) + if file: + return self.britive.patch( + f'{self.base_url}/{vault_id}/secrets/file?path={path}', + files={'file': file, 'secretData': (None, json.dumps(secret_data))}, + ) + return None def rename(self, vault_id: str, path: str = '/', new_name: str = '') -> None: """ diff --git a/src/britive/secrets_manager/vaults.py b/src/britive/secrets_manager/vaults.py index 7a14582..2f78fcc 100644 --- a/src/britive/secrets_manager/vaults.py +++ b/src/britive/secrets_manager/vaults.py @@ -79,15 +79,15 @@ def delete(self, vault_id: str) -> None: return self.britive.delete(f'{self.base_url}/{vault_id}') - def update(self, vault_id: str, **kwargs) -> None: + def update(self, vault_id: str, name: str, **kwargs) -> None: """ Updates a vault. If not all kwargs a provided, the vault will update with the default values of the unprovided kwargs. :param vault_id: The ID of the vault. + :param name: The name of the vault. :param kwargs: Valid fields are... - name - required description rotationTime - time in days between key rotations encryptionAlgorithm - the encryption algorithm to use for the vault @@ -96,7 +96,9 @@ def update(self, vault_id: str, **kwargs) -> None: :return: None """ - return self.britive.patch(f'{self.base_url}/{vault_id}', json=kwargs) + params = {'name': name, **kwargs} + + return self.britive.patch(f'{self.base_url}/{vault_id}', json=params) def rotate_keys(self) -> None: """ From 48ef5fbc2ff9a1ec3ffcaff960b33e6164a8e4fc Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:48:28 -0500 Subject: [PATCH 4/7] feat(global_settings):add itsm functionality --- src/britive/global_settings/__init__.py | 2 + src/britive/global_settings/itsm/__init__.py | 12 ++ .../itsm/connection_metadata.py | 27 +++++ .../global_settings/itsm/connections.py | 111 ++++++++++++++++++ .../global_settings/itsm/integrations.py | 48 ++++++++ 5 files changed, 200 insertions(+) create mode 100644 src/britive/global_settings/itsm/__init__.py create mode 100644 src/britive/global_settings/itsm/connection_metadata.py create mode 100644 src/britive/global_settings/itsm/connections.py create mode 100644 src/britive/global_settings/itsm/integrations.py diff --git a/src/britive/global_settings/__init__.py b/src/britive/global_settings/__init__.py index 0ff7b79..37ff139 100644 --- a/src/britive/global_settings/__init__.py +++ b/src/britive/global_settings/__init__.py @@ -1,5 +1,6 @@ from .banner import Banner from .firewall import Firewall +from .itsm import Itsm from .notification_mediums import NotificationMediums @@ -8,3 +9,4 @@ def __init__(self, britive) -> None: self.banner = Banner(britive) self.firewall = Firewall(britive) self.notification_mediums = NotificationMediums(britive) + self.itsm = Itsm(britive) diff --git a/src/britive/global_settings/itsm/__init__.py b/src/britive/global_settings/itsm/__init__.py new file mode 100644 index 0000000..d93c3f4 --- /dev/null +++ b/src/britive/global_settings/itsm/__init__.py @@ -0,0 +1,12 @@ +from .connection_metadata import ConnectionMetadata +from .connections import Connections +from .integrations import Integrations + + +class Itsm: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/itsm-manager' + self.connection_metadata = ConnectionMetadata(britive) + self.connections = Connections(britive) + self.integrations = Integrations(britive) diff --git a/src/britive/global_settings/itsm/connection_metadata.py b/src/britive/global_settings/itsm/connection_metadata.py new file mode 100644 index 0000000..5f9b680 --- /dev/null +++ b/src/britive/global_settings/itsm/connection_metadata.py @@ -0,0 +1,27 @@ +from typing import Union + + +class ConnectionMetadata: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/itsm-manager/connection-templates' + + def list(self) -> list[str]: + """ + Get a list of supported ITSM connection types. + + :return: A list of supported ITSM connection type identifiers. + """ + + return self.britive.get(f'{self.base_url}/supported-types') + + def get(self, connection_type: str = '') -> Union[list, dict]: + """ + Get ITSM connection metadata details for all supported types or a specific type. + + :param template_type: Optional. The ITSM connection type to retrieve metadata for (e.g., 'servicenow'). + If empty or not provided, returns metadata for all supported types. + :return: Details of ITSM connection metadata for all supported types or a specific type. + """ + + return self.britive.get(f'{self.base_url}/{connection_type}'.rstrip('/')) diff --git a/src/britive/global_settings/itsm/connections.py b/src/britive/global_settings/itsm/connections.py new file mode 100644 index 0000000..944bdd0 --- /dev/null +++ b/src/britive/global_settings/itsm/connections.py @@ -0,0 +1,111 @@ +class Connections: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/itsm-manager/connections' + + def list(self) -> list: + """ + Get a list of all ITSM connections. + + :return: A list of dictionaries containing details of all ITSM connections. + """ + return self.britive.get(self.base_url) + + def create( + self, + connection_type: str, + auth_type: str, + name: str, + description: str = '', + data: dict = None, + ) -> dict: + """ + Create a new ITSM connection. + + :param connection_type: The type of ITSM connection. + :param auth_type: The authentication type for the connection. + :param name: The name of the connection. + :param data: Additional connection authentication properties as a dictionary. + Example: + { + "apiToken": "...", + "loginUrl": "https://jira.atlassian.com", + "username": "first.last@example.com" + } + :param description: Optional. The description of the connection. + :return: Details of the created ITSM connection. + """ + + payload = { + 'type': connection_type, + 'authType': auth_type, + 'name': name, + 'description': description, + 'data': data or {}, + } + + return self.britive.post(self.base_url, json=payload) + + def get(self, connection_id: str) -> dict: + """ + Get details of a specific ITSM connection by ID. + + :param connection_id: The ID of the ITSM connection to retrieve. + :return: A dictionary containing the details of the specified ITSM connection. + """ + if not connection_id or not connection_id.strip(): + raise ValueError('connection_id must be a non-empty string') + return self.britive.get(f'{self.base_url}/{connection_id}') + + def update( + self, + connection_id: str, + connection_type: str = '', + auth_type: str = '', + name: str = '', + description: str = '', + data: dict = None, + ) -> dict: + """ + Update an existing ITSM connection. + + :param connection_id: The ID of the ITSM connection to update. + :param connection_type: The type of ITSM connection. + :param auth_type: The authentication type for the connection. + :param name: The name of the connection. + :param data: Additional connection authentication properties as a dictionary. + Example: + { + "apiToken": "...", + "loginUrl": "https://jira.atlassian.com", + "username": "first.last@example.com" + } + :param description: Optional. The description of the connection. + :return: A dictionary containing the details of the updated ITSM connection. + """ + + payload = {} + + if connection_type: + payload['type'] = connection_type + if auth_type: + payload['authType'] = auth_type + if name: + payload['name'] = name + if description: + payload['description'] = description + if data: + payload['data'] = data + + return self.britive.patch(f'{self.base_url}/{connection_id}', json=payload) + + def delete(self, connection_id: str) -> None: + """ + Delete an ITSM connection by ID. + + :param connection_id: The ID of the ITSM connection to delete. + :return: None. + """ + if not connection_id or not connection_id.strip(): + raise ValueError('connection_id must be a non-empty string') + self.britive.delete(f'{self.base_url}/{connection_id}') diff --git a/src/britive/global_settings/itsm/integrations.py b/src/britive/global_settings/itsm/integrations.py new file mode 100644 index 0000000..7d811a4 --- /dev/null +++ b/src/britive/global_settings/itsm/integrations.py @@ -0,0 +1,48 @@ +class Integrations: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/itsm-integration' + + def validate_filter( + self, + connection_id: str, + filters: dict = None, + ticket_id: str = '', + ticket_type: str = '', + variables: dict = None, + ) -> dict: + """ + Validate the filter criteria and ticket for the ITSM integration settings. + + :param connection_id: The ID of the ITSM connection to validate against. + :param ticket_type: Optional. The type of ITSM ticket. + :param filters: Optional. Additional filter properties. + :param ticket_id: Optional. ITSM ticket ID. + :param ticket_type: Optional. ITSM ticket type. + :param variables: Optional. Additional filter variables. + :return: Filter validation result. + """ + + payload = {'connectionId': connection_id} + if filters: + payload['filter'] = filters + if ticket_id: + payload['ticketId'] = ticket_id + if ticket_type: + payload['ticketType'] = ticket_type + if variables: + payload['variableMap'] = variables + + return self.britive.post(f'{self.base_url}/filter-validation', json=payload) + + def get_ticket(self, connection_id: str, ticket_id: str, ticket_type: str) -> None: + """ + Get the ticket details. + + :param connection_id: The ID of the ITSM connection. + :param ticket_id: ITSM ticket ID. + :param ticket_type: ITSM ticket type. + :return: Details of the ticket. + """ + + self.britive.get(f'{self.base_url}/connections/{connection_id}/ticketTypes/{ticket_type}/tickets/{ticket_id}') From 49eace1780999fc881371f9b1ff4233ae3cf678c Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:50:07 -0500 Subject: [PATCH 5/7] feat:add advanced_settings functionality --- .../access_broker/profiles/__init__.py | 3 ++ .../access_broker/profiles/policies.py | 16 +++---- .../application_management/__init__.py | 2 + .../advanced_settings.py | 47 +++++++++++++++++++ src/britive/my_access.py | 40 ++++++++++++++-- src/britive/my_resources.py | 26 ++++++++++ src/britive/system/policies.py | 3 ++ 7 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 src/britive/application_management/advanced_settings.py diff --git a/src/britive/access_broker/profiles/__init__.py b/src/britive/access_broker/profiles/__init__.py index cdb4a0b..044c4a0 100644 --- a/src/britive/access_broker/profiles/__init__.py +++ b/src/britive/access_broker/profiles/__init__.py @@ -1,3 +1,5 @@ +from britive.application_management.advanced_settings import AdvancedSettings + from .permissions import Permissions from .policies import Policies @@ -6,6 +8,7 @@ class Profiles: def __init__(self, britive) -> None: self.britive = britive self.base_url = f'{self.britive.base_url}/resource-manager/profiles' + self.advanced_settings = AdvancedSettings(britive, base_url='/resource-manager/profile/{}/advanced-settings') self.permissions = Permissions(britive) self.policies = Policies(britive) diff --git a/src/britive/access_broker/profiles/policies.py b/src/britive/access_broker/profiles/policies.py index d2d90bd..af540cb 100644 --- a/src/britive/access_broker/profiles/policies.py +++ b/src/britive/access_broker/profiles/policies.py @@ -33,16 +33,16 @@ def create( :param condition: Condition of the policy. :param members: Dict of member type objects. Example: { - users: [ + 'users': [ {'id': '...'} ], - tags: [ + 'tags': [ {'id': '...'} ], - tokens: [ + 'tokens': [ {'id': '...'} ], - serviceIdentities: [ + 'serviceIdentities': [ {'id': '...'} ], } @@ -111,16 +111,16 @@ def update( :param condition: Condition of the policy. :param members: Dict of member type objects. Example: { - users: [ + 'users': [ {'id': '...'} ], - tags: [ + 'tags': [ {'id': '...'} ], - tokens: [ + 'tokens': [ {'id': '...'} ], - serviceIdentities: [ + 'serviceIdentities': [ {'id': '...'} ], } diff --git a/src/britive/application_management/__init__.py b/src/britive/application_management/__init__.py index 7078e05..4c44343 100644 --- a/src/britive/application_management/__init__.py +++ b/src/britive/application_management/__init__.py @@ -1,5 +1,6 @@ from .access_builder import AccessBuilderSettings from .accounts import Accounts +from .advanced_settings import AdvancedSettings from .applications import Applications from .environment_groups import EnvironmentGroups from .environments import Environments @@ -14,6 +15,7 @@ class ApplicationManagement: def __init__(self, britive) -> None: self.access_builder = AccessBuilderSettings(britive) self.accounts = Accounts(britive) + self.advanced_settings = AdvancedSettings(britive) self.applications = Applications(britive) self.environment_groups = EnvironmentGroups(britive) self.environments = Environments(britive) diff --git a/src/britive/application_management/advanced_settings.py b/src/britive/application_management/advanced_settings.py new file mode 100644 index 0000000..135e5d1 --- /dev/null +++ b/src/britive/application_management/advanced_settings.py @@ -0,0 +1,47 @@ +class AdvancedSettings: + def __init__(self, britive, base_url: str = '/apps/{}/advanced-settings') -> None: + self.britive = britive + self.base_url = self.britive.base_url + base_url + + def create(self, entity_id: str, settings: dict) -> dict: + """ + Create Advanced Settings for a specific application or profile. + + :param entity_id: The ID of the application or profile to create Advanced Settings for. + :param settings: The Advanced Settings settings. + :return: Details of the created Advanced Settings. + """ + + return self.britive.post(self.base_url.format(entity_id), json=settings) + + def get(self, entity_id: str) -> dict: + """ + Get Advanced Settings for a specific application or profile. + + :param entity_id: The ID of the application or profile to retrieve Advanced Settings for. + :return: Details of the Advanced Settings. + """ + + return self.britive.get(self.base_url.format(entity_id)) + + def update(self, entity_id: str, settings: dict) -> dict: + """ + Update Advanced Settings for a specific application or profile. + + :param entity_id: The ID of the application or profile to update Advanced Settings for. + :param settings: The Advanced Settings settings to update. + :return: Details of the updated Advanced Settings. + """ + + return self.britive.put(self.base_url.format(entity_id), json=settings) + + def delete(self, entity_id: str, settings_id: str) -> None: + """ + Delete Advanced Settings for a specific application or profile. + + :param entity_id: The ID of the application or profile associated with the Advanced Settings. + :param settings_id: The ID of the Advanced Settings settings to delete. + :return: None. + """ + + self.britive.delete(f'{self.base_url.format(entity_id)}/{settings_id}') diff --git a/src/britive/my_access.py b/src/britive/my_access.py index c325024..063f459 100644 --- a/src/britive/my_access.py +++ b/src/britive/my_access.py @@ -19,10 +19,10 @@ from .my_requests import MyAccessRequests approval_exceptions = { - 'rejected': ProfileApprovalRejected(), - 'cancelled': ProfileApprovalWithdrawn(), - 'timeout': ProfileApprovalTimedOut(), - 'withdrawn': ProfileApprovalWithdrawn(), + 'rejected': ProfileApprovalRejected, + 'cancelled': ProfileApprovalWithdrawn, + 'timeout': ProfileApprovalTimedOut, + 'withdrawn': ProfileApprovalWithdrawn, } @@ -637,3 +637,35 @@ def delete_filter(self, filter_id: str) -> None: """ return self.britive.delete(f'{self.base_url}/{self.whoami()["userId"]}/filters/{filter_id}') + + def search_tickets(self, profile_id: str, environment_id: str, ticket_type: str, search_text: str = '') -> dict: + """ + Search ITSM tickets for a profile. + + :param profile_id: The ID of the profile. + :param environment_id: The ID of the environment. + :param ticket_type: The type of ITSM ticket. + :param search_text: Optional text to search for in tickets. + :return: Dict of the search results. + """ + + params = {'searchText': search_text} if search_text else {} + + return self.britive.get( + f'{self.base_url}/{profile_id}/environments/{environment_id}/itsm/{ticket_type}/search', params=params + ) + + def validate_ticket(self, profile_id: str, environment_id: str, ticket_type: str, ticket_id: str) -> dict: + """ + Validate an ITSM ticket using the ITSM integration settings for a profile. + + :param profile_id: The ID of the profile. + :param environment_id: The ID of the environment. + :param ticket_type: The type of ticket to validate. + :param ticket_id: The ID of the ticket to validate. + :return: Dict of the validation results. + """ + + return self.britive.get( + f'{self.base_url}/{profile_id}/environments/{environment_id}/itsm/{ticket_type}/validate/{ticket_id}' + ) diff --git a/src/britive/my_resources.py b/src/britive/my_resources.py index 331629c..30ac51e 100644 --- a/src/britive/my_resources.py +++ b/src/britive/my_resources.py @@ -504,3 +504,29 @@ def get_profile_settings_by_name(self, profile_name: str, resource_name: str) -> ids = self._get_profile_and_resource_ids_given_names(profile_name=profile_name, resource_name=resource_name) return self.get_profile_settings(profile_id=ids['profile_id'], resource_id=ids['resource_id']) + + def search_tickets(self, profile_id: str, ticket_type: str, search_text: str = '') -> 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. + :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) + + def validate_ticket(self, profile_id: str, ticket_type: str, ticket_id: str) -> 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. + :return: Dict of the validation results. + """ + + return self.britive.get(f'{self.base_url}/profiles/{profile_id}/itsm/{ticket_type}/validate/{ticket_id}') diff --git a/src/britive/system/policies.py b/src/britive/system/policies.py index 4233b74..5aa3947 100644 --- a/src/britive/system/policies.py +++ b/src/britive/system/policies.py @@ -173,6 +173,7 @@ def build( # noqa: PLR0913 condition_as_dict: bool = False, stepup_auth: bool = False, always_prompt_stepup_auth: bool = False, + advanced_settings: list = None, ) -> dict: """ Build a policy document given the provided inputs. @@ -231,6 +232,7 @@ def build( # noqa: PLR0913 `True` will result in the policy condition being returned/built as a python dictionary. :param stepup_auth: Indicates if step-up authentication is required to access the resource. :param always_prompt_stepup_auth: Indicates if previous successful verification should be remembered + :param advanced_settings: Optional Advanced Settings settings for this policy. :return: A dict which can be provided as a policy to `create` and `update`. """ @@ -283,6 +285,7 @@ def build( # noqa: PLR0913 'serviceIdentities': [{identifier_type: s} for s in service_identities] if service_identities else None, 'tokens': [{identifier_type: t} for t in tokens] if tokens else None, }, + 'settings': advanced_settings or [], } if not users: From 07d1d7f249d1c027455e34c96e2e375306e2b22c Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 29 May 2025 15:51:07 -0500 Subject: [PATCH 6/7] refactor(profiles):break out for clarity and management --- .../application_management/profiles.py | 766 ------------------ .../profiles/__init__.py | 265 ++++++ .../profiles/additional_settings.py | 65 ++ .../profiles/permissions.py | 186 +++++ .../profiles/policies.py | 191 +++++ .../profiles/session_attributes.py | 127 +++ 6 files changed, 834 insertions(+), 766 deletions(-) delete mode 100644 src/britive/application_management/profiles.py create mode 100644 src/britive/application_management/profiles/__init__.py create mode 100644 src/britive/application_management/profiles/additional_settings.py create mode 100644 src/britive/application_management/profiles/permissions.py create mode 100644 src/britive/application_management/profiles/policies.py create mode 100644 src/britive/application_management/profiles/session_attributes.py diff --git a/src/britive/application_management/profiles.py b/src/britive/application_management/profiles.py deleted file mode 100644 index d86b77b..0000000 --- a/src/britive/application_management/profiles.py +++ /dev/null @@ -1,766 +0,0 @@ -import json -from typing import Union - -from britive import exceptions - -creation_defaults = { - 'expirationDuration': 3600000, - 'extensionDuration': 1800000, - 'notificationPriorToExpiration': 300000, - 'extendable': False, - 'extensionLimit': '1', - 'status': 'active', - 'destinationUrl': '', - 'useDefaultAppUrl': True, - 'description': '', -} - -update_fields_to_keep: list = list(creation_defaults) -update_fields_to_keep.append('name') -update_fields_to_keep.remove('status') - - -class Profiles: - def __init__(self, britive) -> None: - self.britive = britive - self.base_url = f'{self.britive.base_url}/apps' - self.permissions = ProfilePermissions(britive) - self.session_attributes = ProfileSessionAttributes(britive) - self.policies = ProfilePolicies(britive) - - def create(self, application_id: str, name: str, **kwargs) -> dict: - """ - Create a profile. - - :param application_id: The ID of the application under which the profile will be created. - :param name: The name of the profile. This is the only required argument. - :param kwargs: A key/value mapping consisting of the following fields. If any/all are omitted default values - will be used. The keys and default values are provided below. - - - expirationDuration: 3600000 - - extensionDuration: 1800000 - - notificationPriorToExpiration: 300000 - - extendable: False - - extensionLimit: '1' - - status: 'active' - - destinationUrl: '' - - useDefaultAppUrl: True - - description: '' - - scope: if not provided, no scopes will be applied. If provided it must follow the - format listed below. - - [ - { - 'type': 'EnvironmentGroup'|'Environment', - 'value':'ID' - }, - ] - - :return: Details of the newly created profile. - """ - - kwargs['appContainerId'] = application_id - kwargs['name'] = name # required field, so it is being called out explicitly in the method parameters - - # merge defaults and provided information - keys in kwargs will overwrite the defaults in creation_defaults - data = {**creation_defaults, **kwargs} # note python 3.5 or greater but only 3.5 and up are supported so okay! - - return self.britive.post(f'{self.base_url}/{application_id}/paps', json=data) - - def list( - self, - application_id: str, - filter_expression: str = None, - environment_association: str = None, - include_policies: bool = False, - ) -> list: - """ - Return an optionally filtered list of profiles associated with the specified application. - - :param application_id: The ID of the application. - :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and - `co`. Example: name co "Dev Account" - :param environment_association: Only list profiles with associations to the specified environment. Cannot be - used in conjunction with `filter_expression`. Example: `environment_association="109876543210"` - :param include_policies: Defaults to False. If set to True will include all policies on each profile and all - members on each policy. Cannot be used in conjunction with `filter_expression`. - :return: List of profiles. - """ - - if filter_expression and environment_association: - raise exceptions.InvalidRequest( - 'Cannot specify `filter_expression` and `environment_association` in the same request.' - ) - - params = { - 'page': 0, - 'size': 100, - 'view': 'summary', # this is required - omitting it results in a 400 not authorized error - } - - if include_policies: - params['view'] = 'includePolicies' - - if filter_expression: - params['filter'] = filter_expression - - if environment_association: - params['environment'] = environment_association - - return self.britive.get(f'{self.base_url}/{application_id}/paps', params=params) - - def get(self, application_id: str, profile_id: str, summary: bool = None) -> dict: - """ - Return details of the provided profile. - - :param application_id: The ID of the application. - :param profile_id: The ID of the profile. - :param summary: Whether to provide a summarized response. Defaults to None to support backwards compatibility - with the legacy functionality/way of obtaining details of the profile. Setting to True will return a - summarized set of attributes for the profile. Setting to False will return a larger set of attributes - for the profile. - :return: Details of the profile. - :raises: ProfileNotFound if the profile does not exist. - """ - - if summary is None: - for profile in self.list(application_id=application_id): - if profile['papId'] == profile_id: - return profile - raise exceptions.ProfileNotFound - params = {} - if summary: - params['view'] = 'summary' - return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}', params=params) - - def update(self, application_id: str, profile_id: str, **kwargs) -> dict: - """ - Update details of the specified profile. - - :param application_id: The ID of the application. - :param profile_id: The ID of the profile to update. - :param kwargs: Refer to the `create()` method for details on parameters that can be provided. For this update - action no default values will be injected for missing parameters. - :return: Details of the updated profile. - """ - - existing = self.get(application_id=application_id, profile_id=profile_id, summary=True) - base = {key: existing[key] for key in update_fields_to_keep} - - kwargs['appContainerId'] = application_id - data = {**base, **kwargs} - - return self.britive.patch(f'{self.base_url}/{application_id}/paps/{profile_id}', json=data) - - def available_resources(self, profile_id: str, filter_expression: str = None) -> list: - """ - AZURE ONLY. This API is applicable to Azure applications only. - - Returns the list of all resource scopes that are available and can be added to a profile. - - :param profile_id: The ID of the profile. - :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and - `co`. Example: name co "Dev Account" - :return: List of available resource scopes. - """ - - params = {'page': 0, 'size': 100} - - if filter_expression: - params['filter'] = filter_expression - - return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}/resources', params=params) - - def get_scopes(self, profile_id: str) -> list: - """ - Get the scopes associated with the specified profile. - - :param profile_id: The ID of the profile for which scopes will be updated. - :return: List of scopes and associated details. - """ - - return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}/scopes') - - def set_scopes(self, profile_id: str, scopes: list) -> list: - """ - Update the scopes associated with the specified profile. - - :param profile_id: The ID of the profile for which scopes will be updated. - :param scopes: List of scopes. Example below. - [ - { - 'type': 'EnvironmentGroup'|'Environment', - 'value':'ID' - }, - ] - :return: List of scopes and associated details. - """ - - return self.britive.post(f'{self.britive.base_url}/paps/{profile_id}/scopes', json=scopes) - - def add_single_environment_scope(self, profile_id: str, environment_id: str) -> None: - """ - Add a single environment to the scopes associated with the specified profile. - - This API call is useful when there are a large number of environments (500+) as the - `set_scopes()` call may time out. - - :param profile_id: The ID of the profile. - :param environment_id: The ID of the environment which will be added to the profile scopes. - :return: None - """ - - return self.britive.patch(f'{self.britive.base_url}/paps/{profile_id}/scopes/{environment_id}') - - def remove_single_environment_scope(self, profile_id: str, environment_id: str) -> None: - """ - Remove a single environment from the scopes associated with the specified profile. - - This API call is useful when there are a large number of environments (500+) as the - `set_scopes()` call may time out. - - :param profile_id: The ID of the profile. - :param environment_id: The ID of the environment which will be removed from the profile scopes. - :return: None - """ - - return self.britive.delete(f'{self.britive.base_url}/paps/{profile_id}/scopes/{environment_id}') - - def enable(self, application_id: str, profile_id: str) -> dict: - """ - Enables a profile. - - :param application_id: The ID of the application. - :param profile_id: The ID of the profile to enable. - :return: Details of the enabled profile. - """ - - return self.britive.post(f'{self.base_url}/{application_id}/paps/{profile_id}/enabled-statuses') - - def disable(self, application_id: str, profile_id: str) -> dict: - """ - Disables a profile. - - :param application_id: The ID of the application. - :param profile_id: The ID of the profile to disable. - :return: Details of the disabled profile. - """ - - return self.britive.post(f'{self.base_url}/{application_id}/paps/{profile_id}/disabled-statuses') - - def delete(self, application_id: str, profile_id: str) -> None: - """ - Deletes a profile. - - :param application_id: The ID of the application. - :param profile_id: The ID of the profile to delete. - :return: None - """ - - return self.britive.delete(f'{self.base_url}/{application_id}/paps/{profile_id}') - - -class ProfilePermissions: - def __init__(self, britive) -> None: - self.britive = britive - self.base_url = f'{self.britive.base_url}/paps' - self.constraints = ProfilePermissionConstraints(britive) - - def add(self, profile_id: str, permission_type: str, permission_name: str) -> dict: - """ - Add a permission to a profile. - - Call `list_available()` to see what permissions can be added. - - Note that for AWS and OCI permissions are not assigned to profiles as the permissions are tied into the - cloud provider directly (AssumeRole for AWS). - - :param profile_id: The ID of the profile. - :param permission_type: The type of permission. Valid values are `role`, `group`, and `policy`. - :param permission_name: The name of the permission. - :return: Details of the permission added. - """ - - data = {'op': 'add', 'permission': {'name': permission_name, 'type': permission_type}} - - return self.britive.post(f'{self.base_url}/{profile_id}/permissions', json=data) - - def list_assigned(self, profile_id: str, filter_expression: str = None) -> list: - """ - List the permissions assigned to the profile. - - :param profile_id: The ID of the profile. - :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and - `co`. Example: name co "Dev Account" - :return: List of permissions assigned to the profile. - """ - - params = {'page': 0, 'size': 100} - - if filter_expression: - params['filter'] = filter_expression - - return self.britive.get(f'{self.base_url}/{profile_id}/permissions', params=params) - - def list_available(self, profile_id: str) -> list: - """ - List permissions available to be assigned to the profile. - - Note that for AWS and OCI permissions are not assigned to profiles as the permissions are tied into the - cloud provider directly (AssumeRole for AWS). - - :param profile_id: The ID of the profile. - :return: List of permissions that are available to be assigned to the profile. - """ - - params = {'page': 0, 'size': 100, 'query': 'available'} - return self.britive.get(f'{self.base_url}/{profile_id}/permissions', params=params) - - def remove(self, profile_id: str, permission_type: str, permission_name: str) -> dict: - """ - Remove a permission to a profile. - - :param profile_id: The ID of the profile. - :param permission_type: The type of permission. Valid values are `role`, `group`, and `policy`. - :param permission_name: The name of the permission. - :return: Details of the permission removed. - """ - - data = {'op': 'remove', 'permission': {'name': permission_name, 'type': permission_type}} - - return self.britive.post(f'{self.base_url}/{profile_id}/permissions', json=data) - - -class ProfilePermissionConstraints: - def __init__(self, britive) -> None: - self.britive = britive - self.base_url = f'{self.britive.base_url}/paps' - - def list_supported_types(self, profile_id: str, permission_name: str, permission_type: str = 'role') -> list: - """ - Lists the supported constraint types. - - :param profile_id: The ID of the profile. - :param permission_name: The name of the permission for which to list supported constraints. - :param permission_type: The type of permission. Defaults to `role`. - :returns: List of supported constraint types. - """ - - url = ( - f'{self.base_url}/{profile_id}/permissions/{permission_name}/' - f'{permission_type}/supported-constraint-types' - ) - return self.britive.get(url) - - def get(self, profile_id: str, permission_name: str, constraint_type: str, permission_type: str = 'role') -> list: - """ - Gets the list of constraints. - - :param profile_id: The ID of the profile. - :param permission_name: The name of the permission for which to list supported constraints. - :param constraint_type: The type of constraint. - :param permission_type: The type of permission. Defaults to `role`. - :returns: List of constraints for the given constraint type. - """ - - url = ( - f'{self.base_url}/{profile_id}/permissions/{permission_name}/' - f'{permission_type}/constraints/{constraint_type}' - ) - return self.britive.get(url).get('result') - - def lint_condition( - self, profile_id: str, permission_name: str, expression: str, permission_type: str = 'role' - ) -> dict: - """ - Lint the provided condition expression. - - :param profile_id: The ID of the profile. - :param permission_name: The name of the permission for which to lint the condition expression. - :param expression: The condition expression to lint. - :param permission_type: The type of permission. Defaults to `role`. - :returns: Results of the lint operation. - """ - - url = f'{self.base_url}/{profile_id}/permissions/{permission_name}/{permission_type}/constraints/condition' - - params = {'operation': 'validate'} - - data = {'expression': expression} - - return self.britive.put(url, params=params, json=data) - - def add( - self, - profile_id: str, - permission_name: str, - constraint_type: str, - constraint: dict, - permission_type: str = 'role', - ) -> None: - """ - Adds the given constraint. - - :param profile_id: The ID of the profile. - :param permission_name: The name of the permission for which the constraint should be added. - :param constraint_type: The type of constraint. - :param constraint: The constraint to add. If `constraint_type == 'condition'` then this parameter should be a - dict with fields `title`, `description`, and `expression`. Otherwise, this parameter should be a dict with - field `name` or string value. - :param permission_type: The type of permission. Defaults to `role`. - :returns: None. - """ - - url = ( - f'{self.base_url}/{profile_id}/permissions/{permission_name}/' - f'{permission_type}/constraints/{constraint_type}' - ) - - params = {'operation': 'add'} - - return self.britive.put(url, params=params, json=constraint) - - def remove( - self, - profile_id: str, - permission_name: str, - constraint_type: str, - constraint: dict = None, - permission_type: str = 'role', - ) -> None: - """ - Removes the given constraint. - - :param profile_id: The ID of the profile. - :param permission_name: The name of the permission for which the constraint should be removed. - :param constraint_type: The type of constraint. - :param constraint: The constraint to remove. If `constraint_type == 'condition'` then omit this parameter or - set it to `None`. Otherwise, this parameter should be a dict with field `name` or string value. - :param permission_type: The type of permission. Defaults to `role`. - :returns: None. - """ - - url = ( - f'{self.base_url}/{profile_id}/permissions/{permission_name}/' - f'{permission_type}/constraints/{constraint_type}' - ) - params = {'operation': 'remove'} - if constraint is None: - constraint = {} - - return self.britive.put(url, params=params, json=constraint) - - -class ProfileSessionAttributes: - def __init__(self, britive) -> None: - self.britive = britive - self.base_url = f'{self.britive.base_url}/paps' - - def add_static(self, profile_id: str, tag_name: str, tag_value: str, transitive: bool = False) -> dict: - """ - AWS ONLY - Add a static session attribute to the profile. - - :param profile_id: The ID of the profile. - :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. - :param tag_value: THe value of the session tag to include in the AssumeRoleWithSAML call. - :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you - may or may not want this. - :return: Details of added attribute. - """ - - data = { - 'sessionAttributeType': 'Static', - 'transitive': transitive, - 'attributeSchemaId': None, - 'mappingName': tag_name, - 'attributeValue': tag_value, - } - - return self.britive.post(f'{self.base_url}/{profile_id}/session-attributes', json=data) - - def add_dynamic(self, profile_id: str, identity_attribute_id: str, tag_name: str, transitive: bool = False) -> dict: - """ - AWS ONLY - Add a dynamic session attribute to the profile. - - The value will be sourced from the identity attribute specified. - - :param profile_id: The ID of the profile. - :param identity_attribute_id: The ID of the identity attribute. - Call `britive.identity_management.identity_attributes.list()` for which attributes can be provided. - :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. The value will be - dynamically determined based on the value of the specified identity attribute. - :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you - may or may not want this. - :return: Details of added attribute. - """ - - data = { - 'sessionAttributeType': 'Identity', - 'transitive': transitive, - 'attributeSchemaId': identity_attribute_id, - 'mappingName': tag_name, - 'attributeValue': None, - } - - return self.britive.post(f'{self.base_url}/{profile_id}/session-attributes', json=data) - - def update_static( - self, profile_id: str, attribute_id, tag_name: str, tag_value: str, transitive: bool = False - ) -> None: - """ - AWS ONLY - Update the static session attribute to the profile. - - :param profile_id: The ID of the profile. - :param attribute_id: The ID of the session attribute to update. - :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. - :param tag_value: THe value of the session tag to include in the AssumeRoleWithSAML call. - :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you - may or may not want this. - :return: None. - """ - - data = { - 'sessionAttributeType': 'Static', - 'transitive': transitive, - 'attributeSchemaId': None, - 'mappingName': tag_name, - 'attributeValue': tag_value, - 'id': attribute_id, - } - - return self.britive.put(f'{self.base_url}/{profile_id}/session-attributes', json=data) - - def update_dynamic( - self, profile_id: str, attribute_id: str, identity_attribute_id: str, tag_name: str, transitive: bool = False - ) -> dict: - """ - AWS ONLY - Update the dynamic session attribute to the profile. - - :param profile_id: The ID of the profile. - :param attribute_id: The ID of the session attribute to update. - :param identity_attribute_id: The ID of the identity attribute. - Call `britive.identity_management.identity_attributes.list()` for which attributes can be provided. - :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. The value will be - dynamically determined based on the value of the specified identity attribute. - :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you - may or may not want this. - :return: Details of added attribute. - """ - - data = { - 'sessionAttributeType': 'Identity', - 'transitive': transitive, - 'attributeSchemaId': identity_attribute_id, - 'mappingName': tag_name, - 'attributeValue': None, - 'id': attribute_id, - } - - return self.britive.put(f'{self.base_url}/{profile_id}/session-attributes', json=data) - - def list(self, profile_id: str) -> list: - """ - Return a list of session attributes associated with the profile. - - :param profile_id: The ID of the profile. - :return: List of session attributes associated with the profile. - """ - - return self.britive.get(f'{self.base_url}/{profile_id}/session-attributes') - - def remove(self, profile_id: str, attribute_id: str) -> None: - """ - Remove an attribute from the profile. - - :param profile_id: The ID of the profile. - :param attribute_id: The ID of the session attribute. - :return: None. - """ - - return self.britive.delete(f'{self.base_url}/{profile_id}/session-attributes/{attribute_id}') - - -class ProfilePolicies: - def __init__(self, britive) -> None: - self.britive = britive - self.base_url = f'{self.britive.base_url}/paps' - - def build( # noqa: PLR0913 - self, - name: str, - description: str = '', - draft: bool = False, - active: bool = True, - read_only: bool = False, - users: list = None, - tags: list = None, - service_identities: list = None, - ips: list = None, - date_schedule: dict = None, - days_schedule: dict = None, - approval_notification_medium: Union[str, list] = None, - time_to_approve: int = 5, - access_validity_time: int = 120, - approver_users: list = None, - approver_tags: list = None, - access_type: str = 'Allow', - identifier_type: str = 'name', - condition_as_dict: bool = False, - stepup_auth: bool = False, - always_prompt_stepup_auth: bool = False, - ) -> dict: - """ - Build a policy document given the provided inputs. - - :param name: The name of the policy. - :param description: An optional description of the policy. - :param draft: Indicates if the policy is a draft. Defaults to `False`. - :param active: Indicates if the policy is active. Defaults to `True`. - :param read_only: Indicates if the policy is a read only. Defaults to `False`. - :param users: Optional list of user names or ids to which this policy applies. - :param tags: Optional list of tag names or ids to which this policy applies. - :param service_identities: Optional list of service identity names or ids to which this policy applies. - :param ips: Optional list of IP addresses for which this policy applies. Provide in CIDR notation - or dotted decimal format for individual (/32) IP addresses. - :param date_schedule: A dict in the format - { - 'fromDate': '2022-10-29 10:30:00', - 'toDate': '2022-11-05 18:30:00', - 'timezone': 'UTC' - } - Timezone formats can be found in the TZ Identifier column of the following page. - https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - :param days_schedule: A dict in the format - { - 'fromTime': '10:30:00', - 'toTime': '18:30:00', - 'timezone': 'UTC', - 'days': ['MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY','SUNDAY'] - } - Timezone formats can be found in the TZ Identifier column of the following page. - https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - :param approval_notification_medium: Optional notification medium name to which approval requests will be - delivered. Can also specify a list of notification medium names. Specifying this parameter indicates the - desire to enable approvals for this policy. - :param time_to_approve: Optional number of minutes to wait for an approval before denying the action. Defaults - to 5 minutes. - :param access_validity_time: Optional number of minutes the access is valid after approval. Defaults to 120 - minutes. - :param approver_users: Optional list of user names or ids who are to be considered approvers. - If `approval_notification_medium` is set then either `approver_users` or `approver_tags` is required. - :param approver_tags: Optional list of tag names who are considered approvers. - If `approval_notification_medium` is set then either `approver_users` or `approver_tags` is required. - :param access_type: The type of access this policy provides. Valid values are `Allow` and `Deny`. Defaults - to `Allow`. - :param identifier_type: Valid values are `id` or `name`. Defaults to `name`. Represents which type of - identifiers are being provided to the other parameters. Either all identifiers must be names or all - identifiers must be IDs. - :param condition_as_dict: Prior to version 2.22.0 the only acceptable format for the condition block of - a policy was as a stringifed json object. As of 2.22.0 the condition block can also be built as a raw - python dictionary. This parameter will default to `False` to support backwards compatibility. Setting to - `True` will result in the policy condition being returned/built as a python dictionary. - :param stepup_auth: Indicates if step-up authentication is required to checkout profile - :param always_prompt_stepup_auth: Indicates if previous successful verification should be remembered - :return: A dict which can be provided as a profile policy to `create` and `update`. - """ - - policy = self.britive.system.policies.build( - name=name, - description=description, - draft=draft, - active=active, - read_only=read_only, - users=users, - tags=tags, - service_identities=service_identities, - ips=ips, - date_schedule=date_schedule, - days_schedule=days_schedule, - approval_notification_medium=approval_notification_medium, - time_to_approve=time_to_approve, - access_validity_time=access_validity_time, - approver_users=approver_users, - approver_tags=approver_tags, - access_type=access_type, - identifier_type=identifier_type, - condition_as_dict=condition_as_dict, - stepup_auth=stepup_auth, - always_prompt_stepup_auth=always_prompt_stepup_auth, - ) - - # clean up the generic policy response and customize for profiles - policy.pop('permissions', None) - policy.pop('roles', None) - policy['consumer'] = 'papservice' - - return policy - - def list(self, profile_id: str) -> list: - """ - List all policies associated with the provided profile. - - :param profile_id: The ID of the profile. - :return: List of policies. - """ - - return self.britive.get(f'{self.base_url}/{profile_id}/policies') - - def get(self, profile_id: str, policy_id: str, condition_as_dict: bool = False) -> dict: - """ - Retrieve details about a specific policy which is associated with the provided profile. - - :param profile_id: The ID of the profile. - :param policy_id: The ID of the policy. - :param condition_as_dict: Prior to version 2.22.0 a policy condition block was always returned as stringified - json. As of 2.22.0 the SDK now supports returning the condition block of a policy as either stringified json - or a raw python dictionary. The Britive backend will also return the condition block in either format, - depending on a query parameter value. Setting this value to `True` will result in the condition block being - returned as a python dictionary. The default of `False` is to support backwards compatibility. - :return: Details of the policy. - """ - - params = {'conditionJson': condition_as_dict} - - policy = self.britive.get(f'{self.base_url}/{profile_id}/policies/{policy_id}', params=params) - - # it seems profile policy is not honoring conditionJson parameter so doing some extra work here - # to get things into the correct format. if in the future that changes we can perhaps remove - # the below logic. - if 'condition' in policy and condition_as_dict and isinstance(policy['condition'], str): - policy['condition'] = json.loads(policy['condition']) - - return policy - - def create(self, profile_id: str, policy: dict) -> dict: - """ - Create a policy associated with the provided profile. - - :param profile_id: The ID of the profile. - :param policy: The policy contents to create. - :return: Details of the newly created policy. - """ - - return self.britive.post(f'{self.base_url}/{profile_id}/policies', json=policy) - - def update(self, profile_id: str, policy_id: str, policy: dict) -> dict: - """ - Update the contents of the provided policy associated with the provided profile. - - :param profile_id: The ID of the profile. - :param policy_id: The ID of the policy. - :param policy: The policy to update. - :return: Details of the updated policy. - """ - - return self.britive.patch(f'{self.base_url}/{profile_id}/policies/{policy_id}', json=policy) - - def delete(self, profile_id: str, policy_id: str) -> None: - """ - Delete the provided policy associated with the provided profile. - - :param profile_id: The ID of the profile. - :param policy_id: The ID of the policy. - :return: None. - """ - - return self.britive.delete(f'{self.base_url}/{profile_id}/policies/{policy_id}') diff --git a/src/britive/application_management/profiles/__init__.py b/src/britive/application_management/profiles/__init__.py new file mode 100644 index 0000000..6a151e4 --- /dev/null +++ b/src/britive/application_management/profiles/__init__.py @@ -0,0 +1,265 @@ +from britive import exceptions +from britive.application_management.advanced_settings import AdvancedSettings + +from .additional_settings import AdditionalSettings +from .permissions import Permissions +from .policies import Policies +from .session_attributes import SessionAttributes + +creation_defaults = { + 'expirationDuration': 3600000, + 'extensionDuration': 1800000, + 'notificationPriorToExpiration': 300000, + 'extendable': False, + 'extensionLimit': '1', + 'status': 'active', + 'destinationUrl': '', + 'useDefaultAppUrl': True, + 'description': '', +} + +update_fields_to_keep: list = list(creation_defaults) +update_fields_to_keep.append('name') +update_fields_to_keep.remove('status') + + +class Profiles: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/apps' + self.additional_settings = AdditionalSettings(britive) + self.advanced_settings = AdvancedSettings(britive, base_url='/paps/{}/advanced-settings') + self.permissions = Permissions(britive) + self.policies = Policies(britive) + self.session_attributes = SessionAttributes(britive) + + def create(self, application_id: str, name: str, **kwargs) -> dict: + """ + Create a profile. + + :param application_id: The ID of the application under which the profile will be created. + :param name: The name of the profile. This is the only required argument. + :param kwargs: A key/value mapping consisting of the following fields. If any/all are omitted default values + will be used. The keys and default values are provided below. + + - expirationDuration: 3600000 + - extensionDuration: 1800000 + - notificationPriorToExpiration: 300000 + - extendable: False + - extensionLimit: '1' + - status: 'active' + - destinationUrl: '' + - useDefaultAppUrl: True + - description: '' + - scope: if not provided, no scopes will be applied. If provided it must follow the + format listed below. + + [ + { + 'type': 'EnvironmentGroup'|'Environment', + 'value':'ID' + }, + ] + + :return: Details of the newly created profile. + """ + + kwargs['appContainerId'] = application_id + kwargs['name'] = name # required field, so it is being called out explicitly in the method parameters + + # merge defaults and provided information - keys in kwargs will overwrite the defaults in creation_defaults + data = {**creation_defaults, **kwargs} # note python 3.5 or greater but only 3.5 and up are supported so okay! + + return self.britive.post(f'{self.base_url}/{application_id}/paps', json=data) + + def list( + self, + application_id: str, + filter_expression: str = None, + environment_association: str = None, + include_policies: bool = False, + ) -> list: + """ + Return an optionally filtered list of profiles associated with the specified application. + + :param application_id: The ID of the application. + :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and + `co`. Example: name co "Dev Account" + :param environment_association: Only list profiles with associations to the specified environment. Cannot be + used in conjunction with `filter_expression`. Example: `environment_association="109876543210"` + :param include_policies: Defaults to False. If set to True will include all policies on each profile and all + members on each policy. Cannot be used in conjunction with `filter_expression`. + :return: List of profiles. + """ + + if filter_expression and environment_association: + raise exceptions.InvalidRequest( + 'Cannot specify `filter_expression` and `environment_association` in the same request.' + ) + + params = { + 'page': 0, + 'size': 100, + 'view': 'summary', # this is required - omitting it results in a 400 not authorized error + } + + if include_policies: + params['view'] = 'includePolicies' + + if filter_expression: + params['filter'] = filter_expression + + if environment_association: + params['environment'] = environment_association + + return self.britive.get(f'{self.base_url}/{application_id}/paps', params=params) + + def get(self, application_id: str, profile_id: str, summary: bool = None) -> dict: + """ + Return details of the provided profile. + + :param application_id: The ID of the application. + :param profile_id: The ID of the profile. + :param summary: Whether to provide a summarized response. Defaults to None to support backwards compatibility + with the legacy functionality/way of obtaining details of the profile. Setting to True will return a + summarized set of attributes for the profile. Setting to False will return a larger set of attributes + for the profile. + :return: Details of the profile. + :raises: ProfileNotFound if the profile does not exist. + """ + + if summary is None: + for profile in self.list(application_id=application_id): + if profile['papId'] == profile_id: + return profile + raise exceptions.ProfileNotFound + params = {} + if summary: + params['view'] = 'summary' + return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}', params=params) + + def update(self, application_id: str, profile_id: str, **kwargs) -> dict: + """ + Update details of the specified profile. + + :param application_id: The ID of the application. + :param profile_id: The ID of the profile to update. + :param kwargs: Refer to the `create()` method for details on parameters that can be provided. For this update + action no default values will be injected for missing parameters. + :return: Details of the updated profile. + """ + + existing = self.get(application_id=application_id, profile_id=profile_id, summary=True) + base = {key: existing[key] for key in update_fields_to_keep} + + kwargs['appContainerId'] = application_id + data = {**base, **kwargs} + + return self.britive.patch(f'{self.base_url}/{application_id}/paps/{profile_id}', json=data) + + def available_resources(self, profile_id: str, filter_expression: str = None) -> list: + """ + AZURE ONLY. This API is applicable to Azure applications only. + + Returns the list of all resource scopes that are available and can be added to a profile. + + :param profile_id: The ID of the profile. + :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and + `co`. Example: name co "Dev Account" + :return: List of available resource scopes. + """ + + params = {'page': 0, 'size': 100} + + if filter_expression: + params['filter'] = filter_expression + + return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}/resources', params=params) + + def get_scopes(self, profile_id: str) -> list: + """ + Get the scopes associated with the specified profile. + + :param profile_id: The ID of the profile for which scopes will be updated. + :return: List of scopes and associated details. + """ + + return self.britive.get(f'{self.britive.base_url}/paps/{profile_id}/scopes') + + def set_scopes(self, profile_id: str, scopes: list) -> list: + """ + Update the scopes associated with the specified profile. + + :param profile_id: The ID of the profile for which scopes will be updated. + :param scopes: List of scopes. Example below. + [ + { + 'type': 'EnvironmentGroup'|'Environment', + 'value':'ID' + }, + ] + :return: List of scopes and associated details. + """ + + return self.britive.post(f'{self.britive.base_url}/paps/{profile_id}/scopes', json=scopes) + + def add_single_environment_scope(self, profile_id: str, environment_id: str) -> None: + """ + Add a single environment to the scopes associated with the specified profile. + + This API call is useful when there are a large number of environments (500+) as the + `set_scopes()` call may time out. + + :param profile_id: The ID of the profile. + :param environment_id: The ID of the environment which will be added to the profile scopes. + :return: None + """ + + return self.britive.patch(f'{self.britive.base_url}/paps/{profile_id}/scopes/{environment_id}') + + def remove_single_environment_scope(self, profile_id: str, environment_id: str) -> None: + """ + Remove a single environment from the scopes associated with the specified profile. + + This API call is useful when there are a large number of environments (500+) as the + `set_scopes()` call may time out. + + :param profile_id: The ID of the profile. + :param environment_id: The ID of the environment which will be removed from the profile scopes. + :return: None + """ + + return self.britive.delete(f'{self.britive.base_url}/paps/{profile_id}/scopes/{environment_id}') + + def enable(self, application_id: str, profile_id: str) -> dict: + """ + Enables a profile. + + :param application_id: The ID of the application. + :param profile_id: The ID of the profile to enable. + :return: Details of the enabled profile. + """ + + return self.britive.post(f'{self.base_url}/{application_id}/paps/{profile_id}/enabled-statuses') + + def disable(self, application_id: str, profile_id: str) -> dict: + """ + Disables a profile. + + :param application_id: The ID of the application. + :param profile_id: The ID of the profile to disable. + :return: Details of the disabled profile. + """ + + return self.britive.post(f'{self.base_url}/{application_id}/paps/{profile_id}/disabled-statuses') + + def delete(self, application_id: str, profile_id: str) -> None: + """ + Deletes a profile. + + :param application_id: The ID of the application. + :param profile_id: The ID of the profile to delete. + :return: None + """ + + return self.britive.delete(f'{self.base_url}/{application_id}/paps/{profile_id}') diff --git a/src/britive/application_management/profiles/additional_settings.py b/src/britive/application_management/profiles/additional_settings.py new file mode 100644 index 0000000..ee07b90 --- /dev/null +++ b/src/britive/application_management/profiles/additional_settings.py @@ -0,0 +1,65 @@ +class AdditionalSettings: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/paps' + + def _build_settings_data(self, **kwargs) -> dict: + key_mapping = { + 'cred': 'useApplicationCredentialType', + 'con': 'consoleAccess', + 'prog': 'programmaticAccess', + 'proj': 'projectIdForServiceAccount', + } + return {key_mapping[k]: v for k, v in kwargs.items() if v is not None} + + def create( + self, + profile_id: str, + use_app_credential_type: bool = None, + console: bool = None, + programmatic: bool = None, + project_id: str = None, + ) -> None: + """ + Create or overwrite profile additional settings for GCP, GCP Standalone, and Azure applications. + + :param profile_id: The ID of the profile. + :param use_app_credential_type: Whether to inherit the credential type from the application. + :param console: Whether to enable console access for the profile. + :param programmatic: Whether to enable programmatic access for the profile. + :param project_id: The project ID for creating service accounts (GCP/GCP Standalone only). + :return: None. + """ + data = self._build_settings_data(cred=use_app_credential_type, con=console, prog=programmatic, proj=project_id) + return self.britive.post(f'{self.base_url}/{profile_id}/additional-settings', json=data) + + def update( + self, + profile_id: str, + use_app_credential_type: bool = None, + console: bool = None, + programmatic: bool = None, + project_id: str = None, + ) -> None: + """ + Update specific additional settings for a profile in GCP, GCP Standalone, and Azure applications. + + :param profile_id: The ID of the profile. + :param use_app_credential_type: Whether to inherit the credential type from the application. + :param console: Whether to enable console access for the profile. + :param programmatic: Whether to enable programmatic access for the profile. + :param project_id: The project ID for creating service accounts (GCP/GCP Standalone only). + :return: None. + """ + data = self._build_settings_data(cred=use_app_credential_type, con=console, prog=programmatic, proj=project_id) + return self.britive.patch(f'{self.base_url}/{profile_id}/additional-settings', json=data) + + def get(self, profile_id: str) -> dict: + """ + Retrieve additional settings for a profile in GCP, GCP Standalone, and Azure applications. + + :param profile_id: The ID of the profile. + :return: Details of added attribute. + """ + + return self.britive.get(f'{self.base_url}/{profile_id}/additional-settings') diff --git a/src/britive/application_management/profiles/permissions.py b/src/britive/application_management/profiles/permissions.py new file mode 100644 index 0000000..9360ee2 --- /dev/null +++ b/src/britive/application_management/profiles/permissions.py @@ -0,0 +1,186 @@ +class Permissions: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/paps' + self.constraints = PermissionConstraints(britive) + + def add(self, profile_id: str, permission_type: str, permission_name: str) -> dict: + """ + Add a permission to a profile. + + Call `list_available()` to see what permissions can be added. + + Note that for AWS and OCI permissions are not assigned to profiles as the permissions are tied into the + cloud provider directly (AssumeRole for AWS). + + :param profile_id: The ID of the profile. + :param permission_type: The type of permission. Valid values are `role`, `group`, and `policy`. + :param permission_name: The name of the permission. + :return: Details of the permission added. + """ + + data = {'op': 'add', 'permission': {'name': permission_name, 'type': permission_type}} + + return self.britive.post(f'{self.base_url}/{profile_id}/permissions', json=data) + + def list_assigned(self, profile_id: str, filter_expression: str = None) -> list: + """ + List the permissions assigned to the profile. + + :param profile_id: The ID of the profile. + :param filter_expression: Can filter based on `name`, `status`, `integrity check`. Valid operators are `eq` and + `co`. Example: name co "Dev Account" + :return: List of permissions assigned to the profile. + """ + + params = {'page': 0, 'size': 100} + + if filter_expression: + params['filter'] = filter_expression + + return self.britive.get(f'{self.base_url}/{profile_id}/permissions', params=params) + + def list_available(self, profile_id: str) -> list: + """ + List permissions available to be assigned to the profile. + + Note that for AWS and OCI permissions are not assigned to profiles as the permissions are tied into the + cloud provider directly (AssumeRole for AWS). + + :param profile_id: The ID of the profile. + :return: List of permissions that are available to be assigned to the profile. + """ + + params = {'page': 0, 'size': 100, 'query': 'available'} + return self.britive.get(f'{self.base_url}/{profile_id}/permissions', params=params) + + def remove(self, profile_id: str, permission_type: str, permission_name: str) -> dict: + """ + Remove a permission to a profile. + + :param profile_id: The ID of the profile. + :param permission_type: The type of permission. Valid values are `role`, `group`, and `policy`. + :param permission_name: The name of the permission. + :return: Details of the permission removed. + """ + + data = {'op': 'remove', 'permission': {'name': permission_name, 'type': permission_type}} + + return self.britive.post(f'{self.base_url}/{profile_id}/permissions', json=data) + + +class PermissionConstraints: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/paps' + + def list_supported_types(self, profile_id: str, permission_name: str, permission_type: str = 'role') -> list: + """ + Lists the supported constraint types. + + :param profile_id: The ID of the profile. + :param permission_name: The name of the permission for which to list supported constraints. + :param permission_type: The type of permission. Defaults to `role`. + :returns: List of supported constraint types. + """ + + url = f'{self.base_url}/{profile_id}/permissions/{permission_name}/{permission_type}/supported-constraint-types' + return self.britive.get(url) + + def get(self, profile_id: str, permission_name: str, constraint_type: str, permission_type: str = 'role') -> list: + """ + Gets the list of constraints. + + :param profile_id: The ID of the profile. + :param permission_name: The name of the permission for which to list supported constraints. + :param constraint_type: The type of constraint. + :param permission_type: The type of permission. Defaults to `role`. + :returns: List of constraints for the given constraint type. + """ + + url = ( + f'{self.base_url}/{profile_id}/permissions/{permission_name}/' + f'{permission_type}/constraints/{constraint_type}' + ) + return self.britive.get(url).get('result') + + def lint_condition( + self, profile_id: str, permission_name: str, expression: str, permission_type: str = 'role' + ) -> dict: + """ + Lint the provided condition expression. + + :param profile_id: The ID of the profile. + :param permission_name: The name of the permission for which to lint the condition expression. + :param expression: The condition expression to lint. + :param permission_type: The type of permission. Defaults to `role`. + :returns: Results of the lint operation. + """ + + url = f'{self.base_url}/{profile_id}/permissions/{permission_name}/{permission_type}/constraints/condition' + + params = {'operation': 'validate'} + + data = {'expression': expression} + + return self.britive.put(url, params=params, json=data) + + def add( + self, + profile_id: str, + permission_name: str, + constraint_type: str, + constraint: dict, + permission_type: str = 'role', + ) -> None: + """ + Adds the given constraint. + + :param profile_id: The ID of the profile. + :param permission_name: The name of the permission for which the constraint should be added. + :param constraint_type: The type of constraint. + :param constraint: The constraint to add. If `constraint_type == 'condition'` then this parameter should be a + dict with fields `title`, `description`, and `expression`. Otherwise, this parameter should be a dict with + field `name` or string value. + :param permission_type: The type of permission. Defaults to `role`. + :returns: None. + """ + + url = ( + f'{self.base_url}/{profile_id}/permissions/{permission_name}/' + f'{permission_type}/constraints/{constraint_type}' + ) + + params = {'operation': 'add'} + + return self.britive.put(url, params=params, json=constraint) + + def remove( + self, + profile_id: str, + permission_name: str, + constraint_type: str, + constraint: dict = None, + permission_type: str = 'role', + ) -> None: + """ + Removes the given constraint. + + :param profile_id: The ID of the profile. + :param permission_name: The name of the permission for which the constraint should be removed. + :param constraint_type: The type of constraint. + :param constraint: The constraint to remove. If `constraint_type == 'condition'` then omit this parameter or + set it to `None`. Otherwise, this parameter should be a dict with field `name` or string value. + :param permission_type: The type of permission. Defaults to `role`. + :returns: None. + """ + + url = ( + f'{self.base_url}/{profile_id}/permissions/{permission_name}/' + f'{permission_type}/constraints/{constraint_type}' + ) + params = {'operation': 'remove'} + if constraint is None: + constraint = {} + + return self.britive.put(url, params=params, json=constraint) diff --git a/src/britive/application_management/profiles/policies.py b/src/britive/application_management/profiles/policies.py new file mode 100644 index 0000000..8409519 --- /dev/null +++ b/src/britive/application_management/profiles/policies.py @@ -0,0 +1,191 @@ +import json +from typing import Union + + +class Policies: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/paps' + + def build( # noqa: PLR0913 + self, + name: str, + description: str = '', + draft: bool = False, + active: bool = True, + read_only: bool = False, + users: list = None, + tags: list = None, + service_identities: list = None, + ips: list = None, + date_schedule: dict = None, + days_schedule: dict = None, + approval_notification_medium: Union[str, list] = None, + time_to_approve: int = 5, + access_validity_time: int = 120, + approver_users: list = None, + approver_tags: list = None, + access_type: str = 'Allow', + identifier_type: str = 'name', + condition_as_dict: bool = False, + stepup_auth: bool = False, + always_prompt_stepup_auth: bool = False, + advanced_settings: dict = None, + ) -> dict: + """ + Build a policy document given the provided inputs. + + :param name: The name of the policy. + :param description: An optional description of the policy. + :param draft: Indicates if the policy is a draft. Defaults to `False`. + :param active: Indicates if the policy is active. Defaults to `True`. + :param read_only: Indicates if the policy is a read only. Defaults to `False`. + :param users: Optional list of user names or ids to which this policy applies. + :param tags: Optional list of tag names or ids to which this policy applies. + :param service_identities: Optional list of service identity names or ids to which this policy applies. + :param ips: Optional list of IP addresses for which this policy applies. Provide in CIDR notation + or dotted decimal format for individual (/32) IP addresses. + :param date_schedule: A dict in the format + { + 'fromDate': '2022-10-29 10:30:00', + 'toDate': '2022-11-05 18:30:00', + 'timezone': 'UTC' + } + Timezone formats can be found in the TZ Identifier column of the following page. + https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + :param days_schedule: A dict in the format + { + 'fromTime': '10:30:00', + 'toTime': '18:30:00', + 'timezone': 'UTC', + 'days': ['MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY','SUNDAY'] + } + Timezone formats can be found in the TZ Identifier column of the following page. + https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + :param approval_notification_medium: Optional notification medium name to which approval requests will be + delivered. Can also specify a list of notification medium names. Specifying this parameter indicates the + desire to enable approvals for this policy. + :param time_to_approve: Optional number of minutes to wait for an approval before denying the action. Defaults + to 5 minutes. + :param access_validity_time: Optional number of minutes the access is valid after approval. Defaults to 120 + minutes. + :param approver_users: Optional list of user names or ids who are to be considered approvers. + If `approval_notification_medium` is set then either `approver_users` or `approver_tags` is required. + :param approver_tags: Optional list of tag names who are considered approvers. + If `approval_notification_medium` is set then either `approver_users` or `approver_tags` is required. + :param access_type: The type of access this policy provides. Valid values are `Allow` and `Deny`. Defaults + to `Allow`. + :param identifier_type: Valid values are `id` or `name`. Defaults to `name`. Represents which type of + identifiers are being provided to the other parameters. Either all identifiers must be names or all + identifiers must be IDs. + :param condition_as_dict: Prior to version 2.22.0 the only acceptable format for the condition block of + a policy was as a stringifed json object. As of 2.22.0 the condition block can also be built as a raw + python dictionary. This parameter will default to `False` to support backwards compatibility. Setting to + `True` will result in the policy condition being returned/built as a python dictionary. + :param stepup_auth: Indicates if step-up authentication is required to checkout profile + :param always_prompt_stepup_auth: Indicates if previous successful verification should be remembered + :param advanced_settings: Optional Advanced Settings settings for this policy. + :return: A dict which can be provided as a profile policy to `create` and `update`. + """ + + policy = self.britive.system.policies.build( + name=name, + description=description, + draft=draft, + active=active, + read_only=read_only, + users=users, + tags=tags, + service_identities=service_identities, + ips=ips, + date_schedule=date_schedule, + days_schedule=days_schedule, + approval_notification_medium=approval_notification_medium, + time_to_approve=time_to_approve, + access_validity_time=access_validity_time, + approver_users=approver_users, + approver_tags=approver_tags, + access_type=access_type, + identifier_type=identifier_type, + condition_as_dict=condition_as_dict, + stepup_auth=stepup_auth, + always_prompt_stepup_auth=always_prompt_stepup_auth, + advanced_settings=advanced_settings, + ) + + # clean up the generic policy response and customize for profiles + policy.pop('permissions', None) + policy.pop('roles', None) + policy['consumer'] = 'papservice' + + return policy + + def list(self, profile_id: str) -> list: + """ + List all policies associated with the provided profile. + + :param profile_id: The ID of the profile. + :return: List of policies. + """ + + return self.britive.get(f'{self.base_url}/{profile_id}/policies') + + def get(self, profile_id: str, policy_id: str, condition_as_dict: bool = False) -> dict: + """ + Retrieve details about a specific policy which is associated with the provided profile. + + :param profile_id: The ID of the profile. + :param policy_id: The ID of the policy. + :param condition_as_dict: Prior to version 2.22.0 a policy condition block was always returned as stringified + json. As of 2.22.0 the SDK now supports returning the condition block of a policy as either stringified json + or a raw python dictionary. The Britive backend will also return the condition block in either format, + depending on a query parameter value. Setting this value to `True` will result in the condition block being + returned as a python dictionary. The default of `False` is to support backwards compatibility. + :return: Details of the policy. + """ + + params = {'conditionJson': condition_as_dict} + + policy = self.britive.get(f'{self.base_url}/{profile_id}/policies/{policy_id}', params=params) + + # it seems profile policy is not honoring conditionJson parameter so doing some extra work here + # to get things into the correct format. if in the future that changes we can perhaps remove + # the below logic. + if 'condition' in policy and condition_as_dict and isinstance(policy['condition'], str): + policy['condition'] = json.loads(policy['condition']) + + return policy + + def create(self, profile_id: str, policy: dict) -> dict: + """ + Create a policy associated with the provided profile. + + :param profile_id: The ID of the profile. + :param policy: The policy contents to create. + :return: Details of the newly created policy. + """ + + return self.britive.post(f'{self.base_url}/{profile_id}/policies', json=policy) + + def update(self, profile_id: str, policy_id: str, policy: dict) -> dict: + """ + Update the contents of the provided policy associated with the provided profile. + + :param profile_id: The ID of the profile. + :param policy_id: The ID of the policy. + :param policy: The policy to update. + :return: Details of the updated policy. + """ + + return self.britive.patch(f'{self.base_url}/{profile_id}/policies/{policy_id}', json=policy) + + def delete(self, profile_id: str, policy_id: str) -> None: + """ + Delete the provided policy associated with the provided profile. + + :param profile_id: The ID of the profile. + :param policy_id: The ID of the policy. + :return: None. + """ + + return self.britive.delete(f'{self.base_url}/{profile_id}/policies/{policy_id}') diff --git a/src/britive/application_management/profiles/session_attributes.py b/src/britive/application_management/profiles/session_attributes.py new file mode 100644 index 0000000..543c953 --- /dev/null +++ b/src/britive/application_management/profiles/session_attributes.py @@ -0,0 +1,127 @@ +class SessionAttributes: + def __init__(self, britive) -> None: + self.britive = britive + self.base_url = f'{self.britive.base_url}/paps' + + def add_static(self, profile_id: str, tag_name: str, tag_value: str, transitive: bool = False) -> dict: + """ + AWS ONLY - Add a static session attribute to the profile. + + :param profile_id: The ID of the profile. + :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. + :param tag_value: The value of the session tag to include in the AssumeRoleWithSAML call. + :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you + may or may not want this. + :return: Details of added attribute. + """ + + data = { + 'sessionAttributeType': 'Static', + 'transitive': transitive, + 'attributeSchemaId': None, + 'mappingName': tag_name, + 'attributeValue': tag_value, + } + + return self.britive.post(f'{self.base_url}/{profile_id}/session-attributes', json=data) + + def add_dynamic(self, profile_id: str, identity_attribute_id: str, tag_name: str, transitive: bool = False) -> dict: + """ + AWS ONLY - Add a dynamic session attribute to the profile. + + The value will be sourced from the identity attribute specified. + + :param profile_id: The ID of the profile. + :param identity_attribute_id: The ID of the identity attribute. + Call `britive.identity_management.identity_attributes.list()` for which attributes can be provided. + :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. The value will be + dynamically determined based on the value of the specified identity attribute. + :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you + may or may not want this. + :return: Details of added attribute. + """ + + data = { + 'sessionAttributeType': 'Identity', + 'transitive': transitive, + 'attributeSchemaId': identity_attribute_id, + 'mappingName': tag_name, + 'attributeValue': None, + } + + return self.britive.post(f'{self.base_url}/{profile_id}/session-attributes', json=data) + + def update_static( + self, profile_id: str, attribute_id, tag_name: str, tag_value: str, transitive: bool = False + ) -> None: + """ + AWS ONLY - Update the static session attribute to the profile. + + :param profile_id: The ID of the profile. + :param attribute_id: The ID of the session attribute to update. + :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. + :param tag_value: THe value of the session tag to include in the AssumeRoleWithSAML call. + :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you + may or may not want this. + :return: None. + """ + + data = { + 'sessionAttributeType': 'Static', + 'transitive': transitive, + 'attributeSchemaId': None, + 'mappingName': tag_name, + 'attributeValue': tag_value, + 'id': attribute_id, + } + + return self.britive.put(f'{self.base_url}/{profile_id}/session-attributes', json=data) + + def update_dynamic( + self, profile_id: str, attribute_id: str, identity_attribute_id: str, tag_name: str, transitive: bool = False + ) -> dict: + """ + AWS ONLY - Update the dynamic session attribute to the profile. + + :param profile_id: The ID of the profile. + :param attribute_id: The ID of the session attribute to update. + :param identity_attribute_id: The ID of the identity attribute. + Call `britive.identity_management.identity_attributes.list()` for which attributes can be provided. + :param tag_name: The name of the session tag to include in the AssumeRoleWithSAML call. The value will be + dynamically determined based on the value of the specified identity attribute. + :param transitive: Set to True to mark the session tag as transitive. Review AWS documentation on why you + may or may not want this. + :return: Details of added attribute. + """ + + data = { + 'sessionAttributeType': 'Identity', + 'transitive': transitive, + 'attributeSchemaId': identity_attribute_id, + 'mappingName': tag_name, + 'attributeValue': None, + 'id': attribute_id, + } + + return self.britive.put(f'{self.base_url}/{profile_id}/session-attributes', json=data) + + def list(self, profile_id: str) -> list: + """ + Return a list of session attributes associated with the profile. + + :param profile_id: The ID of the profile. + :return: List of session attributes associated with the profile. + """ + + return self.britive.get(f'{self.base_url}/{profile_id}/session-attributes') + + def remove(self, profile_id: str, attribute_id: str) -> None: + """ + Remove an attribute from the profile. + + :param profile_id: The ID of the profile. + :param attribute_id: The ID of the session attribute. + :return: None. + """ + + return self.britive.delete(f'{self.base_url}/{profile_id}/session-attributes/{attribute_id}') From a6e463fafd3a2bcd886be48b0f1d7653e03b731e Mon Sep 17 00:00:00 2001 From: theborch Date: Fri, 30 May 2025 13:11:32 -0500 Subject: [PATCH 7/7] v4.2.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ src/britive/__init__.py | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 916b70b..b2b66ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Change Log (v2.8.1+) +## v4.2.0 [2025-05-30] + +__What's New:__ + +* Added `advanced_settings` functionality to: + * `application_management` + * `application_management.profiles` + * `access_broker.profiles` +* Added `global_settings.itsm` functionality. + +__Enhancements:__ + +* Added missing params for `secrets_manager.[secrets|vaults]` and `file` updates. + +__Bug Fixes:__ + +* None + +__Dependencies:__ + +* None + +__Other:__ + +* Updated tests to use uniform naming convention. +* Refactored `application_management.profiles` to break out classes for added clarity. + ## v4.1.3 [2025-03-07] __What's New:__ diff --git a/src/britive/__init__.py b/src/britive/__init__.py index 1e8dc38..ea5d65f 100644 --- a/src/britive/__init__.py +++ b/src/britive/__init__.py @@ -1 +1 @@ -__version__ = '4.1.3' +__version__ = '4.2.0'