This commit is contained in:
virusdefender 2017-01-23 16:01:56 +08:00
parent 0f03e75713
commit 3e42e6648e
24 changed files with 58 additions and 511 deletions

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-25 05:30 # Generated by Django 1.9.12 on 2017-01-23 07:59
from __future__ import unicode_literals from __future__ import unicode_literals
import account.models import account.models
@ -24,19 +24,17 @@ class Migration(migrations.Migration):
('password', models.CharField(max_length=128, verbose_name='password')), ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('username', models.CharField(max_length=30, unique=True)), ('username', models.CharField(max_length=30, unique=True)),
('real_name', models.CharField(blank=True, max_length=30, null=True)), ('real_name', models.CharField(max_length=30, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)), ('email', models.EmailField(max_length=254, null=True)),
('create_time', models.DateTimeField(auto_now_add=True, null=True)), ('create_time', models.DateTimeField(auto_now_add=True, null=True)),
('admin_type', models.IntegerField(default=0)), ('admin_type', models.CharField(default='regular_user', max_length=24)),
('admin_extra_permission', jsonfield.fields.JSONField(default=[])), ('reset_password_token', models.CharField(max_length=40, null=True)),
('problems_status', jsonfield.fields.JSONField(default={})), ('reset_password_token_expire_time', models.DateTimeField(null=True)),
('reset_password_token', models.CharField(blank=True, max_length=40, null=True)), ('auth_token', models.CharField(max_length=40, null=True)),
('reset_password_token_create_time', models.DateTimeField(blank=True, null=True)),
('auth_token', models.CharField(blank=True, max_length=40, null=True)),
('two_factor_auth', models.BooleanField(default=False)), ('two_factor_auth', models.BooleanField(default=False)),
('tfa_token', models.CharField(blank=True, max_length=40, null=True)), ('tfa_token', models.CharField(max_length=40, null=True)),
('open_api', models.BooleanField(default=False)), ('open_api', models.BooleanField(default=False)),
('open_api_appkey', models.CharField(blank=True, max_length=35, null=True)), ('open_api_appkey', models.CharField(max_length=35, null=True)),
('is_disabled', models.BooleanField(default=False)), ('is_disabled', models.BooleanField(default=False)),
], ],
options={ options={
@ -50,17 +48,18 @@ class Migration(migrations.Migration):
name='UserProfile', name='UserProfile',
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={})),
('avatar', models.CharField(default=account.models._random_avatar, max_length=50)), ('avatar', models.CharField(default=account.models._random_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)),
('hduoj_username', models.CharField(blank=True, max_length=30, null=True)),
('bestcoder_username', models.CharField(blank=True, max_length=30, null=True)),
('codeforces_username', models.CharField(blank=True, max_length=30, null=True)),
('accepted_problem_number', models.IntegerField(default=0)), ('accepted_problem_number', models.IntegerField(default=0)),
('submission_number', models.IntegerField(default=0)), ('submission_number', models.IntegerField(default=0)),
('phone_number', models.CharField(blank=True, max_length=15, null=True)), ('phone_number', models.CharField(blank=True, max_length=15, null=True)),
('school', models.CharField(blank=True, max_length=200, null=True)), ('school', models.CharField(blank=True, max_length=200, null=True)),
('major', models.CharField(blank=True, max_length=200, null=True)),
('student_id', models.CharField(blank=True, max_length=15, null=True)), ('student_id', models.CharField(blank=True, max_length=15, null=True)),
('time_zone', models.CharField(blank=True, max_length=32, null=True)),
('language', models.CharField(blank=True, max_length=32, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],
options={ options={

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-25 05:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='real_name',
),
migrations.RemoveField(
model_name='userprofile',
name='bestcoder_username',
),
migrations.RemoveField(
model_name='userprofile',
name='codeforces_username',
),
migrations.RemoveField(
model_name='userprofile',
name='hduoj_username',
),
migrations.AddField(
model_name='userprofile',
name='real_name',
field=models.CharField(blank=True, max_length=30, null=True),
),
]

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-25 06:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0002_auto_20160925_1337'),
]
operations = [
migrations.RemoveField(
model_name='userprofile',
name='real_name',
),
migrations.AddField(
model_name='user',
name='real_name',
field=models.CharField(blank=True, max_length=30, null=True),
),
]

