from anydi import singleton import dmp.google.models as models from dmp.adapters.aws.kms import BaseKMS from dmp.adapters.google import GoogleClient, GoogleClientError, User from dmp.adapters.google.models import ( AccessToken, Audience, RefreshToken, ) from dmp.config import Settings from dmp.core.types import EncryptedToken, PlainToken from dmp.google.dtos import ( GoogleUser, GoogleUserConnectionAdAccount, GoogleUserConnectionResponse, ) from dmp.google.enums import GoogleUserConnectionStatus from dmp.google.exceptions import GoogleUserConnectionScopesError from dmp.google.repositories import ( GoogleAdAccountRepository, GoogleUserConnectionRepository, ) @singleton class GoogleUserConnectionService: def __init__( self, kms: BaseKMS, google_client: GoogleClient, user_connection_repository: GoogleUserConnectionRepository, ad_account_repository: GoogleAdAccountRepository, settings: Settings, ) -> None: self.kms = kms self.google_client = google_client self.user_connection_repository = user_connection_repository self.ad_account_repository = ad_account_repository self.settings = settings def get_user_connection( self, identity_id: str, user_id: str, ) -> models.GoogleUserConnection | None: return self.user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user_id ) def save_user_connection( self, user_connection: models.GoogleUserConnection, / ) -> None: self.user_connection_repository.add(user_connection) def save_user_ad_accounts(self, ad_accounts: list[models.GoogleAdAccount]) -> None: self.ad_account_repository.add(ad_accounts) def get_refresh_token( self, auth_code: PlainToken, redirect_uri: str, code_verifier: str ) -> RefreshToken: token = self.google_client.get_refresh_token( auth_code=auth_code, redirect_uri=redirect_uri, code_verifier=code_verifier, ) if not token.scopes_sufficient(self.settings.google_user_required_scopes): raise GoogleUserConnectionScopesError return token def get_connection_access_token( self, connection: models.GoogleUserConnection, ) -> AccessToken: refresh_token = self.decrypt_token(connection.token) try: token = self.get_access_token(refresh_token=refresh_token) return token except GoogleClientError as e: self.invalidate_user_connection(connection) raise e def create_or_update_user_connection( self, user_connection: models.GoogleUserConnection, ) -> models.GoogleUserConnection: existing_user_connection = ( self.user_connection_repository.get_by_identity_id_and_user_id( identity_id=user_connection.identity_id, user_id=user_connection.user_id ) ) if existing_user_connection: existing_user_connection.token = user_connection.token existing_user_connection.user_name = user_connection.user_name existing_user_connection.is_valid = user_connection.is_valid user_connection = existing_user_connection return user_connection def get_user(self, access_token: PlainToken) -> User: return self.google_client.get_user(access_token=access_token) def get_access_token(self, refresh_token: PlainToken) -> AccessToken: return self.google_client.get_access_token(refresh_token=refresh_token) def encrypt_token(self, plain_token: PlainToken, /) -> EncryptedToken: return self.kms.encrypt(plain_token, context={"field": "token"}) def decrypt_token(self, encrypted_token: EncryptedToken, /) -> PlainToken: return self.kms.decrypt(encrypted_token, context={"field": "token"}) def update_user_data( self, user_connection: models.GoogleUserConnection, user: User ) -> None: user_connection.user_name = user.name or "" def _make_connection_response( self, *, user: GoogleUser | None, status: GoogleUserConnectionStatus, ad_accounts: list[GoogleUserConnectionAdAccount], ) -> GoogleUserConnectionResponse: return GoogleUserConnectionResponse( user=user, status=status, required_scopes=self.settings.google_user_required_scopes, ad_accounts=ad_accounts, ) def make_connected_response( self, user_connection: models.GoogleUserConnection, /, *, user: GoogleUser ) -> GoogleUserConnectionResponse: return self._make_connection_response( user=user, status=GoogleUserConnectionStatus.CONNECTED, ad_accounts=[ GoogleUserConnectionAdAccount.from_model( connection_ad_account.ad_account ) for connection_ad_account in user_connection.connection_ad_accounts ], ) def make_error_response( self, status: GoogleUserConnectionStatus ) -> GoogleUserConnectionResponse: return self._make_connection_response( user=None, status=status, ad_accounts=[], ) def get_active_audience_sharing_connection( self, *, audience_id: str, ad_account_id: str, identity_id: str, ) -> models.GoogleUserConnection | None: user_connections = ( self.user_connection_repository.find_active_by_audience_and_ad_account_id( audience_id=audience_id, ad_account_id=ad_account_id, identity_id=identity_id, ) ) for user_connection in user_connections: try: token = self.get_access_token( refresh_token=self.decrypt_token(user_connection.token) ) self.google_client.get_user(access_token=token.access_token) except GoogleClientError: self.invalidate_user_connection(user_connection) continue return user_connection return None def create_custom_audience( self, access_token: PlainToken, ad_account: models.GoogleAdAccount, name: str, description: str | None, parent_ad_account_id: str | None, ) -> Audience: return self.google_client.create_user_list( access_token=access_token, ad_account_id=ad_account.external_id, name=name, description=description, login_customer_id=parent_ad_account_id, ) def invalidate_user_connection( self, user_connection: models.GoogleUserConnection, / ) -> None: user_connection.is_valid = False self.save_user_connection(user_connection)