Strip spaces on registration and have reset password use email address instead of names (#1218)

* Usernames are now properly stripped before being used in registration checks
* Reset password function now uses email addresses instead of user names for tokens
* Prevent MLC users from resetting their password
bulk-clear-sessions
Kevin Chung 2020-01-20 14:22:06 -05:00 committed by GitHub
parent fe85fdf1e5
commit f660ed1fb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 33 deletions

View File

@ -98,7 +98,7 @@ def confirm(data=None):
def reset_password(data=None):
if data is not None:
try:
name = unserialize(data, max_age=1800)
email_address = unserialize(data, max_age=1800)
except (BadTimeSignature, SignatureExpired):
return render_template(
"reset_password.html", errors=["Your link has expired"]
@ -111,20 +111,35 @@ def reset_password(data=None):
if request.method == "GET":
return render_template("reset_password.html", mode="set")
if request.method == "POST":
user = Users.query.filter_by(name=name).first_or_404()
user.password = request.form["password"].strip()
password = request.form.get("password", "").strip()
user = Users.query.filter_by(email=email_address).first_or_404()
if user.oauth_id:
return render_template(
"reset_password.html",
errors=[
"Your account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
],
)
pass_short = len(password) == 0
if pass_short:
return render_template(
"reset_password.html", errors=["Please pick a longer password"]
)
user.password = password
db.session.commit()
log(
"logins",
format="[{date}] {ip} - successful password reset for {name}",
name=name,
name=user.name,
)
db.session.close()
return redirect(url_for("auth.login"))
if request.method == "POST":
email_address = request.form["email"].strip()
team = Users.query.filter_by(email=email_address).first()
user = Users.query.filter_by(email=email_address).first()
get_errors()
@ -134,7 +149,7 @@ def reset_password(data=None):
errors=["Email could not be sent due to server misconfiguration"],
)
if not team:
if not user:
return render_template(
"reset_password.html",
errors=[
@ -142,7 +157,15 @@ def reset_password(data=None):
],
)
email.forgot_password(email_address, team.name)
if user.oauth_id:
return render_template(
"reset_password.html",
errors=[
"The email address associated with this account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
],
)
email.forgot_password(email_address)
return render_template(
"reset_password.html",
@ -159,9 +182,9 @@ def reset_password(data=None):
def register():
errors = get_errors()
if request.method == "POST":
name = request.form["name"]
email_address = request.form["email"]
password = request.form["password"]
name = request.form.get("name", "").strip()
email_address = request.form.get("email", "").strip().lower()
password = request.form.get("password", "").strip()
name_len = len(name) == 0
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
@ -170,9 +193,9 @@ def register():
.filter_by(email=email_address)
.first()
)
pass_short = len(password.strip()) == 0
pass_short = len(password) == 0
pass_long = len(password) > 128
valid_email = validators.validate_email(request.form["email"])
valid_email = validators.validate_email(email_address)
team_name_email_check = validators.validate_email(name)
if not valid_email:
@ -206,11 +229,7 @@ def register():
)
else:
with app.app_context():
user = Users(
name=name.strip(),
email=email_address.lower(),
password=password.strip(),
)
user = Users(name=name, email=email_address, password=password)
db.session.add(user)
db.session.commit()
db.session.flush()

View File

@ -50,6 +50,7 @@ class TeamSchema(ma.ModelSchema):
name = data.get("name")
if name is None:
return
name = name.strip()
existing_team = Teams.query.filter_by(name=name).first()
current_team = get_current_team()

View File

@ -55,6 +55,7 @@ class UserSchema(ma.ModelSchema):
name = data.get("name")
if name is None:
return
name = name.strip()
existing_user = Users.query.filter_by(name=name).first()
current_user = get_current_user()
@ -95,6 +96,7 @@ class UserSchema(ma.ModelSchema):
email = data.get("email")
if email is None:
return
email = email.strip()
existing_user = Users.query.filter_by(email=email).first()
current_user = get_current_user()

View File

@ -105,7 +105,7 @@ def new():
return render_template("teams/new_team.html", infos=infos, errors=errors)
elif request.method == "POST":
teamname = request.form.get("name")
teamname = request.form.get("name", "").strip()
passphrase = request.form.get("password", "").strip()
errors = get_errors()

View File

@ -16,17 +16,17 @@ def sendmail(addr, text, subject="Message from {ctf_name}"):
return False, "No mail settings configured"
def forgot_password(email, team_name):
token = serialize(team_name)
text = """Did you initiate a password reset? Click the following link to reset your password:
def forgot_password(email):
token = serialize(email)
text = """Did you initiate a password reset? If you didn't initiate this request you can ignore this email.
Click the following link to reset your password:
{0}/{1}
""".format(
url_for("auth.reset_password", _external=True), token
)
return sendmail(email, text)
subject = "Password Reset Request from {ctf_name}"
return sendmail(addr=email, text=text, subject=subject)
def verify_email_address(addr):
@ -36,7 +36,8 @@ def verify_email_address(addr):
url=url_for("auth.confirm", _external=True),
token=token,
)
return sendmail(addr, text)
subject = "Confirm your account for {ctf_name}"
return sendmail(addr=addr, text=text, subject=subject)
def user_created_notification(addr, name, password):

View File

@ -48,6 +48,13 @@ def test_register_duplicate_username():
password="password",
raise_for_error=False,
)
register_user(
app,
name="admin ",
email="admin2@ctfd.io",
password="password",
raise_for_error=False,
)
user_count = Users.query.count()
assert user_count == 2 # There's the admin user and the first created user
destroy_ctfd(app)
@ -353,11 +360,15 @@ def test_user_can_reset_password(mock_smtp):
# Build the email
msg = (
"""Did you initiate a password reset? Click the following link to reset """
"""your password:\n\nhttp://localhost/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M\n\n"""
"Did you initiate a password reset? If you didn't initiate this request you can ignore this email."
"\n\nClick the following link to reset your password:\n"
"http://localhost/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U\n"
)
ctf_name = get_config("ctf_name")
email_msg = MIMEText(msg)
email_msg["Subject"] = "Message from CTFd"
email_msg["Subject"] = "Password Reset Request from {ctf_name}".format(
ctf_name=ctf_name
)
email_msg["From"] = from_addr
email_msg["To"] = to_addr
@ -374,9 +385,11 @@ def test_user_can_reset_password(mock_smtp):
data = {"nonce": sess.get("nonce"), "password": "passwordtwo"}
# Do the password reset
client.get("/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M")
client.get(
"/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U"
)
client.post(
"/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M",
"/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U",
data=data,
)

View File

@ -156,7 +156,6 @@ def test_sendmail_with_mailgun_from_db_config(fake_post_request):
@patch("smtplib.SMTP")
@freeze_time("2012-01-14 03:21:34")
def test_verify_email(mock_smtp):
"""Does verify_email send emails"""
app = create_ctfd()
@ -171,7 +170,8 @@ def test_verify_email(mock_smtp):
from_addr = get_config("mailfrom_addr") or app.config.get("MAILFROM_ADDR")
to_addr = "user@user.com"
verify_email_address(to_addr)
with freeze_time("2012-01-14 03:21:34"):
verify_email_address(to_addr)
# This is currently not actually validated
msg = (
@ -182,7 +182,9 @@ def test_verify_email(mock_smtp):
ctf_name = get_config("ctf_name")
email_msg = MIMEText(msg)
email_msg["Subject"] = "Message from {0}".format(ctf_name)
email_msg["Subject"] = "Confirm your account for {ctf_name}".format(
ctf_name=ctf_name
)
email_msg["From"] = from_addr
email_msg["To"] = to_addr