diff --git a/container_to_container/Dockerfile b/container_to_container/Dockerfile new file mode 100644 index 0000000..e664ebb --- /dev/null +++ b/container_to_container/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.12.4 + +RUN apt-get update +RUN apt -y install mc +RUN apt -y install docker.io +RUN pip install pymongo +RUN pip install email_validator +RUN pip install flask +RUN pip install gunicorn +RUN pip install requests +RUN pip install BeautifulSoup4 +RUN apt -y install bash +RUN pip install --upgrade pip +RUN pip install flask_wtf +RUN pip install wtforms +RUN pip install flask_recaptcha +RUN pip install Markup +RUN pip install captcha Pillow + +COPY tools . + +EXPOSE 80 + +ENTRYPOINT ["/bin/bash", "-c", "gunicorn wsgi:app --bind 0.0.0.0:80"] + + diff --git a/container_to_container/README.md b/container_to_container/README.md index 8b13789..328336c 100644 --- a/container_to_container/README.md +++ b/container_to_container/README.md @@ -1 +1,2 @@ +Set a secret key in tools/secret_key.json. Then build the docker image with make_image.sh. diff --git a/container_to_container/compose.yaml b/container_to_container/compose.yaml new file mode 100644 index 0000000..8c95770 --- /dev/null +++ b/container_to_container/compose.yaml @@ -0,0 +1,20 @@ +services: + overleafregister: + image: "overleafregister_image" + container_name: overleafregister + hostname: overleafregister + restart: always + + networks: + - overleaf-network + + volumes: + - overleaf_register:/data + - /var/run/docker.sock:/var/run/docker.sock + +volumes: + overleaf_register: + +networks: + overleaf-network: + external: true diff --git a/container_to_container/down.sh b/container_to_container/down.sh new file mode 100644 index 0000000..c864209 --- /dev/null +++ b/container_to_container/down.sh @@ -0,0 +1,2 @@ +docker compose down + diff --git a/container_to_container/exec.sh b/container_to_container/exec.sh new file mode 100644 index 0000000..a208d2e --- /dev/null +++ b/container_to_container/exec.sh @@ -0,0 +1 @@ +docker exec -it overleafregister bash diff --git a/container_to_container/logs.sh b/container_to_container/logs.sh new file mode 100644 index 0000000..5fd46e9 --- /dev/null +++ b/container_to_container/logs.sh @@ -0,0 +1,2 @@ +docker compose logs -f + diff --git a/container_to_container/make_image.sh b/container_to_container/make_image.sh new file mode 100644 index 0000000..f01fade --- /dev/null +++ b/container_to_container/make_image.sh @@ -0,0 +1 @@ +docker build --network host -t overleafregister_image . diff --git a/container_to_container/tools/allowed_domains.json b/container_to_container/tools/allowed_domains.json new file mode 100644 index 0000000..b6acf6f --- /dev/null +++ b/container_to_container/tools/allowed_domains.json @@ -0,0 +1,5 @@ +{ + "allowed_domains": [ + "uni-bremen.de" + ] +} diff --git a/container_to_container/tools/blocked_users.json b/container_to_container/tools/blocked_users.json new file mode 100644 index 0000000..60d6660 --- /dev/null +++ b/container_to_container/tools/blocked_users.json @@ -0,0 +1,6 @@ +{ + "blocked_users": [ + "" + ] +} + diff --git a/container_to_container/tools/check_invites.py b/container_to_container/tools/check_invites.py new file mode 100644 index 0000000..fdbaf77 --- /dev/null +++ b/container_to_container/tools/check_invites.py @@ -0,0 +1,13 @@ +import pymongo + +def check_invites(email_to_find: str,container_name:str = "overleafmongo", port: int = 27017) -> bool: + client = pymongo.MongoClient(container_name, port) + db = client.sharelatex + project_invites = db.projectInvites + + search_result = project_invites.find_one({"email": email_to_find}) + if search_result is None: + return False + else: + return True + diff --git a/container_to_container/tools/check_user.py b/container_to_container/tools/check_user.py new file mode 100644 index 0000000..b205393 --- /dev/null +++ b/container_to_container/tools/check_user.py @@ -0,0 +1,13 @@ +import pymongo + +def check_user(email_to_find: str, container_name:str = "overleafmongo", port: int = 27017) -> bool: + client = pymongo.MongoClient(container_name, port) + db = client.sharelatex + users = db.users + + search_result = users.find_one({"email": email_to_find}) + if search_result is None: + return False + else: + return True + diff --git a/container_to_container/tools/create_account.py b/container_to_container/tools/create_account.py new file mode 100644 index 0000000..9717ac8 --- /dev/null +++ b/container_to_container/tools/create_account.py @@ -0,0 +1,14 @@ +import subprocess + +def create_account(email: str = "davrot@uni-bremen.de", container_name: str = "overleafserver"): + + work_string: str = f"cd /overleaf/services/web && node modules/server-ce-scripts/scripts/create-user --email={email}" + result = subprocess.run(["docker", "exec", container_name, "/bin/bash", "-ce", work_string ], capture_output=True, text=True) + + success: bool = False + for i in result.stdout.splitlines(): + if i.startswith(f"Successfully created {email} as a"): + success = True + return success + + diff --git a/container_to_container/tools/main.py b/container_to_container/tools/main.py new file mode 100644 index 0000000..1e244e4 --- /dev/null +++ b/container_to_container/tools/main.py @@ -0,0 +1,53 @@ +import json +from flask import Flask, render_template, request, Response, send_from_directory, session +from io import BytesIO +from captcha.image import ImageCaptcha +import random +import base64 +from process_emails import process_emails + +container_name_mongo:str = "overleafmongo" +port_mongo: int = 27017 +container_name_overleaf: str = "overleafserver" + +app = Flask(__name__) + +with open("secret_key.json", "r") as file: + secret_key: dict = json.load(file) + +assert secret_key is not None +assert secret_key["secret_key"] is not None +app.config['SECRET_KEY'] = secret_key["secret_key"] + + +def generate_captcha(): + image = ImageCaptcha(width=280, height=90) + captcha_text = ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=6)) + data = image.generate(captcha_text) + return captcha_text, data + +@app.route("/register", methods=["GET", "POST"]) +def index() -> Response: + + if request.method == "GET": + captcha_text, captcha_image = generate_captcha() + session['captcha'] = captcha_text + captcha_base64 = base64.b64encode(captcha_image.getvalue()).decode('utf-8') + return render_template('post.html', captcha_image=captcha_base64) + + elif request.method == "POST": + post_content = request.form.get('content') + email = request.form.get('email') + user_captcha = request.form.get('captcha') + + if user_captcha and user_captcha.upper() == session.get('captcha'): + if process_emails(mail_address=email,container_name_mongo=container_name_mongo,port_mongo=port_mongo,container_name_overleaf=container_name_overleaf): + return f"A email was sent to {email}. Please click the activation link. Please check your spam folder!