View File

@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-25 08:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0003_auto_20160925_1402'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='language',
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.AddField(
model_name='userprofile',
name='major',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='userprofile',
name='time_zone',
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.AlterField(
model_name='user',
name='auth_token',
field=models.CharField(max_length=40, null=True),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(max_length=254, null=True),
),
migrations.AlterField(
model_name='user',
name='open_api_appkey',
field=models.CharField(max_length=35, null=True),
),
migrations.AlterField(
model_name='user',
name='real_name',
field=models.CharField(max_length=30, null=True),
),
migrations.AlterField(
model_name='user',
name='reset_password_token',
field=models.CharField(max_length=40, null=True),
),
migrations.AlterField(
model_name='user',
name='reset_password_token_create_time',
field=models.DateTimeField(null=True),
),
migrations.AlterField(
model_name='user',
name='tfa_token',
field=models.CharField(max_length=40, null=True),
),
]

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-10-29 14:55
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('account', '0004_auto_20160925_1649'),
]
operations = [
migrations.RenameField(
model_name='user',
old_name='reset_password_token_create_time',
new_name='reset_password_token_expire_time',
),
migrations.RemoveField(
model_name='user',
name='admin_extra_permission',
),
migrations.RemoveField(
model_name='user',
name='problems_status',
),
migrations.AddField(
model_name='userprofile',
name='problems_status',
field=jsonfield.fields.JSONField(default={}),
),
migrations.AlterField(
model_name='user',
name='admin_type',
field=models.CharField(default='regular_user', max_length=24),
),
]

View File

@ -39,7 +39,7 @@ class User(AbstractBaseUser):
open_api_appkey = models.CharField(max_length=35, null=True) open_api_appkey = models.CharField(max_length=35, null=True)
is_disabled = models.BooleanField(default=False) is_disabled = models.BooleanField(default=False)
USERNAME_FIELD = 'username' USERNAME_FIELD = "username"
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []
objects = UserManager() objects = UserManager()

View File

