import sshtunnel_requests import json import paramiko from pprint import pprint from app.dtos import User from app.utils import alert_message_and_exit, print_message from app.config import PDP_SSH_KEYPATH, PDP_SSH_HOSTNAME, PDP_SSH_USERNAME, PROD_BEARER_TOKEN, PDP_SSH_KEY_PASSWORD PDP_ROLE_PREFIX = "audience_development_" AVAILABLE_PDP_ROLES = [ "audience_development_admin", "audience_development_analyst", "audience_development_audience_manager", "audience_development_client", ] def get_ssh_client(): ssh = paramiko.SSHClient() k = paramiko.Ed25519Key.from_private_key_file(PDP_SSH_KEYPATH, password=PDP_SSH_KEY_PASSWORD) ssh.load_system_host_keys() ssh.connect(hostname=PDP_SSH_HOSTNAME, username=PDP_SSH_USERNAME, pkey=k) return ssh def get_pdp_permissions(identity_id: str, ssh_client=None) -> dict | None: if ssh_client: ssh = ssh_client else: ssh = get_ssh_client() request = """ curl -X GET 'https://prod-ows-pdp.theorchard.io/identity/%(identity_id)s/roles/'\ -H 'Authorization: Bearer %(token)s'\ -H 'accept: application/json' """ % { "token": PROD_BEARER_TOKEN, "identity_id": identity_id, } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") output = json.loads(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) tenants = output.get("tenants") if tenants: clean_tenants_data = {} for tenant_id, tenant_data in tenants.items(): clean_tenants_data[tenant_id] = [role.get("role") for role in tenant_data.get("roles")] return clean_tenants_data return None def get_pdp_permissions_for_identities(identities: list[str], token) -> dict | None: """ :param identities: :param token: :return: {"identity_uuid": {"tenant_uuid": ["role1", "role2", "role3"]}}]} } """ ssh = get_ssh_client() users_permissions = {} for identity_id in identities: request = """ curl -X GET 'https://prod-ows-pdp.theorchard.io/identity/%(identity_id)s/roles/'\ -H 'Authorization: Bearer %(token)s'\ -H 'accept: application/json' """ % { "token": PROD_BEARER_TOKEN, "identity_id": identity_id, } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") output = json.loads(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) tenants = output.get("tenants") if tenants: clean_tenants_data = {} for tenant_id, tenant_data in tenants.items(): clean_tenants_data[tenant_id] = [role.get("role") for role in tenant_data.get("roles")] users_permissions[identity_id] = clean_tenants_data return users_permissions def update_permissions(users: list[User], tenants: list[str]) -> None: """ :param users: list of Users :param tenant: tenant/vendor uuid :return: """ def convert_role(role: str): if 'admin' in role: return 'audience_development_admin' elif 'analyst' in role: return 'audience_development_analyst' elif 'audience_manager' in role: return 'audience_development_audience_manager' else: alert_message_and_exit("Unknown role: {}".format(role)) ssh = get_ssh_client() for tenant in tenants: print_message(f"Updating permissions for tenant {tenant}") for user in users: user_permissions = get_pdp_permissions(user.identity, ssh_client=ssh) old_role = "" if user_permissions: roles = user_permissions.get(tenant) fansifter_roles = [role for role in (roles or []) if role in AVAILABLE_PDP_ROLES] if roles and len(fansifter_roles) > 1: alert_message_and_exit(f"User {user.email} has more than one role: {', '.join(roles)} please resolve it. And run script again.") if roles: old_role = fansifter_roles[0] else: old_role = None new_role = convert_role(user.role) old_role_data = "" if old_role and old_role != new_role: old_role_data = f'{{"role": "{old_role}"}}' elif old_role and old_role == new_role: continue request = """ curl --location --request PUT 'https://prod-ows-pdp.theorchard.io/identity/%(identity_id)s/tenant/%(tenant_uuid)s/attach-and-detach/roles/' \ --header 'Authorization: Bearer %(token)s' \ --header 'Content-Type: application/json' \ --data '{ "tenant_uuid": "%(tenant_uuid)s", "tenant_type": "account", "roles_to_attach": [{"role": "%(new_role)s"}], "roles_to_detach": [%(old_role)s] }' """ % { "token": PROD_BEARER_TOKEN, "identity_id": user.identity, "old_role": old_role_data, "new_role": new_role, "tenant_uuid": tenant } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") output = json.loads(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) def delete_permissions(users: list[User], tenants: list[str]): ssh = get_ssh_client() for tenant in tenants: print_message(f"Delete permissions for tenant {tenant}") for user in users: user_permissions = get_pdp_permissions(user.identity, ssh_client=ssh) if user_permissions: roles = user_permissions.get(tenant) if not roles: print( f"User {user.email} has no role skipping") continue # Filter to only Fansifter-related roles that should be deleted deletable_roles = [role for role in roles if role.startswith('audience_development_') or role.startswith('fansifter_')] if not deletable_roles: print(f"User {user.email} has no Fansifter roles to delete, skipping") continue # Build roles_to_detach array with all deletable roles using same format as migrate_to_v2_permissions roles_to_detach = [] for role in deletable_roles: roles_to_detach.append('{"role": "%(role)s"}' % {'role': role}) print(f"Deleting roles for {user.email}: {deletable_roles}") request = """ curl --location --request PUT 'https://prod-ows-pdp.theorchard.io/identity/%(identity_id)s/tenant/%(tenant_uuid)s/attach-and-detach/roles/' \ --header 'Authorization: Bearer %(token)s' \ --header 'Content-Type: application/json' \ --data '{ "tenant_uuid": "%(tenant_uuid)s", "tenant_type": "account", "roles_to_attach": [], "roles_to_detach": [%(roles_to_detach)s] }' """ % { "token": PROD_BEARER_TOKEN, "identity_id": user.identity, "roles_to_detach": ", ".join(roles_to_detach), "tenant_uuid": tenant } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") output = json.loads(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) def check_resource_type_actions(): ssh = get_ssh_client() request = """ curl --location --request POST 'https://prod-ows-pdp.theorchard.io/identity/self/check/resource-type-actions' \ --header 'Authorization: Bearer %(token)s' \ --header 'Content-Type: application/json' \ --data '{ "resource_type_actions": [ { "resource_type": "audience", "action": "access" } ] }' """ % { "token": PROD_BEARER_TOKEN, } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") output = json.loads(output) pprint(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) def check_resource_test(): ssh = get_ssh_client() request = """ curl --location --request POST 'https://prod-ows-pdp.theorchard.io/identity/self/check/resources/' \ --header 'Authorization: Bearer %(token)s' \ --header 'Content-Type: application/json' \ --data '{ "resources": [ { "resource": { "resource_id": "790925", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "05a509f7-97be-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "791022", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "1af9aef5-a149-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "77877", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "1e4edee6-602f-4898-9359-8184afaea56c", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34536", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "1ee1ebda-42f2-4183-84d2-25df213a7db6", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790797", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "3e1ec365-92f9-11ef-aa92-120c1dfc8269", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "779011", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "40696b88-ed06-11ee-a7c1-12b0989b795f", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "791221", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "434c836b-b655-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34514", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "46fec901-3c02-4421-9fe1-6b02eae9fdbc", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790943", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "4adab9d0-9af7-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "22240", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "4af19713-cd63-4424-8aaf-ac8501ea6d09", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34578", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "4b3532fb-dcf6-49b6-9540-59f523047128", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790999", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "4c22f1a3-9dfe-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "7123", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "573d0372-7f2f-48a6-8deb-c9a6558f9549", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34590", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "57f213e2-7f04-45ef-8b31-71ce1c85e9e3", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790998", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "6050dd0b-9dfd-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790970", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "619e0fa4-9c97-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "82477", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "660bdaf5-aa73-4093-8a87-2cc18277bac7", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790795", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "698de2b2-92f8-11ef-aa92-120c1dfc8269", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "22221", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "70206e8c-88a5-4fca-9952-73fe7fea058f", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34527", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "7939f571-6e71-4a38-9974-6187282db19c", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34496", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "795a64c1-d67d-4ace-b8ab-e46c8f89149b", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "791836", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "800f84a6-e801-11ef-8476-0ef8c77b7565", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "83638", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "84d2c116-e666-4797-b217-67099bea30ac", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "780555", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "94d541e7-21c6-11ef-a3a7-12b0989b795f", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34562", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "9556f5c1-825e-49f0-9b98-0f788bd99f77", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34588", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "97289bd3-47d3-4148-a8ed-a525c91b0d47", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790924", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "9b1e5080-97bd-11ef-b6a8-0affe2ca6553", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34608", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "9d3538c6-5256-4f8e-8699-74f7b4bcbae3", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790793", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "b2ab4d10-92e9-11ef-aa92-120c1dfc8269", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34519", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "b6a7e66e-f3a7-4559-9725-db9f0badd52f", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34548", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "b819ead6-0ddc-40e2-8743-5abb4fb8511e", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34603", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "c7269147-34be-4144-9511-5a91e05a97a7", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "790796", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "d2efb670-92f8-11ef-aa92-120c1dfc8269", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "35023", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "dd48bd5e-d1ab-4ff5-8c3e-3ecd591dac35", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34563", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "e4c3b07c-bfcb-4d46-8731-60261d5b5891", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "34523", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "e7ac4490-73cb-4f47-a4b7-474c41bc1799", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "778093", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "ebcd649b-d02c-11ee-a7c1-12b0989b795f", "tenant_type": "account" } } }, "action": "access" }, { "resource": { "resource_id": "23581", "resource_type": "audience", "attributes": { "tenant": { "tenant_uuid": "f81f60c5-0b52-4481-8cce-c2b5c1a5502c", "tenant_type": "account" } } }, "action": "access" } ]}' """ % { "token": PROD_BEARER_TOKEN, } stdin, stdout, stderr = ssh.exec_command(request) output = stdout.read().decode('ascii').strip("\n") print(output) output = json.loads(output) pprint(output) if output.get("code") == "invalid_token": raise Exception("Invalid token - {}".format(output.get("message"))) # TODO: try this approach https://pypi.org/project/sshtunnel-requests/ # # def get_permissions_v2(identity_id, token): # requests = sshtunnel_requests.from_url( # f'ssh://{PDP_SSH_USERNAME}@{PDP_SSH_HOSTNAME}:8080', PDP_SSH_KEYPATH) # # resp = requests.get( # f'https://prod-ows-pdp.theorchard.io/identity/{identity_id}/roles/', # headers={'Authorization': 'Bearer {token}'}, # ) # print(resp.status_code) # # 200 # print(resp.json())