From a3ca8b23364a2ae3473e9796f9b9eb1ccd19e9ff Mon Sep 17 00:00:00 2001 From: zema1 Date: Wed, 13 Sep 2017 22:37:57 +0800 Subject: [PATCH] Use signals to save ip, user_agent, last_login in sessions --- .travis.yml | 2 +- account/__init__.py | 1 + account/apps.py | 9 +++++++++ account/migrations/0006_user_session_keys.py | 21 ++++++++++++++++++++ account/models.py | 1 + account/signals.py | 21 ++++++++++++++++++++ account/tests.py | 1 - account/views/admin.py | 1 - account/views/oj.py | 7 ++++--- oj/settings.py | 9 ++++----- problem/tests.py | 2 +- problem/views/oj.py | 11 ++++++++-- submission/views/oj.py | 2 +- utils/captcha/__init__.py | 1 - utils/captcha/views.py | 2 -- utils/shortcuts.py | 2 +- 16 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 account/apps.py create mode 100644 account/migrations/0006_user_session_keys.py create mode 100644 account/signals.py diff --git a/.travis.yml b/.travis.yml index 1e3f9648..b0ba8853 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python python: - - "3.5" + - "3.6" install: - sudo apt-get install -qq redis-server && redis-server & - pip install -r deploy/requirements.txt diff --git a/account/__init__.py b/account/__init__.py index e69de29b..d35b9f07 100644 --- a/account/__init__.py +++ b/account/__init__.py @@ -0,0 +1 @@ +default_app_config = 'account.apps.ProfilesConfig' \ No newline at end of file diff --git a/account/apps.py b/account/apps.py new file mode 100644 index 00000000..1336a186 --- /dev/null +++ b/account/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class ProfilesConfig(AppConfig): + name = "account" + verbose_name = "account" + + def ready(self): + import account.signals diff --git a/account/migrations/0006_user_session_keys.py b/account/migrations/0006_user_session_keys.py new file mode 100644 index 00000000..4a0282a6 --- /dev/null +++ b/account/migrations/0006_user_session_keys.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-16 06:22 +from __future__ import unicode_literals + +from django.db import migrations +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0005_auto_20170830_1154'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='session_keys', + field=jsonfield.fields.JSONField(default=[]), + ), + ] diff --git a/account/models.py b/account/models.py index a94e6a4e..1876968d 100644 --- a/account/models.py +++ b/account/models.py @@ -35,6 +35,7 @@ class User(AbstractBaseUser): auth_token = models.CharField(max_length=40, null=True) two_factor_auth = models.BooleanField(default=False) tfa_token = models.CharField(max_length=40, null=True) + session_keys = JSONField(default=[]) # open api key open_api = models.BooleanField(default=False) open_api_appkey = models.CharField(max_length=35, null=True) diff --git a/account/signals.py b/account/signals.py new file mode 100644 index 00000000..4d14970e --- /dev/null +++ b/account/signals.py @@ -0,0 +1,21 @@ +from django.utils.timezone import now +from django.dispatch import receiver +from django.contrib.auth.signals import user_logged_in, user_logged_out + + +@receiver(user_logged_in) +def add_user_session(sender, request, user, **kwargs): + request.session["ip"] = request.META.get('REMOTE_ADDR', '') + request.session["user_agent"] = request.META.get('HTTP_USER_AGENT', '') + request.session["last_login"] = now() + if request.session.session_key not in user.session_keys: + user.session_keys.append(request.session.session_key) + user.save() + + +@receiver(user_logged_out) +def delete_user_session(sender, request, user, **kwargs): + # user may be None + if user and request.session.session_key in user.session_keys: + user.session_keys.remove(request.session.session_key) + user.save() diff --git a/account/tests.py b/account/tests.py index d4addd58..e37867dd 100644 --- a/account/tests.py +++ b/account/tests.py @@ -194,7 +194,6 @@ class AdminUserTest(APITestCase): resp_data = response.data["data"] self.assertEqual(resp_data["username"], self.username) self.assertEqual(resp_data["email"], "test@qq.com") - self.assertEqual(resp_data["real_name"], "test_name") self.assertEqual(resp_data["open_api"], True) self.assertEqual(resp_data["two_factor_auth"], False) self.assertEqual(resp_data["is_disabled"], False) diff --git a/account/views/admin.py b/account/views/admin.py index 62c51158..83f4a93e 100644 --- a/account/views/admin.py +++ b/account/views/admin.py @@ -39,7 +39,6 @@ class UserAdminAPI(APIView): pass user.username = data["username"] - user.real_name = data["real_name"] user.email = data["email"] user.admin_type = data["admin_type"] user.is_disabled = data["is_disabled"] diff --git a/account/views/oj.py b/account/views/oj.py index f567c56f..bc00ea11 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -5,6 +5,7 @@ from otpauth import OtpAuth from django.conf import settings from django.contrib import auth +from importlib import import_module from django.utils.timezone import now from django.views.decorators.csrf import ensure_csrf_cookie from django.utils.decorators import method_decorator @@ -267,8 +268,8 @@ class ApplyResetPasswordAPI(APIView): user = User.objects.get(email=data["email"]) except User.DoesNotExist: return self.error("User does not exist") - if user.reset_password_token_expire_time and \ - 0 < int((user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60: + if user.reset_password_token_expire_time and 0 < int( + (user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60: return self.error("You can only reset password once per 20 minutes") user.reset_password_token = rand_str() user.reset_password_token_expire_time = now() + timedelta(minutes=20) @@ -278,7 +279,7 @@ class ApplyResetPasswordAPI(APIView): "website_name": config.name, "link": f"{config.base_url}/reset-password/{user.reset_password_token}" } - email_html = render_to_string('reset_password_email.html', render_data) + email_html = render_to_string("reset_password_email.html", render_data) send_email_async.delay(config.name, user.email, user.username, diff --git a/oj/settings.py b/oj/settings.py index 1d4983d4..78519142 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -33,10 +33,11 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) INSTALLED_APPS = ( 'django.contrib.auth', - 'django.contrib.contenttypes', 'django.contrib.sessions', + 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', 'account', 'announcement', @@ -45,8 +46,6 @@ INSTALLED_APPS = ( 'contest', 'utils', 'submission', - - 'rest_framework', ) MIDDLEWARE_CLASSES = ( @@ -54,14 +53,14 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', 'account.middleware.AdminRoleRequiredMiddleware', 'account.middleware.SessionSecurityMiddleware', + # 'account.middleware.LogSqlMiddleware', ) - +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' ROOT_URLCONF = 'oj.urls' TEMPLATES = [ diff --git a/problem/tests.py b/problem/tests.py index 3bdc79c9..79791274 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -147,7 +147,7 @@ class ProblemAPITest(APITestCase): def test_get_problem_list(self): self.create_problem() - resp = self.client.get(self.url) + resp = self.client.get(f"{self.url}?limit=10") self.assertSuccess(resp) def get_one_problem(self): diff --git a/problem/views/oj.py b/problem/views/oj.py index 5d3f2fbe..40fb0212 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -6,6 +6,7 @@ from ..serializers import ProblemSerializer, TagSerializer from ..serializers import ContestProblemSerializer from contest.models import ContestRuleType + class ProblemTagAPI(APIView): def get(self, request): return self.success(TagSerializer(ProblemTag.objects.all(), many=True).data) @@ -22,6 +23,10 @@ class ProblemAPI(APIView): except Problem.DoesNotExist: return self.error("Problem does not exist") + limit = request.GET.get("limit") + if not limit: + return self.error("Limit is needed") + problems = Problem.objects.select_related("created_by").filter(visible=True) # 按照标签筛选 tag_text = request.GET.get("tag") @@ -61,12 +66,14 @@ class ContestProblemAPI(APIView): problem_id = request.GET.get("problem_id") if problem_id: try: - problem = ContestProblem.objects.select_related("created_by").get(_id=problem_id, contest=self.contest, visible=True) + problem = ContestProblem.objects.select_related("created_by").get(_id=problem_id, contest=self.contest, + visible=True) except ContestProblem.DoesNotExist: return self.error("Problem does not exist.") return self.success(ContestProblemSerializer(problem).data) - contest_problems = ContestProblem.objects.select_related("created_by").filter(contest=self.contest, visible=True) + contest_problems = ContestProblem.objects.select_related("created_by").filter(contest=self.contest, + visible=True) # 根据profile, 为做过的题目添加标记 data = ContestProblemSerializer(contest_problems, many=True).data if request.user.id: diff --git a/submission/views/oj.py b/submission/views/oj.py index d6e88e03..f4d276d4 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -1,7 +1,7 @@ from account.decorators import login_required, check_contest_permission from judge.tasks import judge_task -from judge.dispatcher import JudgeDispatcher +# from judge.dispatcher import JudgeDispatcher from problem.models import Problem, ProblemRuleType, ContestProblem from contest.models import Contest, ContestStatus from utils.api import APIView, validate_serializer diff --git a/utils/captcha/__init__.py b/utils/captcha/__init__.py index e645e461..8b8375cd 100644 --- a/utils/captcha/__init__.py +++ b/utils/captcha/__init__.py @@ -15,7 +15,6 @@ import os import time import random -from io import BytesIO from PIL import Image, ImageDraw, ImageFont diff --git a/utils/captcha/views.py b/utils/captcha/views.py index 1c6c654f..4642e32f 100644 --- a/utils/captcha/views.py +++ b/utils/captcha/views.py @@ -1,5 +1,3 @@ -from base64 import b64encode - from . import Captcha from ..api import APIView from ..shortcuts import img2base64 diff --git a/utils/shortcuts.py b/utils/shortcuts.py index 634579ab..e2cc4707 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -68,4 +68,4 @@ def img2base64(img): buf_str = buf.getvalue() img_prefix = "data:image/png;base64," b64_str = img_prefix + b64encode(buf_str).decode("utf-8") - return b64_str \ No newline at end of file + return b64_str