Use signals to save ip, user_agent, last_login in sessions

This commit is contained in:
zema1 2017-09-13 22:37:57 +08:00
parent f55a242ec0
commit a3ca8b2336
16 changed files with 74 additions and 19 deletions

View File

@ -1,6 +1,6 @@
language: python language: python
python: python:
- "3.5" - "3.6"
install: install:
- sudo apt-get install -qq redis-server && redis-server & - sudo apt-get install -qq redis-server && redis-server &
- pip install -r deploy/requirements.txt - pip install -r deploy/requirements.txt

View File

@ -0,0 +1 @@
default_app_config = 'account.apps.ProfilesConfig'

9
account/apps.py Normal file
View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class ProfilesConfig(AppConfig):
name = "account"
verbose_name = "account"
def ready(self):
import account.signals

View File

@ -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=[]),
),
]

View File

@ -35,6 +35,7 @@ class User(AbstractBaseUser):
auth_token = models.CharField(max_length=40, null=True) auth_token = models.CharField(max_length=40, null=True)
two_factor_auth = models.BooleanField(default=False) two_factor_auth = models.BooleanField(default=False)
tfa_token = models.CharField(max_length=40, null=True) tfa_token = models.CharField(max_length=40, null=True)
session_keys = JSONField(default=[])
# open api key # open api key
open_api = models.BooleanField(default=False) open_api = models.BooleanField(default=False)
open_api_appkey = models.CharField(max_length=35, null=True) open_api_appkey = models.CharField(max_length=35, null=True)

21
account/signals.py Normal file
View File

@ -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()

View File

@ -194,7 +194,6 @@ class AdminUserTest(APITestCase):
resp_data = response.data["data"] resp_data = response.data["data"]
self.assertEqual(resp_data["username"], self.username) self.assertEqual(resp_data["username"], self.username)
self.assertEqual(resp_data["email"], "test@qq.com") 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["open_api"], True)
self.assertEqual(resp_data["two_factor_auth"], False) self.assertEqual(resp_data["two_factor_auth"], False)
self.assertEqual(resp_data["is_disabled"], False) self.assertEqual(resp_data["is_disabled"], False)

View File

@ -39,7 +39,6 @@ class UserAdminAPI(APIView):
pass pass
user.username = data["username"] user.username = data["username"]
user.real_name = data["real_name"]
user.email = data["email"] user.email = data["email"]
user.admin_type = data["admin_type"] user.admin_type = data["admin_type"]
user.is_disabled = data["is_disabled"] user.is_disabled = data["is_disabled"]

View File

@ -5,6 +5,7 @@ from otpauth import OtpAuth
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from importlib import import_module
from django.utils.timezone import now from django.utils.timezone import now
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -267,8 +268,8 @@ class ApplyResetPasswordAPI(APIView):
user = User.objects.get(email=data["email"]) user = User.objects.get(email=data["email"])
except User.DoesNotExist: except User.DoesNotExist:
return self.error("User does not exist") return self.error("User does not exist")
if user.reset_password_token_expire_time and \ if user.reset_password_token_expire_time and 0 < int(
0 < int((user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60: (user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60:
return self.error("You can only reset password once per 20 minutes") return self.error("You can only reset password once per 20 minutes")
user.reset_password_token = rand_str() user.reset_password_token = rand_str()
user.reset_password_token_expire_time = now() + timedelta(minutes=20) user.reset_password_token_expire_time = now() + timedelta(minutes=20)
@ -278,7 +279,7 @@ class ApplyResetPasswordAPI(APIView):
"website_name": config.name, "website_name": config.name,
"link": f"{config.base_url}/reset-password/{user.reset_password_token}" "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, send_email_async.delay(config.name,
user.email, user.email,
user.username, user.username,

View File

@ -33,10 +33,11 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.contenttypes',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework',
'account', 'account',
'announcement', 'announcement',
@ -45,8 +46,6 @@ INSTALLED_APPS = (
'contest', 'contest',
'utils', 'utils',
'submission', 'submission',
'rest_framework',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -54,14 +53,14 @@ MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'account.middleware.AdminRoleRequiredMiddleware', 'account.middleware.AdminRoleRequiredMiddleware',
'account.middleware.SessionSecurityMiddleware', 'account.middleware.SessionSecurityMiddleware',
# 'account.middleware.LogSqlMiddleware',
) )
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
ROOT_URLCONF = 'oj.urls' ROOT_URLCONF = 'oj.urls'
TEMPLATES = [ TEMPLATES = [

View File

@ -147,7 +147,7 @@ class ProblemAPITest(APITestCase):
def test_get_problem_list(self): def test_get_problem_list(self):
self.create_problem() self.create_problem()
resp = self.client.get(self.url) resp = self.client.get(f"{self.url}?limit=10")
self.assertSuccess(resp) self.assertSuccess(resp)
def get_one_problem(self): def get_one_problem(self):

View File

@ -6,6 +6,7 @@ from ..serializers import ProblemSerializer, TagSerializer
from ..serializers import ContestProblemSerializer from ..serializers import ContestProblemSerializer
from contest.models import ContestRuleType from contest.models import ContestRuleType
class ProblemTagAPI(APIView): class ProblemTagAPI(APIView):
def get(self, request): def get(self, request):
return self.success(TagSerializer(ProblemTag.objects.all(), many=True).data) return self.success(TagSerializer(ProblemTag.objects.all(), many=True).data)
@ -22,6 +23,10 @@ class ProblemAPI(APIView):
except Problem.DoesNotExist: except Problem.DoesNotExist:
return self.error("Problem does not exist") 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) problems = Problem.objects.select_related("created_by").filter(visible=True)
# 按照标签筛选 # 按照标签筛选
tag_text = request.GET.get("tag") tag_text = request.GET.get("tag")
@ -61,12 +66,14 @@ class ContestProblemAPI(APIView):
problem_id = request.GET.get("problem_id") problem_id = request.GET.get("problem_id")
if problem_id: if problem_id:
try: 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: except ContestProblem.DoesNotExist:
return self.error("Problem does not exist.") return self.error("Problem does not exist.")
return self.success(ContestProblemSerializer(problem).data) 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 为做过的题目添加标记 # 根据profile 为做过的题目添加标记
data = ContestProblemSerializer(contest_problems, many=True).data data = ContestProblemSerializer(contest_problems, many=True).data
if request.user.id: if request.user.id:

View File

@ -1,7 +1,7 @@
from account.decorators import login_required, check_contest_permission from account.decorators import login_required, check_contest_permission
from judge.tasks import judge_task from judge.tasks import judge_task
from judge.dispatcher import JudgeDispatcher # from judge.dispatcher import JudgeDispatcher
from problem.models import Problem, ProblemRuleType, ContestProblem from problem.models import Problem, ProblemRuleType, ContestProblem
from contest.models import Contest, ContestStatus from contest.models import Contest, ContestStatus
from utils.api import APIView, validate_serializer from utils.api import APIView, validate_serializer

View File

@ -15,7 +15,6 @@ import os
import time import time
import random import random
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont

View File

@ -1,5 +1,3 @@
from base64 import b64encode
from . import Captcha from . import Captcha
from ..api import APIView from ..api import APIView
from ..shortcuts import img2base64 from ..shortcuts import img2base64