From 72b3e5ff5d68d9f70257f9556068cc1e5de23e1c Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Tue, 26 May 2020 16:58:38 -0300 Subject: Remove cfg.domains Directly connect the 'account' API objects to their mailbox users through the config language instead of trying to fix it up after the fact with a dictionary. Signed-off-by: Jason Gunthorpe --- cloud_mdir_sync/config.py | 14 ++++++++----- cloud_mdir_sync/gmail.py | 23 ++++++++------------ cloud_mdir_sync/main.py | 10 ++++----- cloud_mdir_sync/office365.py | 50 ++++++++++++++++++-------------------------- 4 files changed, 42 insertions(+), 55 deletions(-) diff --git a/cloud_mdir_sync/config.py b/cloud_mdir_sync/config.py index be64f57..88a16b6 100644 --- a/cloud_mdir_sync/config.py +++ b/cloud_mdir_sync/config.py @@ -16,7 +16,6 @@ logger: logging.Logger class Config(object): """Program configuration and general global state""" message_db_dir = "~/mail/.cms/" - domains: Dict[str, Any] = {} trace_file: Any = None web_app: "oauth.WebServer" logger: logging.Logger @@ -42,6 +41,7 @@ class Config(object): self._create_logger() self.cloud_mboxes = [] self.local_mboxes = [] + self.async_tasks = [] self.message_db_dir = os.path.expanduser(self.message_db_dir) self.direct_message = self._direct_message @@ -83,27 +83,31 @@ class Config(object): then the browser will prompt for the user and the choice will be cached. To lock the account to a single tenant specify the Azure Directory name, ie 'contoso.onmicrosoft.com', or the GUID.""" - return (user,tenant) + from .office365 import GraphAPI + self.async_tasks.append(GraphAPI(self, user, tenant)) + return self.async_tasks[-1] def Office365(self, mailbox, account): """Create a cloud mailbox for Office365. Mailbox is the name of O365 mailbox to use, account should be the result of Office365_Account""" from .office365 import O365Mailbox self.cloud_mboxes.append( - O365Mailbox(self, mailbox, user=account[0], tenant=account[1])) + O365Mailbox(self, mailbox, account)) return self.cloud_mboxes[-1] def GMail_Account(self, user): """Define a GMail account credential. The user must be specified as a fully qualified Google Account email address. This supports both consumer GMail accounts, and accounts linked to a G-Suite account.""" - return (user,) + from .gmail import GmailAPI + self.async_tasks.append(GmailAPI(self, user)) + return self.async_tasks[-1] def GMail(self, label, account): """Create a cloud mailbox for Office365. Mailbox is the name of O365 mailbox to use, account should be the result of Office365_Account""" from .gmail import GMailMailbox - self.cloud_mboxes.append(GMailMailbox(self, label, user=account[0])) + self.cloud_mboxes.append(GMailMailbox(self, label, account)) return self.cloud_mboxes[-1] def MailDir(self, directory): diff --git a/cloud_mdir_sync/gmail.py b/cloud_mdir_sync/gmail.py index 8b03e66..b22291f 100644 --- a/cloud_mdir_sync/gmail.py +++ b/cloud_mdir_sync/gmail.py @@ -118,17 +118,20 @@ class GmailAPI(object): authenticator = None headers: Optional[Dict[str, str]] = None - def __init__(self, cfg: config.Config, domain_id: str, user: str): - self.domain_id = domain_id + def __init__(self, cfg: config.Config, user: str): + self.domain_id = f"gmail-{user}" self.cfg = cfg self.user = user + async def go(self): + cfg = self.cfg + connector = aiohttp.connector.TCPConnector(limit=20, limit_per_host=5) self.session = aiohttp.ClientSession(connector=connector, raise_for_status=False) self.redirect_url = cfg.web_app.url + "oauth2/gmail" - self.api_token = cfg.msgdb.get_authenticator(domain_id) + self.api_token = cfg.msgdb.get_authenticator(self.domain_id) self.oauth = requests_oauthlib.OAuth2Session( client_id=self.client_id, client=NativePublicApplicationClient(self.client_id), @@ -350,23 +353,15 @@ class GMailMailbox(mailbox.Mailbox): history_delta = None delete_action = "archive" # or delete - def __init__(self, cfg: config.Config, label: str, user: str): + def __init__(self, cfg: config.Config, label: str, gmail: GmailAPI): super().__init__(cfg) self.label_name = label - self.user = user + self.gmail = gmail self.gmail_messages = {} async def setup_mbox(self): """Setup access to the authenticated API domain for this endpoint""" - cfg = self.cfg - did = f"gmail-{self.user}" - self.name = f"{self.user}:{self.label_name}" - gmail = cfg.domains.get(did) - if gmail is None: - self.gmail = GmailAPI(cfg, did, self.user) - cfg.domains[did] = self.gmail - else: - self.gmail = gmail + self.name = f"{self.gmail.user}:{self.label_name}" # Verify the label exists jmsg = await self.gmail.get_json("v1", f"/users/me/labels") diff --git a/cloud_mdir_sync/main.py b/cloud_mdir_sync/main.py index d085dda..1c86b4a 100644 --- a/cloud_mdir_sync/main.py +++ b/cloud_mdir_sync/main.py @@ -58,9 +58,9 @@ async def update_cloud_from_local(cfg: config.Config, async def synchronize_mail(cfg: config.Config): """Main synchronizing loop""" cfg.web_app = oauth.WebServer() + cfg.async_tasks.append(cfg.web_app) try: - await cfg.web_app.go() - + await asyncio_complete(*(I.go() for I in cfg.async_tasks)) await asyncio_complete(*(mbox.setup_mbox() for mbox in cfg.all_mboxes())) @@ -94,10 +94,8 @@ async def synchronize_mail(cfg: config.Config): cfg.msgdb.cleanup_msgs(msgs) cfg.logger.debug("Changed event, looping") finally: - await asyncio_complete(*(domain.close() - for domain in cfg.domains.values())) - cfg.domains = {} - await cfg.web_app.close() + for I in cfg.async_tasks: + await I.close() def main(): diff --git a/cloud_mdir_sync/office365.py b/cloud_mdir_sync/office365.py index 3f9ddc7..09d8492 100644 --- a/cloud_mdir_sync/office365.py +++ b/cloud_mdir_sync/office365.py @@ -78,37 +78,37 @@ class GraphAPI(object): owa_token = None authenticator = None - def __init__(self, cfg, domain_id, user, tenant): - import msal - self.msl_cache = msal.SerializableTokenCache() - auth = cfg.msgdb.get_authenticator(domain_id) - if auth is not None: - self.msl_cache.deserialize(auth) - - self.domain_id = domain_id + def __init__(self, cfg: config.Config, user: str, tenant: str): + self.domain_id = f"o365-{user}-{tenant}" self.cfg = cfg self.user = user - self.web_app = cfg.web_app + self.tenant = tenant if self.user is not None: self.name = f"{self.user}//{tenant}" else: self.name = f"//{tenant}" - connector = aiohttp.connector.TCPConnector(limit=20, limit_per_host=5) - self.session = aiohttp.ClientSession(connector=connector, - raise_for_status=False) - self.headers = {} - self.owa_headers = {} - # Use the new format much more immutable ids, this will work better # with our caching scheme. See # https://docs.microsoft.com/en-us/graph/outlook-immutable-id - self.headers["Prefer"] = 'IdType="ImmutableId"' + self.headers= {"Prefer": 'IdType="ImmutableId"'} + self.owa_headers = {} + + async def go(self): + import msal + self.msl_cache = msal.SerializableTokenCache() + auth = self.cfg.msgdb.get_authenticator(self.domain_id) + if auth is not None: + self.msl_cache.deserialize(auth) + + connector = aiohttp.connector.TCPConnector(limit=20, limit_per_host=5) + self.session = aiohttp.ClientSession(connector=connector, + raise_for_status=False) self.msal = msal.PublicClientApplication( client_id="122f4826-adf9-465d-8e84-e9d00bc9f234", - authority=f"https://login.microsoftonline.com/{tenant}", + authority=f"https://login.microsoftonline.com/{self.tenant}", token_cache=self.msl_cache) def _cached_authenticate(self): @@ -150,7 +150,7 @@ class GraphAPI(object): self.graph_token = None self.owa_token = None - redirect_url = self.web_app.url + "oauth2/msal" + redirect_url = self.cfg.web_app.url + "oauth2/msal" state = hex(id(self)) + secrets.token_urlsafe(8) url = self.msal.get_authorization_request_url( scopes=self.graph_scopes + self.owa_scopes, @@ -369,25 +369,15 @@ class O365Mailbox(mailbox.Mailbox): def __init__(self, cfg: config.Config, mailbox: str, - user: Optional[str] = None, - tenant="common"): + graph: GraphAPI): super().__init__(cfg) self.mailbox = mailbox - self.tenant = tenant - self.user = user + self.graph = graph async def setup_mbox(self): """Setup access to the authenticated API domain for this endpoint""" cfg = self.cfg self.loop = cfg.loop - did = f"o365-{self.user}-{self.tenant}" - graph = cfg.domains.get(did) - if graph is None: - self.graph = GraphAPI(cfg, did, self.user, self.tenant) - cfg.domains[did] = self.graph - else: - self.graph = graph - self.name = f"{self.graph.name}:{self.mailbox}" json = await self.graph.get_json( -- cgit v1.2.3