mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-12-27 23:51:47 +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):
|
||||
def process_request(self, request):
|
||||
if request.user.is_authenticated() and request.user.is_admin_role():
|
||||
if "last_activity" in request.session:
|
||||
# 24 hours passed since last visit
|
||||
if time.time() - request.session["last_activity"] >= 24 * 60 * 60:
|
||||
if request.user.is_authenticated():
|
||||
if "last_activity" in request.session and request.user.is_admin_role():
|
||||
# 24 hours passed since last visit, 86400 = 24 * 60 * 60
|
||||
if time.time() - request.session["last_activity"] >= 86400:
|
||||
auth.logout(request)
|
||||
return JSONResponse.response({"error": "login-required", "data": _("Please login in first")})
|
||||
# update last active 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):
|
||||
def process_request(self, request):
|
||||
path = request.path_info
|
||||
|
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('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)),
|
||||
('mood', models.CharField(blank=True, max_length=200, null=True)),
|
||||
('accepted_problem_number', models.IntegerField(default=0)),
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from jsonfield import JSONField
|
||||
|
||||
@ -62,9 +63,8 @@ class User(AbstractBaseUser):
|
||||
db_table = "user"
|
||||
|
||||
|
||||
def _random_avatar():
|
||||
import random
|
||||
return "/static/img/avatar/avatar-" + str(random.randint(1, 20)) + ".png"
|
||||
def _default_avatar():
|
||||
return f"/{settings.IMAGE_UPLOAD_DIR}/default.png"
|
||||
|
||||
|
||||
class UserProfile(models.Model):
|
||||
@ -76,7 +76,7 @@ class UserProfile(models.Model):
|
||||
oi_problems_status = JSONField(default={})
|
||||
|
||||
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)
|
||||
mood = models.CharField(max_length=200, 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):
|
||||
real_name = serializers.CharField(max_length=30)
|
||||
avatar = serializers.CharField(max_length=100, allow_null=True, required=False)
|
||||
blog = serializers.URLField(allow_null=True, required=False)
|
||||
mood = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||
phone_number = serializers.CharField(max_length=15, allow_null=True, required=False, )
|
||||
school = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||
major = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||
real_name = serializers.CharField(max_length=30, allow_blank=True)
|
||||
avatar = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
||||
blog = serializers.URLField(allow_blank=True, required=False)
|
||||
mood = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||
phone_number = serializers.CharField(max_length=15, allow_blank=True, required=False, )
|
||||
school = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||
major = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||
|
||||
|
||||
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):
|
||||
self.create_user("test", "test123")
|
||||
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):
|
||||
resp = self.client.get(self.url)
|
||||
@ -189,10 +192,9 @@ class SessionManagementAPITest(APITestCase):
|
||||
data = resp.data["data"]
|
||||
self.assertEqual(len(data), 1)
|
||||
|
||||
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.client.session.session_key)
|
||||
self.assertSuccess(resp)
|
||||
# def test_delete_session_key(self):
|
||||
# resp = self.client.delete(self.url + "?session_key=" + self.session_key)
|
||||
# self.assertSuccess(resp)
|
||||
|
||||
def test_delete_session_with_invalid_key(self):
|
||||
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"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email"),
|
||||
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"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
|
||||
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 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.shortcuts import rand_str, img2base64, datetime2str
|
||||
from utils.shortcuts import rand_str, img2base64, timestamp2utcstr
|
||||
|
||||
from ..decorators import login_required
|
||||
from ..models import User, UserProfile
|
||||
@ -59,7 +59,7 @@ class UserProfileAPI(APIView):
|
||||
return self.success(UserProfileSerializer(user_profile).data)
|
||||
|
||||
|
||||
class AvatarUploadAPI(CSRFExemptAPIView):
|
||||
class AvatarUploadAPI(APIView):
|
||||
request_parsers = ()
|
||||
|
||||
def post(self, request):
|
||||
@ -67,17 +67,26 @@ class AvatarUploadAPI(CSRFExemptAPIView):
|
||||
if form.is_valid():
|
||||
avatar = form.cleaned_data["file"]
|
||||
else:
|
||||
return self.error("Upload failed")
|
||||
if avatar.size > 1024 * 1024:
|
||||
return self.error("Picture too large")
|
||||
if os.path.splitext(avatar.name)[-1].lower() not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
|
||||
return self.error("Invalid file content")
|
||||
# 2097152 = 2 * 1024 * 1024 = 2MB
|
||||
if avatar.size > 2097152:
|
||||
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")
|
||||
|
||||
name = "avatar_" + rand_str(5) + os.path.splitext(avatar.name)[-1]
|
||||
with open(os.path.join(settings.IMAGE_UPLOAD_DIR, name), "wb") as img:
|
||||
name = rand_str(10) + suffix
|
||||
with open(os.path.join(settings.IMAGE_UPLOAD_DIR_ABS, name), "wb") as img:
|
||||
for chunk in avatar:
|
||||
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):
|
||||
@ -333,6 +342,7 @@ class SessionManagementAPI(APIView):
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
SessionStore = engine.SessionStore
|
||||
current_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
||||
current_session = request.session.session_key
|
||||
session_keys = request.user.session_keys
|
||||
result = []
|
||||
modified = False
|
||||
@ -349,7 +359,7 @@ class SessionManagementAPI(APIView):
|
||||
s["current_session"] = True
|
||||
s["ip"] = session["ip"]
|
||||
s["user_agent"] = session["user_agent"]
|
||||
s["last_login"] = datetime2str(session["last_login"])
|
||||
s["last_activity"] = timestamp2utcstr(session["last_activity"])
|
||||
s["session_key"] = key
|
||||
result.append(s)
|
||||
if modified:
|
||||
|
@ -24,4 +24,8 @@ ALLOWED_HOSTS = ["*"]
|
||||
|
||||
TEST_CASE_DIR = "/tmp"
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
|
||||
LOG_PATH = "log/"
|
||||
|
@ -58,6 +58,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'account.middleware.AdminRoleRequiredMiddleware',
|
||||
'account.middleware.SessionSecurityMiddleware',
|
||||
'account.middleware.SessionRecordMiddleware',
|
||||
# 'account.middleware.LogSqlMiddleware',
|
||||
)
|
||||
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_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
|
||||
|
@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import random
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
from base64 import b64encode
|
||||
|
||||
@ -78,3 +79,7 @@ def datetime2str(value, format="iso-8601"):
|
||||
value = value[:-6] + "Z"
|
||||
return value
|
||||
return value.strftime(format)
|
||||
|
||||
|
||||
def timestamp2utcstr(value):
|
||||
return datetime.datetime.utcfromtimestamp(value).isoformat()
|
||||
|
Loading…
Reference in New Issue
Block a user