Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to domain validation #17

Merged
merged 4 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion control/templates/shared/add_vhost_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ <h2>Test custom domain</h2>
<input type="hidden" name="confirm" value="on">
{% if valid[domain] == (true, true) or valid[prefixed] == (true, true) %}
<input type="submit" class="btn btn-outline-primary" value="Looks good, add domain">
{% else %}
{% elif valid[domain] != (none, none) or valid[prefixed] != (none, none) %}
<input type="submit" class="btn btn-outline-danger" value="Add domain anyway">
{% endif %}
</form>
Expand Down
24 changes: 14 additions & 10 deletions control/webapp/member.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import re
import string

from flask import Blueprint, redirect, render_template, request, url_for
from werkzeug.exceptions import Forbidden, NotFound

Expand All @@ -10,7 +7,10 @@
from srcf.database import Domain

from . import inspect_services, utils
from .utils import create_job_maybe_email_and_redirect, effective_member, parse_domain_name, srcf_db_sess as sess
from .utils import (
create_job_maybe_email_and_redirect, effective_member, parse_domain_name,
validate_domain_docroot, srcf_db_sess as sess,
)


bp = Blueprint("member", __name__)
Expand Down Expand Up @@ -252,6 +252,7 @@ def add_vhost():

domain = request.form.get("domain", "").strip()
root = request.form.get("root", "").strip()

if domain:
parsed = parse_domain_name(domain)
if domain != parsed:
Expand All @@ -270,6 +271,11 @@ def add_vhost():
else:
errors["domain"] = "Please enter a domain or subdomain."

if root:
root, msg = validate_domain_docroot(mem, root)
if msg:
errors["root"] = msg

if request.form.get("edit") or errors:
return render_template("member/add_vhost.html", member=mem, domain=domain, root=root, errors=errors)
elif not request.form.get("confirm"):
Expand Down Expand Up @@ -303,12 +309,10 @@ def change_vhost_docroot(domain):

if request.method == "POST":
root = request.form.get("root", "").strip()
if any([ch in root for ch in string.whitespace + "\\" + "\"" + "\'"]) or ".." in root:
errors["root"] = "This document root is invalid."
try:
domain = parse_domain_name(domain)
except ValueError as e:
errors["domain"] = e.args[0]
if root:
root, msg = validate_domain_docroot(mem, root)
if msg:
errors["root"] = msg

if request.method == "POST" and not errors:
return create_job_maybe_email_and_redirect(
Expand Down
22 changes: 14 additions & 8 deletions control/webapp/society.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import re
import string

from flask import Blueprint, redirect, render_template, request, url_for
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
Expand All @@ -10,7 +9,10 @@
from srcf.database import Domain

from . import inspect_services, utils
from .utils import create_job_maybe_email_and_redirect, find_mem_society, parse_domain_name, srcf_db_sess as sess
from .utils import (
create_job_maybe_email_and_redirect, find_mem_society, parse_domain_name,
validate_domain_docroot, srcf_db_sess as sess,
)


bp = Blueprint("society", __name__)
Expand Down Expand Up @@ -286,6 +288,7 @@ def add_vhost(society):

domain = request.form.get("domain", "").strip()
root = request.form.get("root", "").strip()

if domain:
parsed = parse_domain_name(domain)
if domain != parsed:
Expand All @@ -304,6 +307,11 @@ def add_vhost(society):
else:
errors["domain"] = "Please enter a domain or subdomain."

if root:
root, msg = validate_domain_docroot(soc, root)
if msg:
errors["root"] = msg

if request.form.get("edit") or errors:
return render_template("society/add_vhost.html", society=soc, member=mem, domain=domain, root=root, errors=errors)
elif not request.form.get("confirm"):
Expand Down Expand Up @@ -337,12 +345,10 @@ def change_vhost_docroot(society, domain):

if request.method == "POST":
root = request.form.get("root", "").strip()
if any([ch in root for ch in string.whitespace + "\\" + "\"" + "\'"]) or ".." in root:
errors["root"] = "This document root is invalid."
try:
domain = parse_domain_name(domain)
except ValueError as e:
errors["domain"] = e.args[0]
if root:
root, msg = validate_domain_docroot(soc, root)
if msg:
errors["root"] = msg

if request.method == "POST" and not errors:
return create_job_maybe_email_and_redirect(
Expand Down
30 changes: 29 additions & 1 deletion control/webapp/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from functools import partial
import os
import string
import sys
import traceback
from urllib.parse import urlparse
Expand All @@ -15,7 +16,7 @@

from srcf.controllib.jobs import CreateSociety, Reactivate, Signup, SocietyJob
from srcf.controllib.utils import email_re, is_admin, ldapsearch, mysql_conn
from srcf.database import JobLog, queries, Session
from srcf.database import Member, JobLog, queries, Session
from srcf.mail import mail_sysadmins
import ucam_webauth
import ucam_webauth.flask_glue
Expand Down Expand Up @@ -91,6 +92,33 @@ def parse_domain_name(domain):
return domain.encode("idna").decode("ascii")


def validate_domain_docroot(owner, path):
if not path:
return path, None
if any(ch in path for ch in string.whitespace + "\\" + '"' + "'"):
return path, "Document roots cannot contain spaces, backslashes or quotes."
if path.startswith("public_html/"):
path = path.replace("public_html/", "", 1)
if isinstance(owner, Member):
username = owner.crsid
top = "home"
else:
username = owner.society
top = "societies"
base = os.path.join("/public", top, username, "public_html")
target = os.path.abspath(os.path.join(base, path))
if os.path.commonpath((base, target)) != base:
return path, "Document roots must be inside your public_html directory."
elif base == target:
return "", "We've cleared your document root as it appears to be your public_html directory."
elif not os.path.exists(target):
return path, "This document root doesn't exist, or isn't accessible to the webserver. Create the directory first, then try again."
clean = target[len(base) + 1:]
if clean != path:
return clean, "We've fixed your document root to its canonical version; submit again to confirm."
return path, None


# Template helpers
def sif(variable, val):
""""string if": `val` if `variable` is defined and truthy, else ''"""
Expand Down
Loading