Back to the overleaf site..." + else: + return f"We couldn't register your email {email}." + else: + return f"There was a problem with solving the captcha. Try again. Sorry!" + +@app.route("/register/static/", methods=["GET"]) +def serve_static_files(path) -> Response: + return send_from_directory("static", path) diff --git a/container_to_container/tools/process_emails.py b/container_to_container/tools/process_emails.py new file mode 100644 index 0000000..d6cc6aa --- /dev/null +++ b/container_to_container/tools/process_emails.py @@ -0,0 +1,56 @@ +from email_validator import validate_email # type: ignore +import email_validator +import json + +from check_invites import check_invites +from check_user import check_user +from create_account import create_account + +def process_emails( + mail_address: str, + config_file: str = "allowed_domains.json", + blocked_user_file: str = "blocked_users.json", + container_name_mongo:str = "overleafmongo", + port_mongo: int = 27017, + container_name_overleaf: str = "overleafserver", +) -> bool: + + with open(config_file, "r") as file: + allowed_domains: dict = json.load(file) + + with open(blocked_user_file, "r") as file: + blocked_users: dict = json.load(file) + + if (mail_address == "") or (mail_address is None): + return False + try: + emailinfo = validate_email(mail_address, check_deliverability=False) + mail_address = emailinfo.normalized + except email_validator.exceptions_types.EmailSyntaxError: + return False + except email_validator.exceptions_types.EmailNotValidError: + return False + + for blocked_user in blocked_users["blocked_users"]: + if mail_address == blocked_user: + return False + + is_email_allowed: bool = False + + if check_invites(email_to_find=mail_address,container_name=container_name_mongo, port=port_mongo): + is_email_allowed = True + + if check_user(email_to_find=mail_address,container_name=container_name_mongo, port=port_mongo): + is_email_allowed = True + + if is_email_allowed is False: + domain_found: bool = False + for domain in allowed_domains["allowed_domains"]: + if mail_address.endswith(domain): + domain_found = True + + if domain_found is False: + return False + + return create_account(email=mail_address, container_name=container_name_overleaf) + diff --git a/container_to_container/tools/run.sh b/container_to_container/tools/run.sh new file mode 100644 index 0000000..82afc33 --- /dev/null +++ b/container_to_container/tools/run.sh @@ -0,0 +1 @@ +gunicorn wsgi:app --bind 0.0.0.0:80 diff --git a/container_to_container/tools/secret_key.json b/container_to_container/tools/secret_key.json new file mode 100644 index 0000000..8298976 --- /dev/null +++ b/container_to_container/tools/secret_key.json @@ -0,0 +1,4 @@ +{ + "secret_key": "REDACTED" +} + diff --git a/container_to_container/tools/static/logo.png b/container_to_container/tools/static/logo.png new file mode 100644 index 0000000..0a1bef9 Binary files /dev/null and b/container_to_container/tools/static/logo.png differ diff --git a/container_to_container/tools/templates/post.html b/container_to_container/tools/templates/post.html new file mode 100644 index 0000000..74330fa --- /dev/null +++ b/container_to_container/tools/templates/post.html @@ -0,0 +1,49 @@ + + + + + + Register your overleaf account + + + +

+ Logo +
+ +

Register your overleaf account

+
+
+
+ +
+

+

+ +
+ CAPTCHA +
+ +
+

+ +

+ +

What is this?

+This overleaf server is a side project for me.
+Thus, please don't expect a perfectly hosted system with zero downtime. It runs on a ZfN cloud machine.
+If you want to help me maintain this system, you are more than welcome.
+I will do my best but server maintainance is not one of my core duties.
+If you have questions, send me an email: davrot@uni-bremen.de.
+ +

Who can register?

+You can register if you have a University of Bremen email address ending in uni-bremen.de.
+Or someone has invited you to a project. In the latter case you don't need an email address from Univesity of Bremen. + +

Allgemeines

+Impressum
+Datenschutz
+Notfall + + + diff --git a/container_to_container/tools/wsgi.py b/container_to_container/tools/wsgi.py new file mode 100644 index 0000000..fc5f41e --- /dev/null +++ b/container_to_container/tools/wsgi.py @@ -0,0 +1,4 @@ +from main import app + +if __name__ == "__main__": + app.run(debug=True) diff --git a/container_to_container/up.sh b/container_to_container/up.sh new file mode 100644 index 0000000..a4a5dbb --- /dev/null +++ b/container_to_container/up.sh @@ -0,0 +1,2 @@ +docker compose up -d +