Skip to content

Commit 56fc85d

Browse files
committed
Improve docroot validation, make add/edit consistent
1 parent 20cb375 commit 56fc85d

File tree

3 files changed

+57
-19
lines changed

3 files changed

+57
-19
lines changed

control/webapp/member.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import re
2-
import string
3-
41
from flask import Blueprint, redirect, render_template, request, url_for
52
from werkzeug.exceptions import Forbidden, NotFound
63

@@ -10,7 +7,10 @@
107
from srcf.database import Domain
118

129
from . import inspect_services, utils
13-
from .utils import create_job_maybe_email_and_redirect, effective_member, parse_domain_name, srcf_db_sess as sess
10+
from .utils import (
11+
create_job_maybe_email_and_redirect, effective_member, parse_domain_name,
12+
validate_domain_docroot, srcf_db_sess as sess,
13+
)
1414

1515

1616
bp = Blueprint("member", __name__)
@@ -252,6 +252,7 @@ def add_vhost():
252252

253253
domain = request.form.get("domain", "").strip()
254254
root = request.form.get("root", "").strip()
255+
255256
if domain:
256257
parsed = parse_domain_name(domain)
257258
if domain != parsed:
@@ -270,6 +271,11 @@ def add_vhost():
270271
else:
271272
errors["domain"] = "Please enter a domain or subdomain."
272273

274+
if root:
275+
root, msg = validate_domain_docroot(mem, root)
276+
if msg:
277+
errors["root"] = msg
278+
273279
if request.form.get("edit") or errors:
274280
return render_template("member/add_vhost.html", member=mem, domain=domain, root=root, errors=errors)
275281
elif not request.form.get("confirm"):
@@ -303,12 +309,10 @@ def change_vhost_docroot(domain):
303309

304310
if request.method == "POST":
305311
root = request.form.get("root", "").strip()
306-
if any([ch in root for ch in string.whitespace + "\\" + "\"" + "\'"]) or ".." in root:
307-
errors["root"] = "This document root is invalid."
308-
try:
309-
domain = parse_domain_name(domain)
310-
except ValueError as e:
311-
errors["domain"] = e.args[0]
312+
if root:
313+
root, msg = validate_domain_docroot(mem, root)
314+
if msg:
315+
errors["root"] = msg
312316

313317
if request.method == "POST" and not errors:
314318
return create_job_maybe_email_and_redirect(

control/webapp/society.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import re
2-
import string
32

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

1211
from . import inspect_services, utils
13-
from .utils import create_job_maybe_email_and_redirect, find_mem_society, parse_domain_name, srcf_db_sess as sess
12+
from .utils import (
13+
create_job_maybe_email_and_redirect, find_mem_society, parse_domain_name,
14+
validate_domain_docroot, srcf_db_sess as sess,
15+
)
1416

1517

1618
bp = Blueprint("society", __name__)
@@ -286,6 +288,7 @@ def add_vhost(society):
286288

287289
domain = request.form.get("domain", "").strip()
288290
root = request.form.get("root", "").strip()
291+
289292
if domain:
290293
parsed = parse_domain_name(domain)
291294
if domain != parsed:
@@ -304,6 +307,11 @@ def add_vhost(society):
304307
else:
305308
errors["domain"] = "Please enter a domain or subdomain."
306309

310+
if root:
311+
root, msg = validate_domain_docroot(mem, root)
312+
if msg:
313+
errors["root"] = msg
314+
307315
if request.form.get("edit") or errors:
308316
return render_template("society/add_vhost.html", society=soc, member=mem, domain=domain, root=root, errors=errors)
309317
elif not request.form.get("confirm"):
@@ -337,12 +345,10 @@ def change_vhost_docroot(society, domain):
337345

338346
if request.method == "POST":
339347
root = request.form.get("root", "").strip()
340-
if any([ch in root for ch in string.whitespace + "\\" + "\"" + "\'"]) or ".." in root:
341-
errors["root"] = "This document root is invalid."
342-
try:
343-
domain = parse_domain_name(domain)
344-
except ValueError as e:
345-
errors["domain"] = e.args[0]
348+
if root:
349+
root, msg = validate_domain_docroot(mem, root)
350+
if msg:
351+
errors["root"] = msg
346352

347353
if request.method == "POST" and not errors:
348354
return create_job_maybe_email_and_redirect(

control/webapp/utils.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime
22
from functools import partial
33
import os
4+
import string
45
import sys
56
import traceback
67
from urllib.parse import urlparse
@@ -15,7 +16,7 @@
1516

1617
from srcf.controllib.jobs import CreateSociety, Reactivate, Signup, SocietyJob
1718
from srcf.controllib.utils import email_re, is_admin, ldapsearch, mysql_conn
18-
from srcf.database import JobLog, queries, Session
19+
from srcf.database import Member, JobLog, queries, Session
1920
from srcf.mail import mail_sysadmins
2021
import ucam_webauth
2122
import ucam_webauth.flask_glue
@@ -91,6 +92,33 @@ def parse_domain_name(domain):
9192
return domain.encode("idna").decode("ascii")
9293

9394

95+
def validate_domain_docroot(owner, path):
96+
if not path:
97+
return path, None
98+
if any(ch in path for ch in string.whitespace + "\\" + '"' + "'"):
99+
return path, "Document roots cannot contain spaces or quotes."
100+
if path.startswith("public_html/"):
101+
path = path.replace("public_html/", "", 1)
102+
if isinstance(owner, Member):
103+
username = owner.crsid
104+
top = "home"
105+
else:
106+
username = owner.society
107+
top = "societies"
108+
base = os.path.join("/public", top, username, "public_html")
109+
target = os.path.abspath(os.path.join(base, path))
110+
if not target.startswith(base):
111+
return path, "Document roots must be inside your public_html directory."
112+
elif base == target:
113+
return "", "We've cleared your document root as it appears to be your public_html directory."
114+
elif not os.path.exists(target):
115+
return path, "This document root doesn't exist, or isn't accessible to the webserver. Create the directory first, then try again."
116+
clean = target[len(base) + 1:]
117+
if clean != path:
118+
return clean, "We've fixed your document root to its canonical version; submit again to confirm."
119+
return path, None
120+
121+
94122
# Template helpers
95123
def sif(variable, val):
96124
""""string if": `val` if `variable` is defined and truthy, else ''"""

0 commit comments

Comments
 (0)