diff --git a/CTFd/cache/__init__.py b/CTFd/cache/__init__.py index 9181a9a9..686e6949 100644 --- a/CTFd/cache/__init__.py +++ b/CTFd/cache/__init__.py @@ -46,6 +46,12 @@ def clear_pages(): cache.delete_memoized(get_page) +def clear_user_ips(user_id): + from CTFd.utils.user import get_user_ips + + cache.delete_memoized(get_user_ips, user_id=user_id) + + def clear_user_session(user_id): from CTFd.utils.user import get_user_attrs diff --git a/CTFd/utils/initialization/__init__.py b/CTFd/utils/initialization/__init__.py index eca2fb87..19aff5d9 100644 --- a/CTFd/utils/initialization/__init__.py +++ b/CTFd/utils/initialization/__init__.py @@ -7,6 +7,7 @@ from flask import abort, redirect, render_template, request, session, url_for from sqlalchemy.exc import IntegrityError, InvalidRequestError from werkzeug.wsgi import DispatcherMiddleware +from CTFd.cache import clear_user_ips from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException from CTFd.models import Tracking, db from CTFd.utils import config, get_config, markdown @@ -41,6 +42,7 @@ from CTFd.utils.security.csrf import generate_nonce from CTFd.utils.user import ( authed, get_current_user_attrs, + get_current_user_ips, get_current_team_attrs, get_ip, is_admin, @@ -179,20 +181,26 @@ def init_request_processors(app): return if authed(): - track = Tracking.query.filter_by(ip=get_ip(), user_id=session["id"]).first() - if not track: - visit = Tracking(ip=get_ip(), user_id=session["id"]) - db.session.add(visit) + user_ips = get_current_user_ips() + ip = get_ip() + track = None + if ip not in user_ips: + track = Tracking(ip=get_ip(), user_id=session["id"]) + db.session.add(track) else: - track.date = datetime.datetime.utcnow() + if request.method != "GET": + track = Tracking.query.filter_by( + ip=get_ip(), user_id=session["id"] + ).first() + track.date = datetime.datetime.utcnow() - try: - db.session.commit() - except (InvalidRequestError, IntegrityError): - db.session.rollback() - logout_user() - - db.session.close() + if track: + try: + db.session.commit() + except (InvalidRequestError, IntegrityError): + db.session.rollback() + logout_user() + clear_user_ips(user_id=session["id"]) @app.before_request def banned(): diff --git a/CTFd/utils/user/__init__.py b/CTFd/utils/user/__init__.py index 0c41ac19..5186e2b0 100644 --- a/CTFd/utils/user/__init__.py +++ b/CTFd/utils/user/__init__.py @@ -7,7 +7,7 @@ from flask import request, session from CTFd.cache import cache from CTFd.constants.users import UserAttrs from CTFd.constants.teams import TeamAttrs -from CTFd.models import Fails, Users, db, Teams +from CTFd.models import Fails, Users, db, Teams, Tracking from CTFd.utils import get_config @@ -120,6 +120,23 @@ def get_ip(req=None): return remote_addr +def get_current_user_ips(): + if authed(): + return get_user_ips(user_id=session["id"]) + else: + return None + + +@cache.memoize(timeout=60) +def get_user_ips(user_id): + addrs = ( + Tracking.query.with_entities(Tracking.ip.distinct()) + .filter_by(user_id=user_id) + .all() + ) + return [ip for ip, in addrs] + + def get_wrong_submissions_per_minute(account_id): """ Get incorrect submissions per minute.