From df29999e62225b7e93104c020c49763296172f3d Mon Sep 17 00:00:00 2001 From: Evilham Date: Mon, 1 Aug 2022 14:32:51 +0200 Subject: [PATCH] [sso-admin] Generate script for NC mail accounts This must be executed from cron on dd-apps-nextcloud-app. --- dd-apps/docker/nextcloud/nextcloud.yml | 1 + dd-sso/admin/src/admin/lib/admin.py | 43 +++++++++++++++++++---- dd-sso/admin/src/admin/views/MailViews.py | 8 ++--- dd-sso/docker-compose-parts/admin.yml | 2 ++ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/dd-apps/docker/nextcloud/nextcloud.yml b/dd-apps/docker/nextcloud/nextcloud.yml index 5c18f57..690f360 100644 --- a/dd-apps/docker/nextcloud/nextcloud.yml +++ b/dd-apps/docker/nextcloud/nextcloud.yml @@ -33,6 +33,7 @@ services: - /etc/localtime:/etc/localtime:ro - ${SRC_FOLDER}/nextcloud:/var/www/html - ${DATA_FOLDER}/nextcloud:/var/www/html/data + - ${DATA_FOLDER}/nc-mail-queue:/nc-mail-queue:rw environment: - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} diff --git a/dd-sso/admin/src/admin/lib/admin.py b/dd-sso/admin/src/admin/lib/admin.py index 0d8be94..5c1c24d 100644 --- a/dd-sso/admin/src/admin/lib/admin.py +++ b/dd-sso/admin/src/admin/lib/admin.py @@ -22,6 +22,8 @@ import json import logging as log import os import traceback +from datetime import datetime +from pathlib import Path from pprint import pprint from time import sleep @@ -60,7 +62,7 @@ from .helpers import ( rand_password, ) -from typing import TYPE_CHECKING, cast, Any, Dict, Iterable, List, Optional +from typing import TYPE_CHECKING, cast, Any, Dict, Iterable, List, Optional, Tuple if TYPE_CHECKING: from admin.flaskapp import AdminFlaskApp from admin.lib.callbacks import ThirdPartyCallbacks @@ -130,12 +132,41 @@ class Admin: res = res and tp.delete_user(user_id) return res - def nextcloud_mail_set(self, users : List[DDUser], extra_data : Dict) -> Dict: - # TODO: implement - return {} + def _nextcloud_mail_set_cmd(self, user : DDUser, kw : Dict) -> Tuple[str, str]: + account_name = 'DD' # Treating this as a constant + update_cmd = f"""mail:account:update \ + --imap-host '{ kw['inbound_host'] }' --imap-port '{ kw['inbound_port'] }' --imap-ssl-mode '{ kw['inbound_ssl_mode'] }' \\ + --imap-user '{ user['email'] }' --imap-password '{ user['password'] }' \\ + --smtp-host '{ kw['outbound_host'] }' --smtp-port'{ kw['outbound_port'] }' --smtp-ssl-mode '{ kw['outbound_ssl_mode'] }' \\ + --smtp-user '{ user['email'] }' --smtp-password '{ user['password'] }' \\ + -- '{ user['user_id'] }' '{ user['email']}'""" + create_cmd = f"""mail:account:create '{ user['user_id'] }' '{ account_name }' '{ user['email'] }' \\ + '{ kw['inbound_host'] }' '{ kw['inbound_port'] }' '{ kw['inbound_ssl_mode'] }' \\ + '{ user['email'] }' '{ user['password'] }' \\ + '{ kw['outbound_host'] }' '{ kw['outbound_port'] }' '{ kw['outbound_ssl_mode'] }' \\ + '{ user['email'] }' '{ user['password'] }'""" + return (update_cmd, create_cmd) - def nextcloud_mail_delete(self, users : List[DDUser], extra_data : Dict) -> Dict: - # TODO: implement + def _nextcloud_mail_set_sh(self, users : List[DDUser], extra_data : Dict) -> str: + cmds = '\n'.join((f"./occ {u} || ./occ {c}" for u, c in (self._nextcloud_mail_set_cmd(u, extra_data) for u in users))) + return f"""#!/bin/sh -eu +{cmds} +""" + + def nextcloud_mail_set(self, users : List[DDUser], extra_data : Dict) -> Dict: + # TODO: this could (and should) be nicer. + # Ideally we'd use the database as a queue instead of creating the + # shell scripts here. + d = Path(os.environ.get("NC_MAIL_QUEUE_FOLDER", "/nc-mail-queue")) + fn = datetime.utcnow().isoformat() + secrets.token_hex(4) + sh = d.joinpath(fn + '.sh') + tmp = d.joinpath(fn + '.tmp') + # Create executable file + tmp.touch(mode=0o750) + # Write script + tmp.write_text(self._nextcloud_mail_set_sh(users, extra_data)) + # Put it in-place + tmp.rename(sh) return {} def check_connections(self, app : "AdminFlaskApp") -> None: diff --git a/dd-sso/admin/src/admin/views/MailViews.py b/dd-sso/admin/src/admin/views/MailViews.py index 285274e..a152dfe 100644 --- a/dd-sso/admin/src/admin/views/MailViews.py +++ b/dd-sso/admin/src/admin/views/MailViews.py @@ -46,7 +46,7 @@ def setup_mail_views(app: "AdminFlaskApp") -> None: key = json.dumps(mail_3p.our_pubkey_jwk) return key, 200, {"Content-Type": "application/json"} - @app.route("/ddapi/mailusers", methods=["GET", "POST", "PUT", "DELETE"]) + @app.route("/ddapi/mailusers", methods=["GET", "POST", "PUT"]) @has_jws_token(app) def ddapi_mail_users() -> JsonResponse: users: List[Dict[str, Any]] = [] @@ -66,8 +66,10 @@ def setup_mail_views(app: "AdminFlaskApp") -> None: raise Error( "internal_server", "Failure sending users", traceback.format_exc() ) - if request.method not in ["POST", "PUT", "DELETE"]: + if request.method not in ["POST", "PUT"]: # Unsupported method + # Note we do not support DELETE as it is taken care of when the + # full Nextcloud user is deleted. return json.dumps({}), 400, JsonHeaders try: @@ -86,8 +88,6 @@ def setup_mail_views(app: "AdminFlaskApp") -> None: res: Dict if request.method in ["POST", "PUT"]: res = app.admin.nextcloud_mail_set(users, dec_data) - elif request.method == "DELETE": - res = app.admin.nextcloud_mail_delete(users, dec_data) return ( json.dumps(res), 200, diff --git a/dd-sso/docker-compose-parts/admin.yml b/dd-sso/docker-compose-parts/admin.yml index 164d51a..baf4897 100644 --- a/dd-sso/docker-compose-parts/admin.yml +++ b/dd-sso/docker-compose-parts/admin.yml @@ -38,6 +38,7 @@ services: - ${DATA_FOLDER}/saml_certs:/admin/saml_certs:rw - ${DATA_FOLDER}/legal:/admin/admin/static/templates/pages/legal:rw - ${DATA_FOLDER}/dd-admin:/data:rw + - ${DATA_FOLDER}/nc-mail-queue:/nc-mail-queue:rw env_file: - .env environment: @@ -46,4 +47,5 @@ services: - MANAGED_EMAIL_DOMAIN=${MANAGED_EMAIL_DOMAIN} - DATA_FOLDER=/data - CUSTOM_FOLDER=/admin/custom + - NC_MAIL_QUEUE_FOLDER=/nc-mail-queue - LEGAL_PATH=/admin/admin/static/templates/pages/legal