mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-12-28 16:12:13 +00:00
support avatar upload;
use middleware to operate session data.
This commit is contained in:
parent
1ee0596a3a
commit
034ad59f2e
@ -1 +0,0 @@
|
|||||||
default_app_config = "account.apps.ProfilesConfig"
|
|
@ -1,9 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ProfilesConfig(AppConfig):
|
|
||||||
name = "account"
|
|
||||||
verbose_name = "account"
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import account.signals
|
|
@ -12,16 +12,31 @@ from utils.api import JSONResponse
|
|||||||
|
|
||||||
class SessionSecurityMiddleware(MiddlewareMixin):
|
class SessionSecurityMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
if request.user.is_authenticated() and request.user.is_admin_role():
|
if request.user.is_authenticated():
|
||||||
if "last_activity" in request.session:
|
if "last_activity" in request.session and request.user.is_admin_role():
|
||||||
# 24 hours passed since last visit
|
# 24 hours passed since last visit, 86400 = 24 * 60 * 60
|
||||||
if time.time() - request.session["last_activity"] >= 24 * 60 * 60:
|
if time.time() - request.session["last_activity"] >= 86400:
|
||||||
auth.logout(request)
|
auth.logout(request)
|
||||||
return JSONResponse.response({"error": "login-required", "data": _("Please login in first")})
|
return JSONResponse.response({"error": "login-required", "data": _("Please login in first")})
|
||||||
# update last active time
|
|
||||||
request.session["last_activity"] = time.time()
|
request.session["last_activity"] = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
class SessionRecordMiddleware(MiddlewareMixin):
|
||||||
|
def process_request(self, request):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
session = request.session
|
||||||
|
ip = request.META.get("REMOTE_ADDR", "")
|
||||||
|
user_agent = request.META.get("HTTP_USER_AGENT", "")
|
||||||
|
_ip = session.setdefault("ip", ip)
|
||||||
|
_user_agent = session.setdefault("user_agent", user_agent)
|
||||||
|
if ip != _ip or user_agent != _user_agent:
|
||||||
|
session.modified = True
|
||||||
|
user_sessions = request.user.session_keys
|
||||||
|
if request.session.session_key not in user_sessions:
|
||||||
|
user_sessions.append(session.session_key)
|
||||||
|
request.user.save()
|
||||||
|
|
||||||
|
|
||||||
class AdminRoleRequiredMiddleware(MiddlewareMixin):
|
class AdminRoleRequiredMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
path = request.path_info
|
path = request.path_info
|
||||||
|
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('problems_status', jsonfield.fields.JSONField(default={})),
|
('problems_status', jsonfield.fields.JSONField(default={})),
|
||||||
('avatar', models.CharField(default=account.models._random_avatar, max_length=50)),
|
('avatar', models.CharField(default=account.models._default_avatar, max_length=50)),
|
||||||
('blog', models.URLField(blank=True, null=True)),
|
('blog', models.URLField(blank=True, null=True)),
|
||||||
('mood', models.CharField(blank=True, max_length=200, null=True)),
|
('mood', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
('accepted_problem_number', models.IntegerField(default=0)),
|
('accepted_problem_number', models.IntegerField(default=0)),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.contrib.auth.models import AbstractBaseUser
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
|
|
||||||
@ -62,9 +63,8 @@ class User(AbstractBaseUser):
|
|||||||
db_table = "user"
|
db_table = "user"
|
||||||
|
|
||||||
|
|
||||||
def _random_avatar():
|
def _default_avatar():
|
||||||
import random
|
return f"/{settings.IMAGE_UPLOAD_DIR}/default.png"
|
||||||
return "/static/img/avatar/avatar-" + str(random.randint(1, 20)) + ".png"
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
@ -76,7 +76,7 @@ class UserProfile(models.Model):
|
|||||||
oi_problems_status = JSONField(default={})
|
oi_problems_status = JSONField(default={})
|
||||||
|
|
||||||
real_name = models.CharField(max_length=30, blank=True, null=True)
|
real_name = models.CharField(max_length=30, blank=True, null=True)
|
||||||
avatar = models.CharField(max_length=50, default=_random_avatar)
|
avatar = models.CharField(max_length=50, default=_default_avatar)
|
||||||
blog = models.URLField(blank=True, null=True)
|
blog = models.URLField(blank=True, null=True)
|
||||||
mood = models.CharField(max_length=200, blank=True, null=True)
|
mood = models.CharField(max_length=200, blank=True, null=True)
|
||||||
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
||||||
|
@ -70,13 +70,13 @@ class EditUserSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
|
|
||||||
class EditUserProfileSerializer(serializers.Serializer):
|
class EditUserProfileSerializer(serializers.Serializer):
|
||||||
real_name = serializers.CharField(max_length=30)
|
real_name = serializers.CharField(max_length=30, allow_blank=True)
|
||||||
avatar = serializers.CharField(max_length=100, allow_null=True, required=False)
|
avatar = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
||||||
blog = serializers.URLField(allow_null=True, required=False)
|
blog = serializers.URLField(allow_blank=True, required=False)
|
||||||
mood = serializers.CharField(max_length=200, allow_null=True, required=False)
|
mood = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||||
phone_number = serializers.CharField(max_length=15, allow_null=True, required=False, )
|
phone_number = serializers.CharField(max_length=15, allow_blank=True, required=False, )
|
||||||
school = serializers.CharField(max_length=200, allow_null=True, required=False)
|
school = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||||
major = serializers.CharField(max_length=200, allow_null=True, required=False)
|
major = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||||
|
|
||||||
|
|
||||||
class ApplyResetPasswordSerializer(serializers.Serializer):
|
class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
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()
|
|
@ -182,6 +182,9 @@ class SessionManagementAPITest(APITestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_user("test", "test123")
|
self.create_user("test", "test123")
|
||||||
self.url = self.reverse("session_management_api")
|
self.url = self.reverse("session_management_api")
|
||||||
|
# launch a request to provide session data
|
||||||
|
login_url = self.reverse("user_login_api")
|
||||||
|
self.client.post(login_url, data={"username": "test", "password": "test123"})
|
||||||
|
|
||||||
def test_get_sessions(self):
|
def test_get_sessions(self):
|
||||||
resp = self.client.get(self.url)
|
resp = self.client.get(self.url)
|
||||||
@ -189,10 +192,9 @@ class SessionManagementAPITest(APITestCase):
|
|||||||
data = resp.data["data"]
|
data = resp.data["data"]
|
||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
|
|
||||||
def test_delete_session_key(self):
|
# def test_delete_session_key(self):
|
||||||
# resp = self.client.delete(self.url, data={"session_key": self.client.session.session_key})
|
# resp = self.client.delete(self.url + "?session_key=" + self.session_key)
|
||||||
resp = self.client.delete(self.url + "?session_key=" + self.client.session.session_key)
|
# self.assertSuccess(resp)
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_delete_session_with_invalid_key(self):
|
def test_delete_session_with_invalid_key(self):
|
||||||
resp = self.client.delete(self.url + "?session_key=aaaaaaaaaa")
|
resp = self.client.delete(self.url + "?session_key=aaaaaaaaaa")
|
||||||
|
@ -18,7 +18,7 @@ urlpatterns = [
|
|||||||
url(r"^captcha/?$", CaptchaAPIView.as_view(), name="show_captcha"),
|
url(r"^captcha/?$", CaptchaAPIView.as_view(), name="show_captcha"),
|
||||||
url(r"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email"),
|
url(r"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email"),
|
||||||
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||||
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
url(r"^upload_avatar/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
||||||
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
|
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
|
||||||
url(r"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
|
url(r"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
|
||||||
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
|
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
|
||||||
|
@ -12,9 +12,9 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from conf.models import WebsiteConfig
|
from conf.models import WebsiteConfig
|
||||||
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
|
from utils.api import APIView, validate_serializer
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
from utils.shortcuts import rand_str, img2base64, datetime2str
|
from utils.shortcuts import rand_str, img2base64, timestamp2utcstr
|
||||||
|
|
||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import User, UserProfile
|
from ..models import User, UserProfile
|
||||||
@ -59,7 +59,7 @@ class UserProfileAPI(APIView):
|
|||||||
return self.success(UserProfileSerializer(user_profile).data)
|
return self.success(UserProfileSerializer(user_profile).data)
|
||||||
|
|
||||||
|
|
||||||
class AvatarUploadAPI(CSRFExemptAPIView):
|
class AvatarUploadAPI(APIView):
|
||||||
request_parsers = ()
|
request_parsers = ()
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@ -67,17 +67,26 @@ class AvatarUploadAPI(CSRFExemptAPIView):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
avatar = form.cleaned_data["file"]
|
avatar = form.cleaned_data["file"]
|
||||||
else:
|
else:
|
||||||
return self.error("Upload failed")
|
return self.error("Invalid file content")
|
||||||
if avatar.size > 1024 * 1024:
|
# 2097152 = 2 * 1024 * 1024 = 2MB
|
||||||
return self.error("Picture too large")
|
if avatar.size > 2097152:
|
||||||
if os.path.splitext(avatar.name)[-1].lower() not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
|
return self.error("Picture is too large")
|
||||||
|
suffix = os.path.splitext(avatar.name)[-1].lower()
|
||||||
|
if suffix not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
|
||||||
return self.error("Unsupported file format")
|
return self.error("Unsupported file format")
|
||||||
|
|
||||||
name = "avatar_" + rand_str(5) + os.path.splitext(avatar.name)[-1]
|
name = rand_str(10) + suffix
|
||||||
with open(os.path.join(settings.IMAGE_UPLOAD_DIR, name), "wb") as img:
|
with open(os.path.join(settings.IMAGE_UPLOAD_DIR_ABS, name), "wb") as img:
|
||||||
for chunk in avatar:
|
for chunk in avatar:
|
||||||
img.write(chunk)
|
img.write(chunk)
|
||||||
return self.success({"path": "/static/upload/" + name})
|
user_profile = request.user.userprofile
|
||||||
|
_, old_avatar = os.path.split(user_profile.avatar)
|
||||||
|
if old_avatar != "default.png":
|
||||||
|
os.remove(os.path.join(settings.IMAGE_UPLOAD_DIR_ABS, old_avatar))
|
||||||
|
|
||||||
|
user_profile.avatar = f"/{settings.IMAGE_UPLOAD_DIR}/{name}"
|
||||||
|
user_profile.save()
|
||||||
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
class SSOAPI(APIView):
|
class SSOAPI(APIView):
|
||||||
@ -333,6 +342,7 @@ class SessionManagementAPI(APIView):
|
|||||||
engine = import_module(settings.SESSION_ENGINE)
|
engine = import_module(settings.SESSION_ENGINE)
|
||||||
SessionStore = engine.SessionStore
|
SessionStore = engine.SessionStore
|
||||||
current_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
current_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
||||||
|
current_session = request.session.session_key
|
||||||
session_keys = request.user.session_keys
|
session_keys = request.user.session_keys
|
||||||
result = []
|
result = []
|
||||||
modified = False
|
modified = False
|
||||||
@ -349,7 +359,7 @@ class SessionManagementAPI(APIView):
|
|||||||
s["current_session"] = True
|
s["current_session"] = True
|
||||||
s["ip"] = session["ip"]
|
s["ip"] = session["ip"]
|
||||||
s["user_agent"] = session["user_agent"]
|
s["user_agent"] = session["user_agent"]
|
||||||
s["last_login"] = datetime2str(session["last_login"])
|
s["last_activity"] = timestamp2utcstr(session["last_activity"])
|
||||||
s["session_key"] = key
|
s["session_key"] = key
|
||||||
result.append(s)
|
result.append(s)
|
||||||
if modified:
|
if modified:
|
||||||
|
@ -24,4 +24,8 @@ ALLOWED_HOSTS = ["*"]
|
|||||||
|
|
||||||
TEST_CASE_DIR = "/tmp"
|
TEST_CASE_DIR = "/tmp"
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, "static"),
|
||||||
|
]
|
||||||
|
|
||||||
LOG_PATH = "log/"
|
LOG_PATH = "log/"
|
||||||
|
@ -58,6 +58,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'account.middleware.AdminRoleRequiredMiddleware',
|
'account.middleware.AdminRoleRequiredMiddleware',
|
||||||
'account.middleware.SessionSecurityMiddleware',
|
'account.middleware.SessionSecurityMiddleware',
|
||||||
|
'account.middleware.SessionRecordMiddleware',
|
||||||
# 'account.middleware.LogSqlMiddleware',
|
# 'account.middleware.LogSqlMiddleware',
|
||||||
)
|
)
|
||||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||||
@ -213,7 +214,8 @@ BROKER_URL = 'redis://%s:%s/%s' % (REDIS_QUEUE["host"], str(REDIS_QUEUE["port"])
|
|||||||
CELERY_ACCEPT_CONTENT = ["json"]
|
CELERY_ACCEPT_CONTENT = ["json"]
|
||||||
CELERY_TASK_SERIALIZER = "json"
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
|
|
||||||
IMAGE_UPLOAD_DIR = os.path.join(BASE_DIR, 'upload/')
|
IMAGE_UPLOAD_DIR = 'static/avatar'
|
||||||
|
IMAGE_UPLOAD_DIR_ABS = os.path.join(BASE_DIR, IMAGE_UPLOAD_DIR)
|
||||||
|
|
||||||
# 用于限制用户恶意提交大量代码
|
# 用于限制用户恶意提交大量代码
|
||||||
TOKEN_BUCKET_DEFAULT_CAPACITY = 50
|
TOKEN_BUCKET_DEFAULT_CAPACITY = 50
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
@ -78,3 +79,7 @@ def datetime2str(value, format="iso-8601"):
|
|||||||
value = value[:-6] + "Z"
|
value = value[:-6] + "Z"
|
||||||
return value
|
return value
|
||||||
return value.strftime(format)
|
return value.strftime(format)
|
||||||
|
|
||||||
|
|
||||||
|
def timestamp2utcstr(value):
|
||||||
|
return datetime.datetime.utcfromtimestamp(value).isoformat()
|
||||||
|
Loading…
Reference in New Issue
Block a user