@ -84,12 +84,6 @@ class UserAdminAPI(APIView):
user = User.objects.all().order_by("-create_time") user = User.objects.all().order_by("-create_time")
admin_type = request.GET.get("admin_type", None)
if admin_type:
try:
user = user.filter(admin_type__gte=int(admin_type))
except ValueError:
return self.error(_("Invalid parameter"))
keyword = request.GET.get("keyword", None) keyword = request.GET.get("keyword", None)
if keyword: if keyword:
user = user.filter(Q(username__contains=keyword) | user = user.filter(Q(username__contains=keyword) |

View File

@ -2,9 +2,10 @@ from django.contrib import auth
from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from otpauth import OtpAuth
from utils.api import APIView, validate_serializer from utils.api import APIView, validate_serializer
from utils.captcha import Captcha from utils.captcha import Captcha
from utils.otp_auth import OtpAuth
from ..decorators import login_required from ..decorators import login_required
from ..models import User, UserProfile from ..models import User, UserProfile
from ..serializers import (UserLoginSerializer, UserRegisterSerializer, from ..serializers import (UserLoginSerializer, UserRegisterSerializer,

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-25 05:32 # Generated by Django 1.9.12 on 2017-01-23 07:59
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings from django.conf import settings

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.9.10 on 2016-11-19 05:18 # Generated by Django 1.9.12 on 2017-01-23 07:59
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
@ -13,6 +13,35 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='JudgeServer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hostname', models.CharField(max_length=64)),
('ip', models.CharField(blank=True, max_length=32, null=True)),
('judger_version', models.CharField(max_length=24)),
('cpu_core', models.IntegerField()),
('memory_usage', models.FloatField()),
('cpu_usage', models.FloatField()),
('last_heartbeat', models.DateTimeField()),
('create_time', models.DateTimeField(auto_now_add=True)),
('task_number', models.IntegerField(default=0)),
('service_url', models.CharField(blank=True, max_length=128, null=True)),
],
options={
'db_table': 'judge_server',
},
),
migrations.CreateModel(
name='JudgeServerToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=32)),
],
options={
'db_table': 'judge_server_token',
},
),
migrations.CreateModel( migrations.CreateModel(
name='SMTPConfig', name='SMTPConfig',
fields=[ fields=[
@ -34,7 +63,7 @@ class Migration(migrations.Migration):
('base_url', models.CharField(default='http://127.0.0.1', max_length=128)), ('base_url', models.CharField(default='http://127.0.0.1', max_length=128)),
('name', models.CharField(default='Online Judge', max_length=32)), ('name', models.CharField(default='Online Judge', max_length=32)),
('name_shortcut', models.CharField(default='oj', max_length=32)), ('name_shortcut', models.CharField(default='oj', max_length=32)),
('website_footer', models.TextField(default='Online Judge Footer')), ('footer', models.TextField(default='Online Judge Footer')),
('allow_register', models.BooleanField(default=True)), ('allow_register', models.BooleanField(default=True)),
('submission_list_show_all', models.BooleanField(default=True)), ('submission_list_show_all', models.BooleanField(default=True)),
], ],

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.10 on 2016-11-19 08:57
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('conf', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='websiteconfig',
old_name='website_footer',
new_name='footer',
),
]

View File

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.10 on 2016-11-19 14:27
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('conf', '0002_auto_20161119_1657'),
]
operations = [
migrations.CreateModel(
name='JudgeServer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hostname', models.CharField(max_length=64)),
('ip', models.CharField(max_length=32)),
('judger_version', models.CharField(max_length=24)),
('cpu_core', models.IntegerField()),
('memory_usage', models.FloatField()),
('cpu_usage', models.FloatField()),
('last_heartbeat', models.DateTimeField()),
('create_time', models.DateTimeField(auto_now_add=True)),
('task_number', models.IntegerField()),
],
options={
'db_table': 'judge_server',
},
),
]

View File

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.10 on 2016-11-20 10:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('conf', '0003_judgeserver'),
]
operations = [
migrations.AddField(
model_name='judgeserver',
name='service_url',
field=models.CharField(blank=True, max_length=128, null=True),
),
migrations.AlterField(
model_name='judgeserver',
name='ip',
field=models.CharField(blank=True, max_length=32, null=True),
),
]

View File

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.10 on 2016-11-20 11:00
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('conf', '0004_auto_20161120_1834'),
]
operations = [
migrations.CreateModel(
name='JudgeServerToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=32)),
],
options={
'db_table': 'judge_server_token',
},
),
]

View File

@ -1,6 +1,3 @@
# coding=utf-8
from __future__ import unicode_literals
from django.db import models from django.db import models

View File

@ -21,15 +21,15 @@ class SMTPAPI(APIView):
return self.success(None) return self.success(None)
return self.success(SMTPConfigSerializer(smtp).data) return self.success(SMTPConfigSerializer(smtp).data)
@super_admin_required
@validate_serializer(CreateSMTPConfigSerializer) @validate_serializer(CreateSMTPConfigSerializer)
@super_admin_required
def post(self, request): def post(self, request):
SMTPConfig.objects.all().delete() SMTPConfig.objects.all().delete()
smtp = SMTPConfig.objects.create(**request.data) smtp = SMTPConfig.objects.create(**request.data)
return self.success(SMTPConfigSerializer(smtp).data) return self.success(SMTPConfigSerializer(smtp).data)
@super_admin_required
@validate_serializer(EditSMTPConfigSerializer) @validate_serializer(EditSMTPConfigSerializer)
@super_admin_required
def put(self, request): def put(self, request):
data = request.data data = request.data
smtp = SMTPConfig.objects.first() smtp = SMTPConfig.objects.first()

