def patch(self, user_id): user = Users.query.filter_by(id=user_id).first_or_404() data = request.get_json() data["id"] = user_id # Admins should not be able to ban themselves if data["id"] == session["id"] and (data.get("banned") is True or data.get("banned") == "true"): return ( { "success": False, "errors": { "id": "You cannot ban yourself" } }, 400, ) schema = UserSchema(view="admin", instance=user, partial=True) response = schema.load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 # This generates the response first before actually changing the type # This avoids an error during User type changes where we change # the polymorphic identity resulting in an ObjectDeletedError # https://github.com/CTFd/CTFd/issues/1794 response = schema.dump(response.data) db.session.commit() db.session.close() clear_user_session(user_id=user_id) clear_standings() return {"success": True, "data": response.data}
def delete(self, user_id): # Admins should not be able to delete themselves if user_id == session["id"]: return ( { "success": False, "errors": { "id": "You cannot delete yourself" } }, 400, ) Notifications.query.filter_by(user_id=user_id).delete() Awards.query.filter_by(user_id=user_id).delete() Unlocks.query.filter_by(user_id=user_id).delete() Submissions.query.filter_by(user_id=user_id).delete() Solves.query.filter_by(user_id=user_id).delete() Tracking.query.filter_by(user_id=user_id).delete() Users.query.filter_by(id=user_id).delete() db.session.commit() db.session.close() clear_user_session(user_id=user_id) clear_standings() return {"success": True}
def update_user(user): session["id"] = user.id session["hash"] = hmac(user.password) session.permanent = True # Clear out any currently cached user attributes clear_user_session(user_id=user.id)
def patch(self, user_id): user = Users.query.filter_by(id=user_id).first_or_404() data = request.get_json() data["id"] = user_id # Admins should not be able to ban themselves if data["id"] == session["id"] and (data.get("banned") is True or data.get("banned") == "true"): return ( { "success": False, "errors": { "id": "You cannot ban yourself" } }, 400, ) schema = UserSchema(view="admin", instance=user, partial=True) response = schema.load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.commit() response = schema.dump(response.data) db.session.close() clear_user_session(user_id=user_id) clear_standings() return {"success": True, "data": response}
def test_clear_user_session(): app = create_ctfd() with app.app_context(): register_user(app) # Users by default should have a non-admin type user = Users.query.filter_by(id=2).first() with app.test_request_context("/"): login_user(user) user = get_current_user() assert user.id == 2 assert user.type == "user" assert is_admin() is False # Set the user's updated type user = Users.query.filter_by(id=2).first() user.type = "admin" app.db.session.commit() # Should still return False because this is still cached assert is_admin() is False clear_user_session(user_id=2) # Should now return True after clearing cache assert is_admin() is True destroy_ctfd(app)
def join(): infos = get_infos() errors = get_errors() user = get_current_user_attrs() if user.team_id: errors.append("You are already in a team. You cannot join another.") if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append("Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural)) return render_template("teams/join_team.html", infos=infos, errors=errors) if request.method == "POST": teamname = request.form.get("name") passphrase = request.form.get("password", "").strip() team = Teams.query.filter_by(name=teamname).first() if errors: return ( render_template("teams/join_team.html", infos=infos, errors=errors), 403, ) if team and verify_password(passphrase, team.password): team_size_limit = get_config("team_size", default=0) if team_size_limit and len(team.members) >= team_size_limit: errors.append( "{name} has already reached the team size limit of {limit}" .format(name=team.name, limit=team_size_limit)) return render_template("teams/join_team.html", infos=infos, errors=errors) user = get_current_user() user.team_id = team.id db.session.commit() if len(team.members) == 1: team.captain_id = user.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing")) else: errors.append("That information is incorrect") return render_template("teams/join_team.html", infos=infos, errors=errors)
def delete(self): team_disbanding = get_config("team_disbanding", default="inactive_only") if team_disbanding == "disabled": return ( { "success": False, "errors": {"": ["Team disbanding is currently disabled"]}, }, 403, ) team = get_current_team() if team.captain_id != session["id"]: return ( { "success": False, "errors": {"": ["Only team captains can disband their team"]}, }, 403, ) # The team must not have performed any actions in the CTF performed_actions = any( [ team.solves != [], team.fails != [], team.awards != [], Submissions.query.filter_by(team_id=team.id).all() != [], Unlocks.query.filter_by(team_id=team.id).all() != [], ] ) if performed_actions: return ( { "success": False, "errors": { "": [ "You cannot disband your team as it has participated in the event. " "Please contact an admin to disband your team or remove a member." ] }, }, 403, ) for member in team.members: member.team_id = None clear_user_session(user_id=member.id) db.session.delete(team) db.session.commit() clear_team_session(team_id=team.id) clear_standings() db.session.close() return {"success": True}
def confirm(data=None): if not get_config("verify_emails"): # If the CTF doesn't care about confirming email addresses then redierct to challenges return redirect(url_for("challenges.listing")) # User is confirming email account if data and request.method == "GET": try: user_email = unserialize(data, max_age=1800) except (BadTimeSignature, SignatureExpired): return render_template( "confirm.html", errors=["Your confirmation link has expired"]) except (BadSignature, TypeError, base64.binascii.Error): return render_template( "confirm.html", errors=["Your confirmation token is invalid"]) user = Users.query.filter_by(email=user_email).first_or_404() if user.verified: return redirect(url_for("views.settings")) user.verified = True log( "registrations", format="[{date}] {ip} - successful confirmation for {name}", name=user.name, ) db.session.commit() clear_user_session(user_id=user.id) email.successful_registration_notification(user.email) db.session.close() if current_user.authed(): return redirect(url_for("challenges.listing")) return redirect(url_for("auth.login")) # User is trying to start or restart the confirmation flow if not current_user.authed(): return redirect(url_for("auth.login")) user = Users.query.filter_by(id=session["id"]).first_or_404() if user.verified: return redirect(url_for("views.settings")) if data is None: if request.method == "POST": # User wants to resend their confirmation email email.verify_email_address(user.email) log( "registrations", format= "[{date}] {ip} - {name} initiated a confirmation email resend", ) return render_template( "confirm.html", user=user, infos=["Your confirmation email has been resent!"], ) elif request.method == "GET": # User has been directed to the confirm page return render_template("confirm.html", user=user)
def login_user(user): session["id"] = user.id session["name"] = user.name session["email"] = user.email session["nonce"] = generate_nonce() # Clear out any currently cached user attributes clear_user_session(user_id=user.id)
def update_user(user): session["id"] = user.id session["name"] = user.name session["email"] = user.email session["hash"] = hmac(user.password) # Clear out any currently cached user attributes clear_user_session(user_id=user.id)
def delete(self, user_id): Notifications.query.filter_by(user_id=user_id).delete() Awards.query.filter_by(user_id=user_id).delete() Unlocks.query.filter_by(user_id=user_id).delete() Submissions.query.filter_by(user_id=user_id).delete() Solves.query.filter_by(user_id=user_id).delete() Tracking.query.filter_by(user_id=user_id).delete() Users.query.filter_by(id=user_id).delete() db.session.commit() db.session.close() clear_user_session(user_id=user_id) clear_standings() return {"success": True}
def join(): infos = get_infos() errors = get_errors() if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append( "Команды могут содержать не больше {limit} участников".format( limit=team_size_limit, plural=plural)) return render_template("teams/join_team.html", infos=infos, errors=errors) if request.method == "POST": teamname = request.form.get("name") passphrase = request.form.get("password", "").strip() team = Teams.query.filter_by(name=teamname).first() if team and verify_password(passphrase, team.password): team_size_limit = get_config("team_size", default=0) if team_size_limit and len(team.members) >= team_size_limit: errors.append( "Команда {name} уже достигла лимит в {limit} участников". format(name=team.name, limit=team_size_limit)) return render_template("teams/join_team.html", infos=infos, errors=errors) user = get_current_user() user.team_id = team.id db.session.commit() if len(team.members) == 1: team.captain_id = user.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing")) else: errors.append("Такая информация некорректна") return render_template("teams/join_team.html", infos=infos, errors=errors)
def delete(self, team_id): team = Teams.query.filter_by(id=team_id).first_or_404() team_id = team.id for member in team.members: member.team_id = None clear_user_session(user_id=member.id) db.session.delete(team) db.session.commit() clear_team_session(team_id=team_id) clear_standings() db.session.close() return {"success": True}
def patch(self): user = get_current_user() data = request.get_json() schema = UserSchema(view="self", instance=user, partial=True) response = schema.load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.commit() clear_user_session(user_id=user.id) response = schema.dump(response.data) db.session.close() clear_standings() return {"success": True, "data": response.data}
def patch(self, user_id): user = Users.query.filter_by(id=user_id).first_or_404() data = request.get_json() data["id"] = user_id schema = UserSchema(view="admin", instance=user, partial=True) response = schema.load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.commit() response = schema.dump(response.data) db.session.close() clear_user_session(user_id=user_id) clear_standings() return {"success": True, "data": response}
def new(): infos = get_infos() errors = get_errors() if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append( "Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural ) ) return render_template("teams/new_team.html", infos=infos, errors=errors) elif request.method == "POST": teamname = request.form.get("name", "").strip() passphrase = request.form.get("password", "").strip() errors = get_errors() user = get_current_user() existing_team = Teams.query.filter_by(name=teamname).first() if existing_team: errors.append("That team name is already taken") if not teamname: errors.append("That team name is invalid") if errors: return render_template("teams/new_team.html", errors=errors) team = Teams(name=teamname, password=passphrase, captain_id=user.id) db.session.add(team) db.session.commit() user.team_id = team.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing"))
def invite(): infos = get_infos() errors = get_errors() code = request.args.get("code") if code is None: abort(404) user = get_current_user_attrs() if user.team_id: errors.append("You are already in a team. You cannot join another.") try: team = Teams.load_invite_code(code) except TeamTokenExpiredException: abort(403, description="This invite URL has expired") except TeamTokenInvalidException: abort(403, description="This invite URL is invalid") team_size_limit = get_config("team_size", default=0) if request.method == "GET": if team_size_limit: infos.append( "Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=pluralize(number=team_size_limit) ) ) return render_template( "teams/invite.html", team=team, infos=infos, errors=errors ) if request.method == "POST": if errors: return ( render_template( "teams/invite.html", team=team, infos=infos, errors=errors ), 403, ) if team_size_limit and len(team.members) >= team_size_limit: errors.append( "{name} has already reached the team size limit of {limit}".format( name=team.name, limit=team_size_limit ) ) return ( render_template( "teams/invite.html", team=team, infos=infos, errors=errors ), 403, ) user = get_current_user() user.team_id = team.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing"))
def new(): infos = get_infos() errors = get_errors() if bool(get_config("team_creation", default=True)) is False: abort( 403, description="Team creation is currently disabled. Please join an existing team.", ) num_teams_limit = int(get_config("num_teams", default=0)) num_teams = Teams.query.filter_by(banned=False, hidden=False).count() if num_teams_limit and num_teams >= num_teams_limit: abort( 403, description=f"Reached the maximum number of teams ({num_teams_limit}). Please join an existing team.", ) user = get_current_user_attrs() if user.team_id: errors.append("You are already in a team. You cannot join another.") if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append( "Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural ) ) return render_template("teams/new_team.html", infos=infos, errors=errors) elif request.method == "POST": teamname = request.form.get("name", "").strip() passphrase = request.form.get("password", "").strip() website = request.form.get("website") affiliation = request.form.get("affiliation") user = get_current_user() existing_team = Teams.query.filter_by(name=teamname).first() if existing_team: errors.append("That team name is already taken") if not teamname: errors.append("That team name is invalid") # Process additional user fields fields = {} for field in TeamFields.query.all(): fields[field.id] = field entries = {} for field_id, field in fields.items(): value = request.form.get(f"fields[{field_id}]", "").strip() if field.required is True and (value is None or value == ""): errors.append("Please provide all required fields") break # Handle special casing of existing profile fields if field.name.lower() == "affiliation": affiliation = value break elif field.name.lower() == "website": website = value break if field.field_type == "boolean": entries[field_id] = bool(value) else: entries[field_id] = value if website: valid_website = validators.validate_url(website) else: valid_website = True if affiliation: valid_affiliation = len(affiliation) < 128 else: valid_affiliation = True if valid_website is False: errors.append("Websites must be a proper URL starting with http or https") if valid_affiliation is False: errors.append("Please provide a shorter affiliation") if errors: return render_template("teams/new_team.html", errors=errors), 403 team = Teams(name=teamname, password=passphrase, captain_id=user.id) if website: team.website = website if affiliation: team.affiliation = affiliation db.session.add(team) db.session.commit() for field_id, value in entries.items(): entry = TeamFieldEntries(field_id=field_id, value=value, team_id=team.id) db.session.add(entry) db.session.commit() user.team_id = team.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing"))
def reset_password(data=None): if data is not None: try: email_address = unserialize(data, max_age=1800) except (BadTimeSignature, SignatureExpired): return render_template("reset_password.html", errors=["Your link has expired"]) except (BadSignature, TypeError, base64.binascii.Error): return render_template("reset_password.html", errors=["Your reset token is invalid"]) if request.method == "GET": return render_template("reset_password.html", mode="set") if request.method == "POST": 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() clear_user_session(user_id=user.id) log( "logins", format="[{date}] {ip} - successful password reset for {name}", name=user.name, ) db.session.close() email.password_change_alert(user.email) return redirect(url_for("auth.login")) if request.method == "POST": email_address = request.form["email"].strip() user = Users.query.filter_by(email=email_address).first() get_errors() if config.can_send_mail() is False: return render_template( "reset_password.html", errors=[ "Email could not be sent due to server misconfiguration" ], ) if not user: return render_template( "reset_password.html", errors=[ "If that account exists you will receive an email, please check your inbox" ], ) 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", errors=[ "If that account exists you will receive an email, please check your inbox" ], ) return render_template("reset_password.html")
def oauth_redirect(): oauth_code = request.args.get("code") state = request.args.get("state") if session["nonce"] != state: log("logins", "[{date}] {ip} - OAuth State validation mismatch") error_for(endpoint="auth.login", message="OAuth State validation mismatch.") return redirect(url_for("auth.login")) if oauth_code: url = (get_app_config("OAUTH_TOKEN_ENDPOINT") or get_config("oauth_token_endpoint") or "https://auth.majorleaguecyber.org/oauth/token") client_id = get_app_config("OAUTH_CLIENT_ID") or get_config( "oauth_client_id") client_secret = get_app_config("OAUTH_CLIENT_SECRET") or get_config( "oauth_client_secret") headers = {"content-type": "application/x-www-form-urlencoded"} data = { "code": oauth_code, "client_id": client_id, "client_secret": client_secret, "grant_type": "authorization_code", } token_request = requests.post(url, data=data, headers=headers) if token_request.status_code == requests.codes.ok: token = token_request.json()["access_token"] user_url = (get_app_config("OAUTH_API_ENDPOINT") or get_config("oauth_api_endpoint") or "https://api.majorleaguecyber.org/user") headers = { "Authorization": "Bearer " + str(token), "Content-type": "application/json", } api_data = requests.get(url=user_url, headers=headers).json() user_id = api_data["id"] user_name = api_data["name"] user_email = api_data["email"] user = Users.query.filter_by(email=user_email).first() if user is None: # Check if we are allowing registration before creating users if registration_visible() or mlc_registration(): user = Users( name=user_name, email=user_email, oauth_id=user_id, verified=True, ) db.session.add(user) db.session.commit() else: log("logins", "[{date}] {ip} - Public registration via MLC blocked") error_for( endpoint="auth.login", message= "Public registration is disabled. Please try again later.", ) return redirect(url_for("auth.login")) if get_config("user_mode") == TEAMS_MODE: team_id = api_data["team"]["id"] team_name = api_data["team"]["name"] team = Teams.query.filter_by(oauth_id=team_id).first() if team is None: team = Teams(name=team_name, oauth_id=team_id, captain_id=user.id) db.session.add(team) db.session.commit() clear_team_session(team_id=team.id) team_size_limit = get_config("team_size", default=0) if team_size_limit and len(team.members) >= team_size_limit: plural = "" if team_size_limit == 1 else "s" size_error = "Teams are limited to {limit} member{plural}.".format( limit=team_size_limit, plural=plural) error_for(endpoint="auth.login", message=size_error) return redirect(url_for("auth.login")) team.members.append(user) db.session.commit() if user.oauth_id is None: user.oauth_id = user_id user.verified = True db.session.commit() clear_user_session(user_id=user.id) login_user(user) return redirect(url_for("challenges.listing")) else: log("logins", "[{date}] {ip} - OAuth token retrieval failure") error_for(endpoint="auth.login", message="OAuth token retrieval failure.") return redirect(url_for("auth.login")) else: log("logins", "[{date}] {ip} - Received redirect without OAuth code") error_for(endpoint="auth.login", message="Received redirect without OAuth code.") return redirect(url_for("auth.login"))
def new(): infos = get_infos() errors = get_errors() if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append("Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural)) return render_template("teams/new_team.html", infos=infos, errors=errors) elif request.method == "POST": teamname = request.form.get("name", "").strip() passphrase = request.form.get("password", "").strip() errors = get_errors() website = request.form.get("website") affiliation = request.form.get("affiliation") user = get_current_user() existing_team = Teams.query.filter_by(name=teamname).first() if existing_team: errors.append("Такое имя команды уже занято") if not teamname: errors.append("Имя команды неправильное") # Process additional user fields fields = {} for field in TeamFields.query.all(): fields[field.id] = field entries = {} for field_id, field in fields.items(): value = request.form.get(f"fields[{field_id}]", "").strip() if field.required is True and (value is None or value == ""): errors.append("Пожалуйста, укажите все обязательные поля") break # Handle special casing of existing profile fields if field.name.lower() == "affiliation": affiliation = value break elif field.name.lower() == "website": website = value break if field.field_type == "boolean": entries[field_id] = bool(value) else: entries[field_id] = value if website: valid_website = validators.validate_url(website) else: valid_website = True if affiliation: valid_affiliation = len(affiliation) < 128 else: valid_affiliation = True if valid_website is False: errors.append( "Вебсайт должен быть правильной ссылкой, начинающейся с http или https" ) if valid_affiliation is False: errors.append("Пожалуйста, укажите учреждение покороче") if errors: return render_template("teams/new_team.html", errors=errors) team = Teams(name=teamname, password=passphrase, captain_id=user.id) if website: team.website = website if affiliation: team.affiliation = affiliation db.session.add(team) db.session.commit() for field_id, value in entries.items(): entry = TeamFieldEntries(field_id=field_id, value=value, team_id=team.id) db.session.add(entry) db.session.commit() user.team_id = team.id db.session.commit() clear_user_session(user_id=user.id) clear_team_session(team_id=team.id) return redirect(url_for("challenges.listing"))