From 6a422f6b70cef8697686a70668e990b7396a81be Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bonicoli Date: Wed, 23 Feb 2022 20:44:19 +0100 Subject: [PATCH] Send a notification to users Email the owners of the given repositories telling them that terms and conditions of the service must be abided. A private redmine note is added to an issue to keep track of these requests. This script must be executed on the Gitea host. Usage: # cat repositories.txt https://forge.chapril.org/user1/repo1 https://forge.chapril.org/userX/repoZ # ./notify_owners.py repositories.txt --- notify_owners.py | 194 +++++++++++++++++++++++++++++++++ notify_owners.requirements.txt | 2 + 2 files changed, 196 insertions(+) create mode 100755 notify_owners.py create mode 100644 notify_owners.requirements.txt diff --git a/notify_owners.py b/notify_owners.py new file mode 100755 index 0000000..fe25f5d --- /dev/null +++ b/notify_owners.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 + +import configparser +import datetime +from email.message import EmailMessage +import fileinput +import functools +import getpass +import json +import logging +import smtplib +from time import sleep +from string import Template +from urllib.parse import urlsplit + +import requests + +SUBJECT = "Licence du dépôt '$repository' hébergé sur la forge Chapril" +SUBJECT_TMPL = Template(SUBJECT) + +MAIL = """\ +Bonjour, + +Je suis animateur bénévole du service forge.chapril.org. + +Sauf erreur de ma part, le dépôt '$repository' [1] +ne spécifie pas clairement de licence et n'est donc pas conforme aux +conditions générales d'utilisation du service [2]. + +Nous vous demandons de bien vouloir vous conformer aux conditions +générales d'utilisation du service, à défaut ce dépôt sera supprimé dans +15 jours. + +Nous sommes heureux que nos services numériques libres, éthiques et loyaux +puissent vous être utiles. + +[1] https://forge.chapril.org/$user/$repository +[2] https://www.chapril.org/cgu.html#forgechaprilorg-cpu + +En vous souhaitant, au nom de toute l'équipe du Chapril, un agréable +usage de nos services libres, éthique et loyaux. + +-- +$animsys""" +MAIL_TMPL = Template(MAIL) + +AGIR_URL = "https://agir.april.org" +FORGE_ML = "forge-support@chapril.org" + + +def get_db_cursor(): + import psycopg2 + + conn = psycopg2.connect(**db_config_from_gitea(), target_session_attrs="read-only") + conn.set_session(readonly=True) + assert conn.readonly + logging.debug("Connected to gitea database") + return conn.cursor() + + +def db_config_from_gitea(): + config = configparser.ConfigParser() + with open("/etc/gitea/gitea.ini", encoding="utf-8") as giteacfg: + # skip the first lines without a section + while True: + pos = giteacfg.tell() + line = giteacfg.readline() + try: + config.read_string(line) + except configparser.MissingSectionHeaderError: + pass + + if config.sections(): + config.clear() + break + giteacfg.seek(pos) + config.read_file(giteacfg) + + db_params = { + "options": "-c default_transaction_read_only=on", + } + + if ":" in config["database"]["host"]: + db_host, db_port = config["database"]["host"].split(":") + db_params["host"] = db_host + db_params["port"] = db_port + else: + db_params["host"] = config["database"]["host"] + + for param, gitea_param in ( + ("sslmode", "ssl_mode"), + ("dbname", "name"), + ("user", "user"), + ("password", "passwd"), + ): + if gitea_param in config["database"]: + db_params[param] = config["database"][gitea_param] + + if "schema" in config["database"] and config["database"]["schema"]: + db_params["options"] += f' -c search_path={config["database"]["schema"]}' + + return db_params + + +def fetch_mail_from_db(cur, user): + query = "select email from public.user where name = %s;" + cur.execute(query, (user,)) + return cur.fetchone()[0] + + +def notify(smtp, repositories, agir_key, fetch_mail): + headers = {"X-Redmine-API-Key": agir_key, "Content-Type": "application/json"} + note = Template( + json.dumps( + { + "issue": { + "private_notes": True, + "notes": f'🔲 "$repository":$url: @$user@ notified by mail ' + f"({datetime.date.today()})", + } + } + ) + ) + + req = requests.get(f"{AGIR_URL}/users/current.json", headers=headers) + req.raise_for_status() + sender = req.json()["user"] + if "firstname" in sender and "lastname" in sender: + animsys = f"{sender['firstname']} {sender['lastname']} " f"({sender['login']})" + else: + animsys = sender["login"] + + for url in repositories: + url = url.strip() + if not url: + continue + if not "/" in url: + raise ValueError(f"URL not recognized ({url})") + user, repository = urlsplit(url).path.split("/")[1:3] + assert user and repository, f"Can not fetch user and repository from {url}" + user_email = fetch_mail(user) + logging.debug("Mail for '%s' is '%s'", user, user_email) + subject = SUBJECT_TMPL.substitute(repository=repository) + message = MAIL_TMPL.substitute( + user=user, repository=repository, animsys=animsys + ) + + email = EmailMessage() + email.set_content(message) + email["Subject"] = subject + email["From"] = FORGE_ML + email["To"] = user_email + email["CC"] = "forge-support@chapril.org" + + smtp.send_message(email) + logging.debug("Mail to '%s' (%s) sent for %s.", user, user_email, url) + + issue_api = f"{AGIR_URL}/issues/5615.json" + req = requests.put( + issue_api, + data=note.substitute(user=user, repository=repository, url=url), + headers=headers, + ) + req.raise_for_status() + logging.debug("Note added to %s for %s", issue_api, url) + + sleep(1) # throttle mail and web notifications! + + +def main(): + logging.basicConfig(level=logging.DEBUG) + + # In order to use another SMTP server (in such case, fetch_mail may need to + # be re-implemented using the Gitea API) + # + # import ssl + # server = input("type your smtp server: ") + # login = input("type your smtp login: ") + # password = getpass.getpass(prompt="type your password and press enter:") + # context = ssl.create_default_context(cafile="/usr/local/share/ca-certificates/own.crt") + # with smtplib.smtp_ssl(server, 465, context=context) as smtp: + # smtp.login(login, password) + + agir_key = getpass.getpass(prompt="Type agir.april.org API key:") + + repositories = fileinput.input() + with smtplib.SMTP("127.0.0.1", 25) as smtp: + with get_db_cursor() as cursor: + fetch_mail = functools.partial(fetch_mail_from_db, cursor) + notify(smtp, repositories, agir_key, fetch_mail) + + +if __name__ == "__main__": + main() diff --git a/notify_owners.requirements.txt b/notify_owners.requirements.txt new file mode 100644 index 0000000..b24e524 --- /dev/null +++ b/notify_owners.requirements.txt @@ -0,0 +1,2 @@ +psycopg2-binary +requests