View File

@ -2,3 +2,4 @@ Django<1.10
djangorestframework==3.3.3 djangorestframework==3.3.3
pillow pillow
jsonfield jsonfield
otpauth

View File

@ -1,4 +1,3 @@
# coding=utf-8
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from account.models import User, UserProfile, AdminType from account.models import User, UserProfile, AdminType
from utils.shortcuts import rand_str from utils.shortcuts import rand_str
@ -12,9 +11,10 @@ class Command(BaseCommand):
self.stdout.write(self.style.WARNING("Super admin user 'root' already exists, " self.stdout.write(self.style.WARNING("Super admin user 'root' already exists, "
"would you like to reset it's password?\n" "would you like to reset it's password?\n"
"Input yes to confirm: ")) "Input yes to confirm: "))
if raw_input() == "yes": if input() == "yes":
rand_password = rand_str(length=6) rand_password = rand_str(length=6)
admin.set_password(rand_password) # admin.set_password(rand_password)
admin.set_password("rootroot")
admin.save() admin.save()
self.stdout.write(self.style.SUCCESS("Successfully created super admin user password.\n" self.stdout.write(self.style.SUCCESS("Successfully created super admin user password.\n"
"Username: root\nPassword: %s\n" "Username: root\nPassword: %s\n"
@ -27,7 +27,9 @@ class Command(BaseCommand):
except User.DoesNotExist: except User.DoesNotExist:
user = User.objects.create(username="root", email="root@oj.com", admin_type=AdminType.SUPER_ADMIN) user = User.objects.create(username="root", email="root@oj.com", admin_type=AdminType.SUPER_ADMIN)
rand_password = rand_str(length=6) rand_password = rand_str(length=6)
user.set_password(rand_password) # user.set_password(rand_password)
# for dev
user.set_password("rootroot")
user.save() user.save()
UserProfile.objects.create(user=user) UserProfile.objects.create(user=user)
self.stdout.write(self.style.SUCCESS("Successfully created super admin user.\n" self.stdout.write(self.style.SUCCESS("Successfully created super admin user.\n"

View File

@ -1,208 +0,0 @@
# -*- coding: utf-8 -*-
"""
otpauth
~~~~~~~
Implements two-step verification of HOTP/TOTP.
:copyright: (c) 2013 - 2015 by Hsiaoming Yang.
:license: BSD, see LICENSE for more details.
"""
import sys
import time
import hmac
import base64
import struct
import hashlib
import warnings
if sys.version_info[0] == 3:
PY2 = False
string_type = str
else:
PY2 = True
string_type = unicode
range = xrange
__author__ = 'Hsiaoming Yang <me@lepture.com>'
__homepage__ = 'https://github.com/lepture/otpauth'
__version__ = '1.0.1'
__all__ = ['OtpAuth', 'HOTP', 'TOTP', 'generate_hotp', 'generate_totp']
HOTP = 'hotp'
TOTP = 'totp'
class OtpAuth(object):
"""One Time Password Authentication.
:param secret: A secret token for the authentication.
"""
def __init__(self, secret):
self.secret = secret
def hotp(self, counter=4):
"""Generate a HOTP code.
:param counter: HOTP is a counter based algorithm.
"""
return generate_hotp(self.secret, counter)
def totp(self, period=30, timestamp=None):
"""Generate a TOTP code.
A TOTP code is an extension of HOTP algorithm.
:param period: A period that a TOTP code is valid in seconds
:param timestamp: Create TOTP at this given timestamp
"""
return generate_totp(self.secret, period, timestamp)
def valid_hotp(self, code, last=0, trials=100):
"""Valid a HOTP code.
:param code: A number that is less than 6 characters.
:param last: Guess HOTP code from last + 1 range.
:param trials: Guest HOTP code end at last + trials + 1.
"""
if not valid_code(code):
return False
code = bytes(int(code))
for i in range(last + 1, last + trials + 1):
if compare_digest(bytes(self.hotp(counter=i)), code):
return i
return False
def valid_totp(self, code, period=30, timestamp=None):
"""Valid a TOTP code.
:param code: A number that is less than 6 characters.
:param period: A period that a TOTP code is valid in seconds
:param timestamp: Validate TOTP at this given timestamp
"""
if not valid_code(code):
return False
return compare_digest(
bytes(self.totp(period, timestamp)),
bytes(int(code))
)
@property
def encoded_secret(self):
secret = base64.b32encode(to_bytes(self.secret))
# bytes to string
secret = secret.decode('utf-8')
# remove pad string
return secret.strip('=')
def to_uri(self, type, label, issuer, counter=None):
"""Generate the otpauth protocal string.
:param type: Algorithm type, hotp or totp.
:param label: Label of the identifier.
:param issuer: The company, the organization or something else.
:param counter: Counter of the HOTP algorithm.
"""
type = type.lower()
if type not in ('hotp', 'totp'):
raise ValueError('type must be hotp or totp')
if type == 'hotp' and not counter:
raise ValueError('HOTP type authentication need counter')
# https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
url = ('otpauth://%(type)s/%(label)s?secret=%(secret)s'
'&issuer=%(issuer)s')
dct = dict(
type=type, label=label, issuer=issuer,
secret=self.encoded_secret, counter=counter
)
ret = url % dct
if type == 'hotp':
ret = '%s&counter=%s' % (ret, counter)
return ret
def to_google(self, type, label, issuer, counter=None):
"""Generate the otpauth protocal string for Google Authenticator.
.. deprecated:: 0.2.0
Use :func:`to_uri` instead.
"""
warnings.warn('deprecated, use to_uri instead', DeprecationWarning)
return self.to_uri(type, label, issuer, counter)
def generate_hotp(secret, counter=4):
"""Generate a HOTP code.
:param secret: A secret token for the authentication.
:param counter: HOTP is a counter based algorithm.
"""
# https://tools.ietf.org/html/rfc4226
msg = struct.pack('>Q', counter)
digest = hmac.new(to_bytes(secret), msg, hashlib.sha1).digest()
ob = digest[19]
if PY2:
ob = ord(ob)
pos = ob & 15
base = struct.unpack('>I', digest[pos:pos + 4])[0] & 0x7fffffff
token = base % 1000000
return token
def generate_totp(secret, period=30, timestamp=None):
"""Generate a TOTP code.
A TOTP code is an extension of HOTP algorithm.
:param secret: A secret token for the authentication.
:param period: A period that a TOTP code is valid in seconds
:param timestamp: Current time stamp.
"""
if timestamp is None:
timestamp = time.time()
counter = int(timestamp) // period
return generate_hotp(secret, counter)
def to_bytes(text):
if isinstance(text, string_type):
# Python3 str -> bytes
# Python2 unicode -> str
text = text.encode('utf-8')
return text
def valid_code(code):
code = string_type(code)
return code.isdigit() and len(code) <= 6
def compare_digest(a, b):
func = getattr(hmac, 'compare_digest', None)
if func:
return func(a, b)
# fallback
if len(a) != len(b):
return False
rv = 0
if PY2:
from itertools import izip
for x, y in izip(a, b):
rv |= ord(x) ^ ord(y)
else:
for x, y in zip(a, b):
rv |= x ^ y
return rv == 0

View File

View File

@ -1,4 +1,3 @@
# coding=utf-8
import logging import logging
import random import random