Commit dc3a9561 authored by ibuler's avatar ibuler

[Update] 基本完成登陆审核

parent 23b777b2
......@@ -5,3 +5,4 @@ from .auth import *
from .token import *
from .mfa import *
from .access_key import *
from .login_confirm import *
# -*- coding: utf-8 -*-
#
from rest_framework.generics import UpdateAPIView
from django.shortcuts import get_object_or_404
from common.permissions import IsOrgAdmin
from ..models import LoginConfirmSetting
from ..serializers import LoginConfirmSettingSerializer
__all__ = ['LoginConfirmSettingUpdateApi']
class LoginConfirmSettingUpdateApi(UpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = LoginConfirmSettingSerializer
def get_object(self):
from users.models import User
user_id = self.kwargs.get('user_id')
user = get_object_or_404(User, pk=user_id)
defaults = {'user': user}
s, created = LoginConfirmSetting.objects.get_or_create(
defaults, user=user,
)
return s
# Generated by Django 2.2.5 on 2019-10-31 10:23
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('authentication', '0002_auto_20190729_1423'),
]
operations = [
migrations.CreateModel(
name='LoginConfirmSetting',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('reviewers', models.ManyToManyField(blank=True, related_name='review_login_confirm_settings', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='login_confirm_setting', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'abstract': False,
},
),
]
import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _, ugettext as __
from rest_framework.authtoken.models import Token
from django.conf import settings
......@@ -40,8 +40,8 @@ class PrivateToken(Token):
class LoginConfirmSetting(CommonModelMixin):
user = models.OneToOneField('users.User', on_delete=models.CASCADE, verbose_name=_("User"), related_name=_("login_confirmation_setting"))
reviewers = models.ManyToManyField('users.User', verbose_name=_("Reviewers"), related_name=_("review_login_confirmation_settings"))
user = models.OneToOneField('users.User', on_delete=models.CASCADE, verbose_name=_("User"), related_name="login_confirm_setting")
reviewers = models.ManyToManyField('users.User', verbose_name=_("Reviewers"), related_name="review_login_confirm_settings", blank=True)
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
@classmethod
......@@ -50,7 +50,7 @@ class LoginConfirmSetting(CommonModelMixin):
def create_confirm_order(self, request=None):
from orders.models import LoginConfirmOrder
title = _('User login confirm: {}'.format(self.user))
title = _('User login confirm: {}').format(self.user)
if request:
remote_addr = get_request_ip(request)
city = get_ip_city(remote_addr)
......
......@@ -4,17 +4,16 @@ from django.core.cache import cache
from rest_framework import serializers
from users.models import User
from .models import AccessKey
from .models import AccessKey, LoginConfirmSetting
__all__ = [
'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer',
'MFAChallengeSerializer',
'MFAChallengeSerializer', 'LoginConfirmSettingSerializer',
]
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ['id', 'secret', 'is_active', 'date_created']
......@@ -87,3 +86,9 @@ class MFAChallengeSerializer(BearerTokenMixin, serializers.Serializer):
username = self.context["username"]
return self.create_response(username)
class LoginConfirmSettingSerializer(serializers.ModelSerializer):
class Meta:
model = LoginConfirmSetting
fields = ['id', 'user', 'reviewers', 'date_created', 'date_updated']
read_only_fields = ['date_created', 'date_updated']
......@@ -61,9 +61,6 @@
<div class="col-md-6">
{% include '_copyright.html' %}
</div>
<div class="col-md-6 text-right">
<small>2014-2019</small>
</div>
</div>
</div>
</body>
......
......@@ -19,7 +19,8 @@ urlpatterns = [
api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
path('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth')
path('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth'),
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
]
urlpatterns += router.urls
......
......@@ -179,7 +179,7 @@ class UserLoginGuardView(RedirectView):
if user.otp_enabled and user.otp_secret_key and \
not self.request.session.get('auth_otp'):
return reverse('authentication:login-otp')
confirm_setting = LoginConfirmSetting.get_user_confirm_setting(user)
confirm_setting = user.get_login_confirm_setting()
if confirm_setting and not self.request.session.get('auth_confirm'):
order = confirm_setting.create_confirm_order(self.request)
self.request.session['auth_order_id'] = str(order.id)
......
This diff is collapsed.
# Generated by Django 2.2.5 on 2019-10-31 10:23
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='LoginConfirmOrder',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('user_display', models.CharField(max_length=128, verbose_name='User display name')),
('title', models.CharField(max_length=256, verbose_name='Title')),
('body', models.TextField(verbose_name='Body')),
('assignee_display', models.CharField(blank=True, max_length=128, null=True, verbose_name='Assignee display name')),
('assignees_display', models.CharField(blank=True, max_length=128, verbose_name='Assignees display name')),
('type', models.CharField(choices=[('login_confirm', 'Login confirm')], max_length=16, verbose_name='Type')),
('status', models.CharField(choices=[('accepted', 'Accepted'), ('rejected', 'Rejected'), ('pending', 'Pending')], default='pending', max_length=16)),
('ip', models.GenericIPAddressField(blank=True, null=True)),
('city', models.CharField(blank=True, default='', max_length=16)),
('assignee', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loginconfirmorder_handled', to=settings.AUTH_USER_MODEL, verbose_name='Assignee')),
('assignees', models.ManyToManyField(related_name='loginconfirmorder_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Assignees')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loginconfirmorder_requested', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'ordering': ('-date_created',),
'abstract': False,
},
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('order_id', models.UUIDField()),
('user_display', models.CharField(max_length=128, verbose_name='User display name')),
('body', models.TextField(verbose_name='Body')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='comments', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'ordering': ('date_created',),
},
),
]
......@@ -48,6 +48,14 @@ class BaseOrder(CommonModelMixin):
def comments(self):
return Comment.objects.filter(order_id=self.id)
@property
def body_as_html(self):
return self.body.replace('\n', '<br/>')
@property
def status_display(self):
return self.get_status_display()
class Meta:
abstract = True
ordering = ('-date_created',)
......
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from django.dispatch import receiver
from django.db.models.signals import m2m_changed
from django.conf import settings
from django.db.models.signals import m2m_changed, post_save
from common.tasks import send_mail_async
from common.utils import get_logger, reverse
from common.utils import get_logger
from .models import LoginConfirmOrder
from .utils import (
send_login_confirm_order_mail_to_assignees,
send_login_confirm_action_mail_to_user
)
logger = get_logger(__name__)
def send_mail(order, assignees):
recipient_list = [user.email for user in assignees]
user = order.user
if not recipient_list:
logger.error("Order not has assignees: {}".format(order.id))
return
subject = '{}: {}'.format(_("New order"), order.title)
detail_url = reverse('orders:login-confirm-order-detail',
kwargs={'pk': order.id}, external=True)
message = _("""
<div>
<p>Your has a new order</p>
<div>
<b>Title:</b> {order.title}
<br/>
<b>User:</b> {user}
<br/>
<b>City:</b> {order.city}
<br/>
<b>IP:</b> {order.ip}
<br/>
<a href={url}>click here to review</a>
</div>
</div>
""").format(order=order, user=user, url=detail_url)
if settings.DEBUG:
try:
print(message)
except OSError:
pass
send_mail_async.delay(subject, message, recipient_list, html_message=message)
logger = get_logger(__name__)
@receiver(m2m_changed, sender=LoginConfirmOrder.assignees.through)
def on_login_confirm_order_assignee_set(sender, instance=None, action=None,
def on_login_confirm_order_assignees_set(sender, instance=None, action=None,
model=None, pk_set=None, **kwargs):
print(">>>>>>>>>>>>>>>>>>>>>>>.")
print(action)
if action == 'post_add':
print("<<<<<<<<<<<<<<<<<<<<")
logger.debug('New order create, send mail: {}'.format(instance.id))
assignees = model.objects.filter(pk__in=pk_set)
send_mail(instance, assignees)
print(assignees)
send_login_confirm_order_mail_to_assignees(instance, assignees)
@receiver(post_save, sender=LoginConfirmOrder)
def on_login_confirm_order_status_change(sender, instance=None, created=False, **kwargs):
if created or instance.status == "pending":
return
logger.debug('Order changed, send mail: {}'.format(instance.id))
send_login_confirm_action_mail_to_user(instance)
......@@ -14,7 +14,6 @@
<th class="text-center">{% trans 'Title' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'City' %}</th>
<th class="text-center">{% trans 'Status' %}</th>
<th class="text-center">{% trans 'Datetime' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
......@@ -38,8 +37,12 @@ function initTable() {
cellData = htmlEscape(cellData);
var detailBtn = '<a href="{% url "orders:login-confirm-order-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var d = cellData + "(" + rowData.city + ")";
$(td).html(d)
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
if (cellData === "accepted") {
$(td).html('<i class="fa fa-check text-navy"></i>')
} else if (cellData === "rejected") {
......@@ -48,13 +51,13 @@ function initTable() {
$(td).html('<i class="fa fa-spinner text-info"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData) {
{targets: 5, createdCell: function (td, cellData) {
var d = toSafeLocalDateStr(cellData);
$(td).html(d)
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
var acceptBtn = '<a class="btn btn-xs btn-info" data-uid="{{ DEFAULT_PK }}" >{% trans "Accept" %}</a> ';
var rejectBtn = '<a class="btn btn-xs btn-danger " data-uid="{{ DEFAULT_PK }}" >{% trans "Reject" %}</a>';
{targets: 6, createdCell: function (td, cellData, rowData) {
var acceptBtn = '<a class="btn btn-xs btn-info btn-action" data-action="accept" data-uid="{{ DEFAULT_PK }}" >{% trans "Accept" %}</a> ';
var rejectBtn = '<a class="btn btn-xs btn-danger btn-action" data-action="reject" data-uid="{{ DEFAULT_PK }}" >{% trans "Reject" %}</a>';
acceptBtn = acceptBtn.replace('{{ DEFAULT_PK }}', cellData);
rejectBtn = rejectBtn.replace('{{ DEFAULT_PK }}', cellData);
var acceptBtnRef = $(acceptBtn);
......@@ -68,10 +71,10 @@ function initTable() {
}}],
ajax_url: '{% url "api-orders:login-confirm-order-list" %}',
columns: [
{data: "id"}, {data: "title", className: "text-left"}, {data: "user_display"},
{data: "ip"}, {data: "city"},
{data: "status", orderable: false},
{data: "date_created"},
{data: "id"}, {data: "title", className: "text-left"},
{data: "user_display"}, {data: "ip"},
{data: "status", orderable: false, width: "30px"},
{data: "date_created", width: "120px"},
{data: "id", orderable: false, width: "100px"}
],
op_html: $('#actions').html()
......@@ -82,7 +85,6 @@ function initTable() {
$(document).ready(function(){
initTable();
$('')
var menu = [
{title: "IP", value: "ip"},
{title: "{% trans 'Title' %}", value: "title"},
......@@ -93,12 +95,21 @@ $(document).ready(function(){
]}
];
initTableFilterDropdown('#login_confirm_order_list_table_filter input', menu)
}).on('click', '.expired', function () {
var msg = '{% trans "User is expired" %}';
toastr.error(msg)
}).on('click', '.inactive', function () {
var msg = '{% trans 'User is inactive' %}';
toastr.error(msg)
}).on('click', '.btn-action', function () {
var actionCreateUrl = "{% url 'api-orders:login-confirm-order-create-action' pk=DEFAULT_PK %}";
var orderId = $(this).data('uid');
actionCreateUrl = actionCreateUrl.replace("{{ DEFAULT_PK }}", orderId);
var action = $(this).data('action');
var comment = '';
var data = {
url: actionCreateUrl,
method: 'POST',
body: JSON.stringify({action: action, comment: comment}),
success: function () {
window.location.reload();
}
};
requestApi(data);
})
</script>
{% endblock %}
......
# -*- coding: utf-8 -*-
#
from django.conf import settings
from django.utils.translation import ugettext as _
from common.utils import get_logger, reverse
from common.tasks import send_mail_async
logger = get_logger(__name__)
def send_login_confirm_order_mail_to_assignees(order, assignees):
recipient_list = [user.email for user in assignees]
user = order.user
if not recipient_list:
logger.error("Order not has assignees: {}".format(order.id))
return
subject = '{}: {}'.format(_("New order"), order.title)
detail_url = reverse('orders:login-confirm-order-detail',
kwargs={'pk': order.id}, external=True)
message = _("""
<div>
<p>Your has a new order</p>
<div>
<b>Title:</b> {order.title}
<br/>
<b>User:</b> {user}
<br/>
<b>Assignees:</b> {order.assignees_display}
<br/>
<b>City:</b> {order.city}
<br/>
<b>IP:</b> {order.ip}
<br/>
<a href={url}>click here to review</a>
</div>
</div>
""").format(order=order, user=user, url=detail_url)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
def send_login_confirm_action_mail_to_user(order):
if not order.user:
logger.error("Order not has user: {}".format(order.id))
return
user = order.user
recipient_list = [user.email]
subject = '{}: {}'.format(_("Order has been reply"), order.title)
message = _("""
<div>
<p>Your order has been replay</p>
<div>
<b>Title:</b> {order.title}
<br/>
<b>Assignee:</b> {order.assignee_display}
<br/>
<b>Status:</b> {order.status_display}
<br/>
</div>
</div>
""").format(order=order)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
......@@ -416,6 +416,9 @@ function makeLabel(data) {
function parseTableFilter(value) {
var cleanValues = [];
if (!value) {
return {}
}
var valuesArray = value.split(':');
for (var i=0; i<valuesArray.length; i++) {
var v = valuesArray[i].trim();
......
......@@ -55,9 +55,6 @@
<div class="col-md-6">
{% include '_copyright.html' %}
</div>
<div class="col-md-6 text-right">
<small>2014-2019</small>
</div>
</div>
</div>
</body>
......
......@@ -117,6 +117,13 @@ class AuthMixin:
return True
return False
def get_login_confirm_setting(self):
if hasattr(self, 'login_confirm_setting'):
s = self.login_confirm_setting
if s.reviewers.all().count() and s.is_active:
return s
return False
class RoleMixin:
ROLE_ADMIN = 'Admin'
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment