mirror of https://github.com/jumpserver/jumpserver
Merge branch 'audits'
commit
948214cacb
|
@ -23,17 +23,20 @@ class TerminalRegisterView(ListCreateAPIView):
|
|||
|
||||
def create(self, request, *args, **kwargs):
|
||||
name = request.data.get('name', '')
|
||||
remote_addr = request.META.get('X-Real-IP') or request.META.get('REMOTE_ADDR')
|
||||
serializer = self.serializer_class(data={'name': name, 'remote_addr': remote_addr})
|
||||
remote_addr = request.META.get('X-Real-IP') or \
|
||||
request.META.get('REMOTE_ADDR')
|
||||
serializer = self.serializer_class(
|
||||
data={'name': name, 'remote_addr': remote_addr})
|
||||
|
||||
if get_object_or_none(Terminal, name=name):
|
||||
return Response({'msg': 'Already register, Need administrator active it'}, status=200)
|
||||
return Response({'msg': 'Already register, Need '
|
||||
'administrator active it'}, status=200)
|
||||
|
||||
if serializer.is_valid():
|
||||
terminal = serializer.save()
|
||||
app_user, access_key = terminal.create_related_app_user()
|
||||
data = {}
|
||||
data['applications'] = copy.deepcopy(serializer.data)
|
||||
data['terminal'] = copy.deepcopy(serializer.data)
|
||||
data['user'] = app_user.to_json()
|
||||
data['access_key_id'] = access_key.id
|
||||
data['access_key_secret'] = access_key.secret
|
||||
|
@ -54,11 +57,11 @@ class TerminalViewSet(viewsets.ModelViewSet):
|
|||
def create(self, request, *args, **kwargs):
|
||||
return Response({'msg': 'Use register view except that'}, status=404)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if instance.user is not None:
|
||||
instance.user.delete()
|
||||
return super(TerminalViewSet, self).destroy(request, *args, **kwargs)
|
||||
# def destroy(self, request, *args, **kwargs):
|
||||
# instance = self.get_object()
|
||||
# if instance.user is not None:
|
||||
# instance.user.delete()
|
||||
# return super(TerminalViewSet, self).destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
class TerminalHeatbeatViewSet(viewsets.ModelViewSet):
|
||||
|
|
|
@ -14,8 +14,9 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['id', 'name', 'remote_addr', 'type', 'url', 'comment', 'is_accepted',
|
||||
'is_active', 'get_type_display', 'proxy_online', 'is_alive']
|
||||
fields = ['id', 'name', 'remote_addr', 'type', 'url', 'comment',
|
||||
'is_accepted', 'is_active', 'get_type_display',
|
||||
'proxy_online', 'is_alive']
|
||||
|
||||
@staticmethod
|
||||
def get_proxy_online(obj):
|
||||
|
@ -31,6 +32,7 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
|
||||
date_start = serializers.DateTimeField
|
||||
class Meta:
|
||||
model = TerminalHeatbeat
|
||||
|
||||
|
|
|
@ -214,23 +214,9 @@ class AdminUserForm(forms.ModelForm):
|
|||
|
||||
class SystemUserForm(forms.ModelForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(),
|
||||
label=_('Asset'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
|
||||
)
|
||||
asset_groups = forms.ModelMultipleChoiceField(queryset=AssetGroup.objects.all(),
|
||||
label=_('Asset group'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')})
|
||||
)
|
||||
auto_generate_key = forms.BooleanField(initial=True)
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True,
|
||||
help_text=_('If also set private key, use that first'), required=False)
|
||||
password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False)
|
||||
|
||||
|
@ -242,22 +228,13 @@ class SystemUserForm(forms.ModelForm):
|
|||
initial['asset_groups'] = kwargs['instance'].asset_groups.all()
|
||||
super(SystemUserForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def _save_m2m(self):
|
||||
# Save assets relation with admin user
|
||||
super(SystemUserForm, self)._save_m2m()
|
||||
assets = self.cleaned_data['assets']
|
||||
asset_groups = self.cleaned_data['asset_groups']
|
||||
self.instance.assets.clear()
|
||||
self.instance.assets.add(*tuple(assets))
|
||||
self.instance.asset_groups.clear()
|
||||
self.instance.asset_groups.add(*tuple(asset_groups))
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super(SystemUserForm, self).save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
|
||||
if system_user.auth_method == 'P':
|
||||
if password:
|
||||
system_user.password = password
|
||||
print(password)
|
||||
|
@ -268,11 +245,30 @@ class SystemUserForm(forms.ModelForm):
|
|||
system_user.save()
|
||||
return self.instance
|
||||
|
||||
# Todo: check valid
|
||||
# def clean_private_key_file(self):
|
||||
# if not self.cleaned_data['auto_generate_key']:
|
||||
# if not self.cleaned_data['private_key_file']:
|
||||
# raise forms.ValidationError(_('Private key required'))
|
||||
|
||||
# def clean_password(self):
|
||||
# if self.cleaned_data['auth_method'] == 'P':
|
||||
# if not self.cleaned_data['password']:
|
||||
# raise forms.ValidationError(_('Password required'))
|
||||
# return self.cleaned_data['password']
|
||||
|
||||
# def clean(self):
|
||||
# password = self.cleaned_data['password']
|
||||
# private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
#
|
||||
# if not (password or private_key_file):
|
||||
# raise forms.ValidationError(_('Password and private key file must be input one'))
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auth_method',
|
||||
'auto_push', 'auto_update', 'sudo', 'comment', 'shell', 'home', 'uid',
|
||||
'auto_push', 'sudo', 'comment', 'shell', 'home', 'uid',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
|
@ -282,7 +278,6 @@ class SystemUserForm(forms.ModelForm):
|
|||
'name': '* required',
|
||||
'username': '* required',
|
||||
'auto_push': 'Auto push system user to asset',
|
||||
'auto_update': 'Auto update system user ssh key',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -99,6 +99,12 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
return fields
|
||||
|
||||
|
||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'protocol', 'auth_method', 'comment')
|
||||
|
||||
|
||||
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
|
@ -145,13 +151,13 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = ("id", "hostname", "ip", "port", "system_users", "is_inherited",
|
||||
fields = ("id", "hostname", "ip", "port", "system_users_granted", "is_inherited",
|
||||
"is_active", "system_users_join", "comment")
|
||||
|
||||
@staticmethod
|
||||
|
@ -163,7 +169,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
return ', '.join([system_user.username for system_user in obj.system_users.all()])
|
||||
return ', '.join([system_user.username for system_user in obj.system_users_granted])
|
||||
|
||||
|
||||
class IDCSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
|
|
|
@ -27,23 +27,31 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
{% if form.no_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
{{ form.username|bootstrap_horizontal }}
|
||||
{{ form.protocol|bootstrap_horizontal }}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{{ form.auth_method|bootstrap_horizontal }}
|
||||
<div class="password-auth hidden">
|
||||
{{ form.password|bootstrap_horizontal }}
|
||||
</div>
|
||||
<div class="public-key-auth">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_generate_key}}
|
||||
</div>
|
||||
</div>
|
||||
{{ form.password|bootstrap_horizontal }}
|
||||
<div>
|
||||
{{ form.private_key_file|bootstrap_horizontal }}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_default.id_for_label }}" class="col-sm-2 control-label">{% trans 'As default' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.as_default}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -52,20 +60,12 @@
|
|||
{{ form.auto_push}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_update.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto update' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_update}}
|
||||
</div>
|
||||
</div>
|
||||
{{ form.assets|bootstrap_horizontal }}
|
||||
{{ form.asset_groups|bootstrap_horizontal }}
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{{ form.sudo|bootstrap_horizontal }}
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
{{ form.home|bootstrap_horizontal }}
|
||||
{{ form.shell|bootstrap_horizontal }}
|
||||
{{ form.uid|bootstrap_horizontal }}
|
||||
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
|
@ -81,8 +81,36 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
function authMethodDisplay() {
|
||||
if ($(auth_method).val() == 'P') {
|
||||
$('.password-auth').removeClass('hidden');
|
||||
$('.public-key-auth').addClass('hidden');
|
||||
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
|
||||
|
||||
} else if ($(auth_method).val() == 'K') {
|
||||
$('.password-auth').addClass('hidden');
|
||||
$('.public-key-auth').removeClass('hidden');
|
||||
|
||||
if ($(auto_generate_key).prop('checked')){
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
|
||||
} else {
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
|
||||
{# $('#'+'{{ form.private_key_file.id_for_label }}').attr('required', 'required');#}
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authMethodDisplay();
|
||||
$(auth_method).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
$(auto_generate_key).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
|
||||
|
||||
if ($('#'+'{{ form.protocol.id_for_label }}').val() == 'telnet') {
|
||||
$('#'+'{{ form.auto_generate_key.id_for_label }}').closest('.form-group').remove();
|
||||
|
|
|
@ -547,6 +547,10 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
kwargs.update(context)
|
||||
return super(SystemUserCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_invalid(self, form):
|
||||
print(form.errors)
|
||||
return super(SystemUserCreateView, self).form_invalid(form)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
success_message = _('Create system user <a href="%s">%s</a> successfully.' %
|
||||
(
|
||||
|
|
|
@ -6,6 +6,7 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
from rest_framework import generics, viewsets
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from . import models, serializers
|
||||
from .hands import IsSuperUserOrAppUser, Terminal, IsAppUser
|
||||
|
@ -51,13 +52,3 @@ class CommandLogViewSet(viewsets.ModelViewSet):
|
|||
serializer_class = serializers.CommandLogSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
# class CommandLogTitleApi(APIView):
|
||||
# def get(self, request):
|
||||
# response = [
|
||||
# {"name": "command_no", "title": "ID", "type": "number"},
|
||||
# {"name": "command", "title": "Title", "visible": True, "filterable": True},
|
||||
# {"name": "datetime", "title": "Datetime", "type"},
|
||||
# {"name": "output", "title": "Output", "filterable": True},
|
||||
# ]
|
||||
#
|
|
@ -17,10 +17,13 @@ class LoginLog(models.Model):
|
|||
|
||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
|
||||
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
|
||||
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2,
|
||||
verbose_name=_('Login type'))
|
||||
login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||
login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('User agent'))
|
||||
login_city = models.CharField(max_length=100, blank=True, null=True,
|
||||
verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=100, blank=True, null=True,
|
||||
verbose_name=_('User agent'))
|
||||
date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||
|
||||
class Meta:
|
||||
|
@ -66,7 +69,8 @@ class ProxyLog(models.Model):
|
|||
|
||||
|
||||
class CommandLog(models.Model):
|
||||
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE, related_name='commands')
|
||||
proxy_log = models.ForeignKey(ProxyLog, on_delete=models.CASCADE,
|
||||
related_name='commands')
|
||||
command_no = models.IntegerField()
|
||||
command = models.CharField(max_length=1000, blank=True)
|
||||
output = models.TextField(blank=True)
|
||||
|
@ -78,7 +82,8 @@ class CommandLog(models.Model):
|
|||
@property
|
||||
def output_decode(self):
|
||||
try:
|
||||
return base64.b64decode(self.output).replace('\n', '<br />')
|
||||
return base64.b64decode(self.output).decode('utf-8') \
|
||||
.replace('\n', '<br />')
|
||||
except UnicodeDecodeError:
|
||||
return 'UnicodeDecodeError'
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@ class ProxyLogSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = models.ProxyLog
|
||||
fields = ['id', 'name', 'username', 'hostname', 'ip', 'system_user', 'login_type', 'terminal',
|
||||
'log_file', 'was_failed', 'is_finished', 'date_start', 'date_finished', 'time',
|
||||
fields = ['id', 'name', 'username', 'hostname', 'ip', 'system_user',
|
||||
'login_type', 'terminal', 'log_file', 'was_failed',
|
||||
'is_finished', 'date_start', 'date_finished', 'time',
|
||||
'command_length', "commands_dict"]
|
||||
|
||||
@staticmethod
|
||||
|
@ -32,3 +33,4 @@ class ProxyLogSerializer(serializers.ModelSerializer):
|
|||
class CommandLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.CommandLog
|
||||
fields = '__all__'
|
||||
|
|
|
@ -68,30 +68,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Detail' %} <b>{{ user_object.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table2 table-stripped toggle-arrow-tiny" data-page-size="8">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,17 +14,16 @@ from .models import ProxyLog, CommandLog, LoginLog
|
|||
from .hands import User, Asset, SystemUser, AdminUserRequiredMixin
|
||||
|
||||
|
||||
date_now = timezone.localtime(timezone.now())
|
||||
now_s = date_now.strftime('%m/%d/%Y')
|
||||
seven_days_ago_s = (date_now-timezone.timedelta(7)).strftime('%m/%d/%Y')
|
||||
|
||||
|
||||
class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
||||
model = ProxyLog
|
||||
template_name = 'audits/proxy_log_list.html'
|
||||
context_object_name = 'proxy_log_list'
|
||||
|
||||
def get_queryset(self):
|
||||
date_now = timezone.localtime(timezone.now())
|
||||
now_s = date_now.strftime('%m/%d/%Y')
|
||||
seven_days_ago_s = (date_now-timezone.timedelta(7)).strftime('%m/%d/%Y')
|
||||
|
||||
self.queryset = super(ProxyLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.username = username = self.request.GET.get('username', '')
|
||||
|
@ -37,7 +36,8 @@ class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
|||
date_from = timezone.datetime.strptime(date_from_s, '%m/%d/%Y')
|
||||
self.queryset = self.queryset.filter(date_start__gt=date_from)
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
|
||||
date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59',
|
||||
'%m/%d/%Y %H:%M:%S')
|
||||
self.queryset = self.queryset.filter(date_start__lt=date_to)
|
||||
if username:
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
|
@ -54,7 +54,6 @@ class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
|||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
print(self.date_to_s)
|
||||
context = {
|
||||
'app': _('Audits'),
|
||||
'action': _('Proxy log list'),
|
||||
|
@ -110,6 +109,9 @@ class CommandLogListView(AdminUserRequiredMixin, ListView):
|
|||
context_object_name = 'command_list'
|
||||
|
||||
def get_queryset(self):
|
||||
date_now = timezone.localtime(timezone.now())
|
||||
now_s = date_now.strftime('%m/%d/%Y')
|
||||
seven_days_ago_s = (date_now-timezone.timedelta(7)).strftime('%m/%d/%Y')
|
||||
self.queryset = super(CommandLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.sort = sort = self.request.GET.get('sort', '-datetime')
|
||||
|
@ -161,6 +163,9 @@ class LoginLogListView(AdminUserRequiredMixin, ListView):
|
|||
context_object_name = 'login_log_list'
|
||||
|
||||
def get_queryset(self):
|
||||
date_now = timezone.localtime(timezone.now())
|
||||
now_s = date_now.strftime('%m/%d/%Y')
|
||||
seven_days_ago_s = (date_now - timezone.timedelta(7)).strftime('%m/%d/%Y')
|
||||
self.queryset = super(LoginLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.username = username = self.request.GET.get('username', '')
|
||||
|
|
|
@ -16,5 +16,6 @@ app = Celery('jumpserver')
|
|||
# Using a string here means the worker will not have to
|
||||
# pickle the object when using Windows.
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
|
||||
app.autodiscover_tasks(lambda: [app_config.split('.')[0]
|
||||
for app_config in settings.INSTALLED_APPS])
|
||||
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from users.permissions import IsValidUser, IsSuperUser
|
||||
from users.permissions import IsValidUser, IsSuperUser, IsAppUser
|
||||
from common.utils import get_object_or_none
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups, get_user_asset_permissions, \
|
||||
get_user_group_asset_permissions, get_user_group_granted_assets, get_user_group_granted_asset_groups
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups, \
|
||||
get_user_asset_permissions, get_user_group_asset_permissions, \
|
||||
get_user_group_granted_assets, get_user_group_granted_asset_groups
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, AssetGroup, AssetGroupSerializer
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, \
|
||||
AssetGroup, AssetGroupSerializer, SystemUser
|
||||
from . import serializers
|
||||
|
||||
|
||||
|
@ -80,11 +82,12 @@ class UserGrantedAssetsApi(ListAPIView):
|
|||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
||||
queryset = []
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
queryset = get_user_granted_assets(user)
|
||||
else:
|
||||
queryset = []
|
||||
for k, v in get_user_granted_assets(user).items():
|
||||
k.system_users_granted = v
|
||||
queryset.append(k)
|
||||
return queryset
|
||||
|
||||
|
||||
|
@ -104,19 +107,26 @@ class UserGrantedAssetGroupsApi(ListAPIView):
|
|||
|
||||
|
||||
class MyGrantedAssetsApi(ListAPIView):
|
||||
"""授权给用户的资产列表
|
||||
[{'hostname': 'x','ip': 'x', ..,
|
||||
'system_users_granted': [{'name': 'x', .}, ...]
|
||||
"""
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
user = self.request.user
|
||||
if user:
|
||||
queryset = get_user_granted_assets(user)
|
||||
else:
|
||||
queryset = []
|
||||
for asset, system_users in get_user_granted_assets(user).items():
|
||||
asset.system_users_granted = system_users
|
||||
queryset.append(asset)
|
||||
return queryset
|
||||
|
||||
|
||||
class MyGrantedAssetsGroupsApi(APIView):
|
||||
"""授权给用户的资产组列表, 非直接通过授权规则授权的资产组列表, 而是授权资产的所有
|
||||
资产组之和"""
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -141,6 +151,7 @@ class MyGrantedAssetsGroupsApi(APIView):
|
|||
|
||||
|
||||
class MyAssetGroupAssetsApi(ListAPIView):
|
||||
"""授权用户资产组下的资产列表, 非该资产组的所有资产,而是被授权的"""
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
|
@ -152,8 +163,9 @@ class MyAssetGroupAssetsApi(ListAPIView):
|
|||
|
||||
if user and asset_group:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset in assets:
|
||||
if asset_group in asset.groups.all():
|
||||
for asset in asset_group.assets.all():
|
||||
if asset in assets:
|
||||
asset.system_users_granted = assets[asset]
|
||||
queryset.append(asset)
|
||||
return queryset
|
||||
|
||||
|
@ -186,3 +198,23 @@ class UserGroupGrantedAssetGroupsApi(ListAPIView):
|
|||
else:
|
||||
queryset = []
|
||||
return queryset
|
||||
|
||||
|
||||
class ValidateUserAssetPermissionView(APIView):
|
||||
permission_classes = (IsAppUser,)
|
||||
|
||||
@staticmethod
|
||||
def get(request):
|
||||
user_id = request.query_params.get('user_id', '')
|
||||
asset_id = request.query_params.get('asset_id', '')
|
||||
system_id = request.query_params.get('system_user_id', '')
|
||||
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
assets_granted = get_user_granted_assets(user)
|
||||
if system_user in assets_granted.get(asset, []):
|
||||
return Response({'msg': True}, status=200)
|
||||
else:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
|
|
@ -11,6 +11,7 @@ from .hands import User
|
|||
class AssetPermissionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class UserAssetPermissionSerializer(AssetPermissionSerializer):
|
||||
|
@ -23,4 +24,3 @@ class UserAssetPermissionSerializer(AssetPermissionSerializer):
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
@ -7,30 +7,50 @@ from .. import api
|
|||
app_name = 'perms'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||
router.register('v1/asset-permissions',
|
||||
api.AssetPermissionViewSet,
|
||||
'asset-permission')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/user/my/assets/$', api.MyGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
url(r'^v1/user/my/asset-groups/$', api.MyGrantedAssetsGroupsApi.as_view(), name='my-asset-groups'),
|
||||
url(r'^v1/user/my/asset-group/(?P<pk>[0-9]+)/assets/$', api.MyAssetGroupAssetsApi.as_view(),
|
||||
# 用户可以使用自己的Token或其它认证查看自己授权的资产,资产组等
|
||||
url(r'^v1/user/my/assets/$',
|
||||
api.MyGrantedAssetsApi.as_view(),
|
||||
name='my-assets'),
|
||||
url(r'^v1/user/my/asset-groups/$',
|
||||
api.MyGrantedAssetsGroupsApi.as_view(),
|
||||
name='my-asset-groups'),
|
||||
url(r'^v1/user/my/asset-group/(?P<pk>[0-9]+)/assets/$',
|
||||
api.MyAssetGroupAssetsApi.as_view(),
|
||||
name='user-my-asset-group-assets'),
|
||||
|
||||
# Select user permission of asset and asset group
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/asset-groups/$', api.UserGrantedAssetGroupsApi.as_view(),
|
||||
# 查询某个用户授权的资产和资产组
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/assets/$',
|
||||
api.UserGrantedAssetsApi.as_view(),
|
||||
name='user-assets'),
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/asset-groups/$',
|
||||
api.UserGrantedAssetGroupsApi.as_view(),
|
||||
name='user-asset-groups'),
|
||||
|
||||
# Select user group permission of asset and asset group
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/asset-groups/$', api.UserGroupGrantedAssetGroupsApi.as_view(),
|
||||
# 查询某个用户组授权的资产和资产组
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/assets/$',
|
||||
api.UserGroupGrantedAssetsApi.as_view(),
|
||||
name='user-group-assets'),
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/asset-groups/$',
|
||||
api.UserGroupGrantedAssetGroupsApi.as_view(),
|
||||
name='user-group-asset-groups'),
|
||||
|
||||
|
||||
# Revoke permission api
|
||||
url(r'^v1/asset-permissions/user/revoke/', api.RevokeUserAssetPermission.as_view(),
|
||||
# 回收用户或用户组授权
|
||||
url(r'^v1/asset-permissions/user/revoke/$',
|
||||
api.RevokeUserAssetPermission.as_view(),
|
||||
name='revoke-user-asset-permission'),
|
||||
url(r'^v1/asset-permissions/user-group/revoke/', api.RevokeUserGroupAssetPermission.as_view(),
|
||||
url(r'^v1/asset-permissions/user-group/revoke/$',
|
||||
api.RevokeUserGroupAssetPermission.as_view(),
|
||||
name='revoke-user-group-asset-permission'),
|
||||
|
||||
# 验证用户是否有某个资产和系统用户的权限
|
||||
url(r'v1/asset-permission/user/validate/$',
|
||||
api.ValidateUserAssetPermissionView.as_view(),
|
||||
name='validate-user-asset-permission')
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -37,6 +37,8 @@ def get_user_group_granted_assets(user_group):
|
|||
if not asset_permission.is_valid:
|
||||
continue
|
||||
for asset in asset_permission.get_granted_assets():
|
||||
if not asset.is_active:
|
||||
continue
|
||||
if asset in assets:
|
||||
assets[asset] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
|
@ -127,6 +129,8 @@ def get_user_granted_assets_direct(user):
|
|||
if not asset_permission.is_valid:
|
||||
continue
|
||||
for asset in asset_permission.get_granted_assets():
|
||||
if not asset.is_active:
|
||||
continue
|
||||
if asset in assets:
|
||||
assets[asset] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
|
@ -147,12 +151,13 @@ def get_user_granted_assets_inherit_from_user_groups(user):
|
|||
for user_group in user_groups:
|
||||
assets_inherited = get_user_group_granted_assets(user_group)
|
||||
for asset in assets_inherited:
|
||||
if not asset.is_active:
|
||||
continue
|
||||
if asset in assets:
|
||||
assets[asset] |= assets_inherited[asset]
|
||||
else:
|
||||
setattr(asset, 'inherited', True)
|
||||
assets[asset] = assets_inherited[asset]
|
||||
|
||||
return assets
|
||||
|
||||
|
||||
|
@ -167,6 +172,8 @@ def get_user_granted_assets(user):
|
|||
assets = assets_inherited
|
||||
|
||||
for asset in assets_direct:
|
||||
if not asset.is_active:
|
||||
continue
|
||||
if asset in assets:
|
||||
assets[asset] |= assets_direct[asset]
|
||||
else:
|
||||
|
|
|
@ -39,7 +39,8 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
|||
self.sort = sort = self.request.GET.get('sort', '-date_created')
|
||||
|
||||
if keyword:
|
||||
self.queryset = self.queryset.filter(Q(users__name__contains=keyword) |
|
||||
self.queryset = self.queryset\
|
||||
.filter(Q(users__name__contains=keyword) |
|
||||
Q(users__username__contains=keyword) |
|
||||
Q(user_groups__name__contains=keyword) |
|
||||
Q(assets__ip__contains=keyword) |
|
||||
|
@ -54,7 +55,9 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
|||
return self.queryset
|
||||
|
||||
|
||||
class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class AssetPermissionCreateView(AdminUserRequiredMixin,
|
||||
SuccessMessageMixin,
|
||||
CreateView):
|
||||
model = AssetPermission
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
|
@ -69,11 +72,11 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, Cre
|
|||
return super(AssetPermissionCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
success_message = _('Create asset permission <a href="%s"> %s </a> successfully.' %
|
||||
(
|
||||
reverse_lazy('perms:asset-permission-detail', kwargs={'pk': self.object.pk}),
|
||||
self.object.name,
|
||||
))
|
||||
success_message = _(
|
||||
'Create asset permission <a href="%s"> %s </a> '
|
||||
'successfully.' % (reverse_lazy('perms:asset-permission-detail',
|
||||
kwargs={'pk': self.object.pk}),
|
||||
self.object.name,))
|
||||
return success_message
|
||||
|
||||
|
||||
|
@ -81,7 +84,8 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
model = AssetPermission
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
success_message = _('Update asset permission <a href="%s"> %s </a> successfully.')
|
||||
success_message = _('Update asset permission '
|
||||
'<a href="%s"> %s </a> successfully.')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -92,7 +96,8 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
return super(AssetPermissionUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
success_url = reverse_lazy('perms:asset-permission-detail', kwargs={'pk': self.object.pk})
|
||||
success_url = reverse_lazy('perms:asset-permission-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
return success_url
|
||||
|
||||
|
||||
|
@ -105,7 +110,8 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
|||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Asset permission detail'),
|
||||
'system_users_remain': [system_user for system_user in SystemUser.objects.all()
|
||||
'system_users_remain': [
|
||||
system_user for system_user in SystemUser.objects.all()
|
||||
if system_user not in self.object.system_users.all()],
|
||||
'system_users': self.object.system_users.all(),
|
||||
}
|
||||
|
@ -119,7 +125,9 @@ class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
|
|||
success_url = reverse_lazy('perms:asset-permission-list')
|
||||
|
||||
|
||||
class AssetPermissionUserView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
class AssetPermissionUserView(AdminUserRequiredMixin,
|
||||
SingleObjectMixin,
|
||||
ListView):
|
||||
template_name = 'perms/asset_permission_user.html'
|
||||
context_object_name = 'asset_permission'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
|
@ -132,7 +140,9 @@ class AssetPermissionUserView(AdminUserRequiredMixin, SingleObjectMixin, ListVie
|
|||
def get_queryset(self):
|
||||
queryset = self.object.get_granted_users()
|
||||
if self.keyword:
|
||||
search_func = functools.partial(search_object_attr, value=self.keyword,
|
||||
search_func = functools.partial(
|
||||
search_object_attr,
|
||||
value=self.keyword,
|
||||
attr_list=['username', 'name', 'email'],
|
||||
ignore_case=True)
|
||||
queryset = filter(search_func, queryset)
|
||||
|
@ -144,9 +154,12 @@ class AssetPermissionUserView(AdminUserRequiredMixin, SingleObjectMixin, ListVie
|
|||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Asset permission user list'),
|
||||
'users_remain': [user for user in User.objects.all() if user not in users_granted],
|
||||
'users_remain': [
|
||||
user for user in User.objects.all()
|
||||
if user not in users_granted],
|
||||
'user_groups': self.object.user_groups.all(),
|
||||
'user_groups_remain': [user_group for user_group in UserGroup.objects.all()
|
||||
'user_groups_remain': [
|
||||
user_group for user_group in UserGroup.objects.all()
|
||||
if user_group not in user_groups_granted],
|
||||
'keyword': self.keyword,
|
||||
}
|
||||
|
@ -154,7 +167,9 @@ class AssetPermissionUserView(AdminUserRequiredMixin, SingleObjectMixin, ListVie
|
|||
return super(AssetPermissionUserView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetPermissionAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
class AssetPermissionAssetView(AdminUserRequiredMixin,
|
||||
SingleObjectMixin,
|
||||
ListView):
|
||||
template_name = 'perms/asset_permission_asset.html'
|
||||
context_object_name = 'asset_permission'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
|
@ -162,12 +177,14 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListVi
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AssetPermission.objects.all())
|
||||
self.keyword = self.request.GET.get('keyword', '')
|
||||
return super(AssetPermissionAssetView, self).get(request, *args, **kwargs)
|
||||
return super(AssetPermissionAssetView, self)\
|
||||
.get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.object.get_granted_assets()
|
||||
if self.keyword:
|
||||
search_func = functools.partial(search_object_attr, value=self.keyword,
|
||||
search_func = functools.partial(
|
||||
search_object_attr, value=self.keyword,
|
||||
attr_list=['hostname', 'ip'],
|
||||
ignore_case=True)
|
||||
queryset = filter(search_func, queryset)
|
||||
|
@ -179,9 +196,12 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListVi
|
|||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Asset permission asset list'),
|
||||
'assets_remain': (asset for asset in Asset.objects.all() if asset not in assets_granted),
|
||||
'assets_remain': [
|
||||
asset for asset in Asset.objects.all()
|
||||
if asset not in assets_granted],
|
||||
'asset_groups': self.object.asset_groups.all(),
|
||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
||||
'asset_groups_remain': [
|
||||
asset_group for asset_group in AssetGroup.objects.all()
|
||||
if asset_group not in asset_groups_granted],
|
||||
'keyword': self.keyword,
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
import base64
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from rest_framework import generics, viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
# from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
||||
|
@ -19,7 +13,8 @@ from common.utils import get_logger
|
|||
from .utils import check_user_valid, generate_token
|
||||
from .models import User, UserGroup
|
||||
from .hands import write_login_log_async
|
||||
from .permissions import IsSuperUser, IsAppUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from .permissions import (
|
||||
IsSuperUser, IsAppUser, IsValidUser)
|
||||
from . import serializers
|
||||
|
||||
|
||||
|
@ -98,7 +93,8 @@ class UserToken(APIView):
|
|||
password = request.data.get('password', '')
|
||||
public_key = request.data.get('public_key', '')
|
||||
|
||||
user, msg = check_user_valid(username=username, email=email,
|
||||
user, msg = check_user_valid(
|
||||
username=username, email=email,
|
||||
password=password, public_key=public_key)
|
||||
else:
|
||||
user = request.user
|
||||
|
@ -116,24 +112,31 @@ class UserProfile(APIView):
|
|||
def get(self, request):
|
||||
return Response(request.user.to_json())
|
||||
|
||||
def post(self, request):
|
||||
return Response(request.user.to_json())
|
||||
|
||||
|
||||
class UserAuthApi(APIView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
def post(self, request):
|
||||
username = request.data.get('username', '')
|
||||
password = request.data.get('password', '')
|
||||
public_key = request.data.get('public_key', '')
|
||||
login_type = request.data.get('login_type', '')
|
||||
login_ip = request.data.get('remote_addr', None) or request.META.get('REMOTE_ADDR', '')
|
||||
login_ip = request.data.get('remote_addr', None)
|
||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||
|
||||
user, msg = check_user_valid(username=username, password=password, public_key=public_key)
|
||||
user, msg = check_user_valid(
|
||||
username=username, password=password,
|
||||
public_key=public_key)
|
||||
|
||||
if user:
|
||||
token = generate_token(request, user)
|
||||
write_login_log_async.delay(user.username, name=user.name, user_agent=user_agent,
|
||||
login_ip=login_ip, login_type=login_type)
|
||||
write_login_log_async.delay(
|
||||
user.username, name=user.name,
|
||||
user_agent=user_agent, login_ip=login_ip,
|
||||
login_type=login_type)
|
||||
return Response({'token': token, 'user': user.to_json()})
|
||||
else:
|
||||
return Response({'msg': msg}, status=401)
|
||||
|
|
|
@ -2,16 +2,18 @@
|
|||
#
|
||||
|
||||
import base64
|
||||
import uuid
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import authentication, exceptions, permissions
|
||||
from django.utils.six import text_type
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import HTTP_HEADER_ENCODING
|
||||
from rest_framework import authentication, exceptions, permissions
|
||||
from rest_framework.authentication import CSRFCheck
|
||||
|
||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
||||
from .utils import refresh_token
|
||||
|
@ -27,6 +29,21 @@ def get_request_date_header(request):
|
|||
|
||||
|
||||
class AccessKeyAuthentication(authentication.BaseAuthentication):
|
||||
"""App使用Access key进行签名认证, 目前签名算法比较简单,
|
||||
app注册或者手动建立后,会生成 access_key_id 和 access_key_secret,
|
||||
然后使用 如下算法生成签名:
|
||||
Signature = md5(access_key_secret + '\n' + Date)
|
||||
example: Signature = md5('d32d2b8b-9a10-4b8d-85bb-1a66976f6fdc' + '\n' +
|
||||
'Thu, 12 Jan 2017 08:19:41 GMT')
|
||||
请求时设置请求header
|
||||
header['Authorization'] = 'Sign access_key_id:Signature' 如:
|
||||
header['Authorization'] =
|
||||
'Sign d32d2b8b-9a10-4b8d-85bb-1a66976f6fdc:OKOlmdxgYPZ9+SddnUUDbQ=='
|
||||
|
||||
验证时根据相同算法进行验证, 取到access_key_id对应的access_key_id, 从request
|
||||
headers取到Date, 然后进行md5, 判断得到的结果是否相同, 如果是认证通过, 否则 认证
|
||||
失败
|
||||
"""
|
||||
keyword = 'Sign'
|
||||
model = AccessKey
|
||||
|
||||
|
@ -40,22 +57,30 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
msg = _('Invalid signature header. No credentials provided.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
elif len(auth) > 2:
|
||||
msg = _('Invalid signature header. Signature string should not contain spaces.')
|
||||
msg = _('Invalid signature header. Signature '
|
||||
'string should not contain spaces.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
try:
|
||||
sign = auth[1].decode().split(':')
|
||||
if len(sign) != 2:
|
||||
msg = _('Invalid signature header. Format like AccessKeyId:Signature')
|
||||
msg = _('Invalid signature header. '
|
||||
'Format like AccessKeyId:Signature')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
except UnicodeError:
|
||||
msg = _('Invalid signature header. Signature string should not contain invalid characters.')
|
||||
msg = _('Invalid signature header. '
|
||||
'Signature string should not contain invalid characters.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
access_key_id = sign[0]
|
||||
try:
|
||||
uuid.UUID(access_key_id)
|
||||
except ValueError:
|
||||
raise exceptions.AuthenticationFailed('Access key id invalid')
|
||||
request_signature = sign[1]
|
||||
|
||||
return self.authenticate_credentials(request, access_key_id, request_signature)
|
||||
return self.authenticate_credentials(
|
||||
request, access_key_id, request_signature)
|
||||
|
||||
@staticmethod
|
||||
def authenticate_credentials(request, access_key_id, request_signature):
|
||||
|
@ -68,14 +93,17 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
try:
|
||||
request_unix_time = http_to_unixtime(request_date)
|
||||
except ValueError:
|
||||
raise exceptions.AuthenticationFailed(_('HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT'))
|
||||
raise exceptions.AuthenticationFailed(
|
||||
_('HTTP header: Date not provide '
|
||||
'or not %a, %d %b %Y %H:%M:%S GMT'))
|
||||
|
||||
if int(time.time()) - request_unix_time > 15 * 60:
|
||||
raise exceptions.AuthenticationFailed(_('Expired, more than 15 minutes'))
|
||||
raise exceptions.AuthenticationFailed(
|
||||
_('Expired, more than 15 minutes'))
|
||||
|
||||
signature = make_signature(access_key_secret, request_date)
|
||||
if not signature == request_signature:
|
||||
raise exceptions.AuthenticationFailed(_('Invalid signature. %s: %s' % (signature, request_signature)))
|
||||
raise exceptions.AuthenticationFailed(_('Invalid signature.'))
|
||||
|
||||
if not access_key.user.is_active:
|
||||
raise exceptions.AuthenticationFailed(_('User disabled.'))
|
||||
|
@ -97,13 +125,15 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
|||
msg = _('Invalid token header. No credentials provided.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
elif len(auth) > 2:
|
||||
msg = _('Invalid token header. Sign string should not contain spaces.')
|
||||
msg = _('Invalid token header. Sign string '
|
||||
'should not contain spaces.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
try:
|
||||
token = auth[1].decode()
|
||||
except UnicodeError:
|
||||
msg = _('Invalid token header. Sign string should not contain invalid characters.')
|
||||
msg = _('Invalid token header. Sign string '
|
||||
'should not contain invalid characters.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
return self.authenticate_credentials(token)
|
||||
|
||||
|
@ -125,4 +155,6 @@ class PrivateTokenAuthentication(authentication.TokenAuthentication):
|
|||
|
||||
class SessionAuthentication(authentication.SessionAuthentication):
|
||||
def enforce_csrf(self, request):
|
||||
return None
|
||||
reason = CSRFCheck().process_view(request, None, (), {})
|
||||
if reason:
|
||||
raise exceptions.AuthenticationFailed(reason)
|
||||
|
|
|
@ -16,7 +16,8 @@ class AccessKey(models.Model):
|
|||
default=uuid.uuid4, editable=False)
|
||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||
default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, verbose_name='User', related_name='access_key')
|
||||
user = models.ForeignKey(User, verbose_name='User',
|
||||
related_name='access_key')
|
||||
|
||||
def get_id(self):
|
||||
return str(self.id)
|
||||
|
|
|
@ -18,9 +18,12 @@ urlpatterns = [
|
|||
url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'),
|
||||
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'),
|
||||
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/password/reset/$', api.UserResetPasswordApi.as_view(), name='user-reset-password'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/public-key/reset/$', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/public-key/update/$', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/password/reset/$',
|
||||
api.UserResetPasswordApi.as_view(), name='user-reset-password'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/public-key/reset/$',
|
||||
api.UserResetPKApi.as_view(), name='user-public-key-reset'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/public-key/update/$',
|
||||
api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/groups/$',
|
||||
api.UserUpdateGroupApi.as_view(), name='user-update-group'),
|
||||
url(r'^v1/user-groups/(?P<pk>\d+)/users/$',
|
||||
|
|
|
@ -6,39 +6,72 @@ from .. import views
|
|||
app_name = 'users'
|
||||
|
||||
urlpatterns = [
|
||||
# Login view
|
||||
url(r'^login$', views.UserLoginView.as_view(), name='login'),
|
||||
url(r'^logout$', views.UserLogoutView.as_view(), name='logout'),
|
||||
url(r'^password/forgot$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
|
||||
url(r'^password/forgot$', views.UserForgotPasswordView.as_view(),
|
||||
name='forgot-password'),
|
||||
url(r'^password/forgot/sendmail-success$',
|
||||
views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
|
||||
url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'),
|
||||
url(r'^password/reset/success$', views.UserResetPasswordSuccessView.as_view(),
|
||||
views.UserForgotPasswordSendmailSuccessView.as_view(),
|
||||
name='forgot-password-sendmail-success'),
|
||||
url(r'^password/reset$', views.UserResetPasswordView.as_view(),
|
||||
name='reset-password'),
|
||||
url(r'^password/reset/success$',
|
||||
views.UserResetPasswordSuccessView.as_view(),
|
||||
name='reset-password-success'),
|
||||
|
||||
# User view
|
||||
url(r'^user$', views.UserListView.as_view(), name='user-list'),
|
||||
url(r'^user/(?P<pk>[0-9]+)$', views.UserDetailView.as_view(), name='user-detail'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission$', views.UserAssetPermissionView.as_view(),
|
||||
url(r'^user/(?P<pk>[0-9]+)$', views.UserDetailView.as_view(),
|
||||
name='user-detail'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission$',
|
||||
views.UserAssetPermissionView.as_view(),
|
||||
name='user-asset-permission'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission/create$',
|
||||
views.UserAssetPermissionCreateView.as_view(),
|
||||
name='user-asset-permission-create'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'),
|
||||
url(r'^user/export/', views.UserExportView.as_view(), name='user-export'),
|
||||
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
|
||||
url(r'^user/import/$', views.BulkImportUserView.as_view(), name='user-import'),
|
||||
# url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'),
|
||||
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/assets',
|
||||
views.UserGrantedAssetView.as_view(),
|
||||
name='user-granted-asset'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/login-history',
|
||||
views.UserDetailView.as_view(),
|
||||
name='user-login-history'),
|
||||
url(r'^user/export/',
|
||||
views.UserExportView.as_view(),
|
||||
name='user-export'),
|
||||
url(r'^first-login/$',
|
||||
views.UserFirstLoginView.as_view(),
|
||||
name='user-first-login'),
|
||||
url(r'^user/import/$',
|
||||
views.UserBulkImportView.as_view(),
|
||||
name='user-import'),
|
||||
url(r'^user/create$',
|
||||
views.UserCreateView.as_view(),
|
||||
name='user-create'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/update$',
|
||||
views.UserUpdateView.as_view(),
|
||||
name='user-update'),
|
||||
|
||||
# User group view
|
||||
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
||||
url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission$', views.UserGroupAssetPermissionView.as_view(),
|
||||
url(r'^user-group$',
|
||||
views.UserGroupListView.as_view(),
|
||||
name='user-group-list'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)$',
|
||||
views.UserGroupDetailView.as_view(),
|
||||
name='user-group-detail'),
|
||||
url(r'^user-group/create$',
|
||||
views.UserGroupCreateView.as_view(),
|
||||
name='user-group-create'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/update$',
|
||||
views.UserGroupUpdateView.as_view(),
|
||||
name='user-group-update'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission$',
|
||||
views.UserGroupAssetPermissionView.as_view(),
|
||||
name='user-group-asset-permission'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission/create$', views.UserGroupAssetPermissionCreateView.as_view(),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission/create$',
|
||||
views.UserGroupAssetPermissionCreateView.as_view(),
|
||||
name='user-group-asset-permission-create'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/assets', views.UserGroupGrantedAssetView.as_view(),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/assets',
|
||||
views.UserGroupGrantedAssetView.as_view(),
|
||||
name='user-group-granted-asset'),
|
||||
]
|
||||
|
|
|
@ -1,593 +0,0 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.writer.excel import save_virtual_workbook
|
||||
from openpyxl import load_workbook
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.db import IntegrityError
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect, csrf_exempt
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.list import ListView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, FormMixin
|
||||
from django.views.generic.detail import DetailView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from perms.models import AssetPermission
|
||||
from .models import User, UserGroup
|
||||
from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_password_mail
|
||||
from .hands import write_login_log_async
|
||||
from . import forms
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLoginView(FormView):
|
||||
template_name = 'users/login.html'
|
||||
form_class = forms.UserLoginForm
|
||||
redirect_field_name = 'next'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(self.get_success_url())
|
||||
return super(UserLoginView, self).get(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
auth_login(self.request, form.get_user())
|
||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
write_login_log_async.delay(self.request.user.username, self.request.user.name,
|
||||
login_type='W', login_ip=login_ip, user_agent=user_agent)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.user.is_first_login:
|
||||
return reverse('users:user-first-login')
|
||||
|
||||
return self.request.POST.get(
|
||||
self.redirect_field_name,
|
||||
self.request.GET.get(self.redirect_field_name, reverse('index')))
|
||||
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
return super(UserLogoutView, self).get(request)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Logout success'),
|
||||
'messages': _('Logout success, return login page'),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserLogoutView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'users/user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserListView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Users'),
|
||||
'action': _('User list'),
|
||||
'groups': UserGroup.objects.all()
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = User
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_create.html'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
success_message = _('Create user <a href="%s">%s</a> successfully.')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserCreateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('Create user')})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user = form.save(commit=False)
|
||||
user.created_by = self.request.user.username or 'System'
|
||||
user.save()
|
||||
user_add_success_next(user)
|
||||
return super(UserCreateView, self).form_valid(form)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return self.success_message % (
|
||||
reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk}),
|
||||
self.object.name,
|
||||
)
|
||||
|
||||
|
||||
class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = User
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_update.html'
|
||||
context_object_name = 'user_object'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
username = self.object.username
|
||||
user = form.save(commit=False)
|
||||
user.username = username
|
||||
user.save()
|
||||
password = self.request.POST.get('password', '')
|
||||
if password:
|
||||
user.set_password(password)
|
||||
return super(UserUpdateView, self).form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserUpdateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('Update user')})
|
||||
return context
|
||||
|
||||
|
||||
class UserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_detail.html'
|
||||
context_object_name = "user"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
|
||||
context = {'app': _('Users'), 'action': _('User detail'), 'groups': groups}
|
||||
kwargs.update(context)
|
||||
return super(UserDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'users/user_group_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserGroupListView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('User group list')})
|
||||
return context
|
||||
|
||||
|
||||
class UserGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = UserGroup
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserGroupCreateView, self).get_context_data(**kwargs)
|
||||
users = User.objects.all()
|
||||
context.update({'app': _('Users'), 'action': _('Create user group'), 'users': users})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user_group = form.save()
|
||||
users_id_list = self.request.POST.getlist('users', [])
|
||||
users = User.objects.filter(id__in=users_id_list)
|
||||
user_group.created_by = self.request.user.username or 'Admin'
|
||||
user_group.users.add(*users)
|
||||
user_group.save()
|
||||
return super(UserGroupCreateView, self).form_valid(form)
|
||||
|
||||
|
||||
class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = UserGroup
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# self.object = self.get_object()
|
||||
context = super(UserGroupUpdateView, self).get_context_data(**kwargs)
|
||||
users = User.objects.all()
|
||||
group_users = [user.id for user in self.object.users.all()]
|
||||
context.update({
|
||||
'app': _('Users'),
|
||||
'action': _('Update User Group'),
|
||||
'users': users,
|
||||
'group_users': group_users
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user_group = form.save()
|
||||
users_id_list = self.request.POST.getlist('users', [])
|
||||
users = User.objects.filter(id__in=users_id_list)
|
||||
user_group.users.clear()
|
||||
user_group.users.add(*users)
|
||||
user_group.save()
|
||||
return super(UserGroupUpdateView, self).form_valid(form)
|
||||
|
||||
|
||||
class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = UserGroup
|
||||
context_object_name = 'user_group'
|
||||
template_name = 'users/user_group_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
users = User.objects.exclude(id__in=self.object.users.all())
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('User Group Detail'),
|
||||
'users': users,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserForgotPasswordView(TemplateView):
|
||||
template_name = 'users/forgot_password.html'
|
||||
|
||||
def post(self, request):
|
||||
email = request.POST.get('email')
|
||||
user = get_object_or_none(User, email=email)
|
||||
if not user:
|
||||
return self.get(request, errors=_('Email address invalid, input again'))
|
||||
else:
|
||||
send_reset_password_mail(user)
|
||||
return HttpResponseRedirect(reverse('users:forgot-password-sendmail-success'))
|
||||
|
||||
|
||||
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Send reset password message'),
|
||||
'messages': _('Send reset password mail success, login your mail box and follow it '),
|
||||
'redirect_url': reverse('users:login'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserForgotPasswordSendmailSuccessView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserResetPasswordSuccessView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Reset password success'),
|
||||
'messages': _('Reset password success, return to login page'),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserResetPasswordSuccessView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserResetPasswordView(TemplateView):
|
||||
template_name = 'users/reset_password.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
token = request.GET.get('token')
|
||||
user = User.validate_reset_token(token)
|
||||
|
||||
if not user:
|
||||
kwargs.update({'errors': _('Token invalid or expired')})
|
||||
return super(UserResetPasswordView, self).get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
password = request.POST.get('password')
|
||||
password_confirm = request.POST.get('password-confirm')
|
||||
token = request.GET.get('token')
|
||||
|
||||
if password != password_confirm:
|
||||
return self.get(request, errors=_('Password not same'))
|
||||
|
||||
user = User.validate_reset_token(token)
|
||||
if not user:
|
||||
return self.get(request, errors=_('Token invalid or expired'))
|
||||
|
||||
user.reset_password(password)
|
||||
return HttpResponseRedirect(reverse('users:reset-password-success'))
|
||||
|
||||
|
||||
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||
template_name = 'users/first_login.html'
|
||||
form_list = [forms.UserInfoForm, forms.UserKeyForm]
|
||||
file_storage = default_storage
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_authenticated() and not request.user.is_first_login:
|
||||
return redirect(reverse('index'))
|
||||
return super(UserFirstLoginView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def done(self, form_list, form_dict, **kwargs):
|
||||
user = self.request.user
|
||||
for form in form_list:
|
||||
for field in form:
|
||||
if field.value():
|
||||
setattr(user, field.name, field.value())
|
||||
if field.name == 'enable_otp':
|
||||
user.enable_otp = field.value()
|
||||
user.is_first_login = False
|
||||
user.is_public_key_valid = True
|
||||
user.save()
|
||||
return redirect(reverse('index'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserFirstLoginView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('First Login')})
|
||||
return context
|
||||
|
||||
def get_form_initial(self, step):
|
||||
user = self.request.user
|
||||
if step == '0':
|
||||
return {
|
||||
'name': user.name or user.username,
|
||||
'enable_otp': user.enable_otp or True,
|
||||
'wechat': user.wechat or '',
|
||||
'phone': user.phone or ''
|
||||
}
|
||||
return super(UserFirstLoginView, self).get_form_initial(step)
|
||||
|
||||
def get_form(self, step=None, data=None, files=None):
|
||||
form = super(UserFirstLoginView, self).get_form(step, data, files)
|
||||
|
||||
if step is None:
|
||||
step = self.steps.current
|
||||
|
||||
if step == '1':
|
||||
form.user = self.request.user
|
||||
return form
|
||||
|
||||
|
||||
class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
||||
model = User
|
||||
template_name = 'users/user_asset_permission.html'
|
||||
context_object_name = 'user'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
'action': 'User asset permissions',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserAssetPermissionView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
||||
model = UserGroup
|
||||
template_name = 'users/user_group_asset_permission.html'
|
||||
context_object_name = 'user_group'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
'action': 'User group asset permissions',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupAssetPermissionView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user = self.get_object(queryset=User.objects.all())
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': user.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionCreateView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserAssetPermissionCreateView, self).get_form(form_class=form_class)
|
||||
form.user = self.user
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': self.user.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-asset-permission', kwargs={'pk': self.user.id})
|
||||
|
||||
|
||||
class UserGroupAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = forms.UserGroupPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return redirect(reverse('users:user-group-asset-permission', kwargs={'pk': user_group.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionCreateView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserGroupAssetPermissionCreateView, self).get_form(form_class=form_class)
|
||||
form.user_group = self.user_group
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return redirect(reverse('users:user-group-asset-permission', kwargs={'pk': self.user_group.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-group-asset-permission', kwargs={'pk': self.user_group.id})
|
||||
|
||||
|
||||
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_granted_asset.html'
|
||||
context_object_name = 'user'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserGrantedAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User granted asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGrantedAssetView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_group_granted_asset.html'
|
||||
context_object_name = 'user_group'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupGrantedAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User group granted asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupGrantedAssetView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
|
||||
def form_invalid(self, form):
|
||||
try:
|
||||
error = form.errors.values()[-1][-1]
|
||||
except Exception as e:
|
||||
print e
|
||||
error = _('Invalid file.')
|
||||
data = {
|
||||
'success': False,
|
||||
'msg': error
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
wb = load_workbook(form.cleaned_data['file'])
|
||||
ws = wb.get_active_sheet()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
data = {'valid': False, 'msg': 'Not a valid Excel file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
rows = ws.rows
|
||||
header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
|
||||
header = [col.value for col in next(rows)]
|
||||
print(header)
|
||||
if header != header_need:
|
||||
data = {'valid': False, 'msg': 'Must be same format as template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created = []
|
||||
updated = []
|
||||
failed = []
|
||||
for row in rows:
|
||||
user_dict = dict(zip(header, [col.value for col in row]))
|
||||
groups_name = user_dict.pop('groups')
|
||||
if groups_name:
|
||||
groups_name = groups_name.split(',')
|
||||
groups = UserGroup.objects.filter(name__in=groups_name)
|
||||
else:
|
||||
groups = None
|
||||
try:
|
||||
user = User.objects.create(**user_dict)
|
||||
user_add_success_next(user)
|
||||
created.append(user_dict['username'])
|
||||
except IntegrityError as e:
|
||||
user = User.objects.filter(username=user_dict['username'])
|
||||
if not user:
|
||||
failed.append(user_dict['username'])
|
||||
continue
|
||||
user.update(**user_dict)
|
||||
user = user[0]
|
||||
updated.append(user_dict['username'])
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
failed.append(user_dict['username'])
|
||||
user = None
|
||||
|
||||
if user and groups:
|
||||
user.groups.add(*tuple(groups))
|
||||
user.save()
|
||||
|
||||
data = {
|
||||
'created': created,
|
||||
'created_info': 'Created {}'.format(len(created)),
|
||||
'updated': updated,
|
||||
'updated_info': 'Updated {}'.format(len(updated)),
|
||||
'failed': failed,
|
||||
'failed_info': 'Failed {}'.format(len(failed)),
|
||||
'valid': True,
|
||||
'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed))
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class UserExportView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
spm = request.GET.get('spm', '')
|
||||
users_id = cache.get(spm)
|
||||
if not users_id and not isinstance(users_id, list):
|
||||
return HttpResponse('May be expired', status=404)
|
||||
|
||||
users = User.objects.filter(id__in=users_id)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'User'
|
||||
header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
|
||||
ws.append(header)
|
||||
|
||||
for user in users:
|
||||
ws.append([user.name, user.username, user.email,
|
||||
','.join([group.name for group in user.groups.all()]),
|
||||
user.role, user.phone, user.wechat, user.comment])
|
||||
|
||||
filename = 'users-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type='applications/vnd.ms-excel')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
users_id = json.loads(request.body).get('users_id', [])
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().get_hex()
|
||||
cache.set(spm, users_id, 300)
|
||||
url = reverse('users:user-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from .login import *
|
||||
from .user import *
|
||||
from .group import *
|
|
@ -0,0 +1,169 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView, FormMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from perms.models import AssetPermission
|
||||
from ..models import User, UserGroup
|
||||
from ..utils import AdminUserRequiredMixin
|
||||
from .. import forms
|
||||
|
||||
__all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
|
||||
'UserGroupUpdateView', 'UserGroupAssetPermissionCreateView',
|
||||
'UserGroupAssetPermissionView', 'UserGroupGrantedAssetView']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'users/user_group_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserGroupListView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('User group list')})
|
||||
return context
|
||||
|
||||
|
||||
class UserGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = UserGroup
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserGroupCreateView, self).get_context_data(**kwargs)
|
||||
users = User.objects.all()
|
||||
context.update({'app': _('Users'), 'action': _('Create user group'),
|
||||
'users': users})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user_group = form.save()
|
||||
users_id_list = self.request.POST.getlist('users', [])
|
||||
users = User.objects.filter(id__in=users_id_list)
|
||||
user_group.created_by = self.request.user.username or 'Admin'
|
||||
user_group.users.add(*users)
|
||||
user_group.save()
|
||||
return super(UserGroupCreateView, self).form_valid(form)
|
||||
|
||||
|
||||
class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = UserGroup
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# self.object = self.get_object()
|
||||
context = super(UserGroupUpdateView, self).get_context_data(**kwargs)
|
||||
users = User.objects.all()
|
||||
group_users = [user.id for user in self.object.users.all()]
|
||||
context.update({
|
||||
'app': _('Users'),
|
||||
'action': _('Update User Group'),
|
||||
'users': users,
|
||||
'group_users': group_users
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user_group = form.save()
|
||||
users_id_list = self.request.POST.getlist('users', [])
|
||||
users = User.objects.filter(id__in=users_id_list)
|
||||
user_group.users.clear()
|
||||
user_group.users.add(*users)
|
||||
user_group.save()
|
||||
return super(UserGroupUpdateView, self).form_valid(form)
|
||||
|
||||
|
||||
class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = UserGroup
|
||||
context_object_name = 'user_group'
|
||||
template_name = 'users/user_group_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
users = User.objects.exclude(id__in=self.object.users.all())
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('User Group Detail'),
|
||||
'users': users,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupAssetPermissionView(AdminUserRequiredMixin, FormMixin,
|
||||
SingleObjectMixin, ListView):
|
||||
model = UserGroup
|
||||
template_name = 'users/user_group_asset_permission.html'
|
||||
context_object_name = 'user_group'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionView, self)\
|
||||
.get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
'action': 'User group asset permissions',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupAssetPermissionView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = forms.UserGroupPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return redirect(reverse('users:user-group-asset-permission',
|
||||
kwargs={'pk': user_group.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionCreateView, self)\
|
||||
.post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserGroupAssetPermissionCreateView, self)\
|
||||
.get_form(form_class=form_class)
|
||||
form.user_group = self.user_group
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return redirect(reverse('users:user-group-asset-permission',
|
||||
kwargs={'pk': self.user_group.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-group-asset-permission',
|
||||
kwargs={'pk': self.user_group.id})
|
||||
|
||||
|
||||
class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_group_granted_asset.html'
|
||||
context_object_name = 'user_group'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupGrantedAssetView, self)\
|
||||
.get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User group granted asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupGrantedAssetView, self).get_context_data(**kwargs)
|
|
@ -0,0 +1,202 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import User
|
||||
from ..utils import send_reset_password_mail
|
||||
from ..hands import write_login_log_async
|
||||
from .. import forms
|
||||
|
||||
|
||||
__all__ = ['UserLoginView', 'UserLogoutView',
|
||||
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
||||
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
||||
'UserFirstLoginView']
|
||||
|
||||
|
||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLoginView(FormView):
|
||||
template_name = 'users/login.html'
|
||||
form_class = forms.UserLoginForm
|
||||
redirect_field_name = 'next'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(self.get_success_url())
|
||||
return super(UserLoginView, self).get(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
auth_login(self.request, form.get_user())
|
||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
write_login_log_async.delay(self.request.user.username,
|
||||
self.request.user.name,
|
||||
login_type='W', login_ip=login_ip,
|
||||
user_agent=user_agent)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.user.is_first_login:
|
||||
return reverse('users:user-first-login')
|
||||
|
||||
return self.request.POST.get(
|
||||
self.redirect_field_name,
|
||||
self.request.GET.get(self.redirect_field_name, reverse('index')))
|
||||
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
return super(UserLogoutView, self).get(request)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Logout success'),
|
||||
'messages': _('Logout success, return login page'),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserLogoutView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserForgotPasswordView(TemplateView):
|
||||
template_name = 'users/forgot_password.html'
|
||||
|
||||
def post(self, request):
|
||||
email = request.POST.get('email')
|
||||
user = get_object_or_none(User, email=email)
|
||||
if not user:
|
||||
return self.get(request, errors=_('Email address invalid, '
|
||||
'please input again'))
|
||||
else:
|
||||
send_reset_password_mail(user)
|
||||
return HttpResponseRedirect(
|
||||
reverse('users:forgot-password-sendmail-success'))
|
||||
|
||||
|
||||
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Send reset password message'),
|
||||
'messages': _('Send reset password mail success, '
|
||||
'login your mail box and follow it '),
|
||||
'redirect_url': reverse('users:login'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserForgotPasswordSendmailSuccessView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserResetPasswordSuccessView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Reset password success'),
|
||||
'messages': _('Reset password success, return to login page'),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserResetPasswordSuccessView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserResetPasswordView(TemplateView):
|
||||
template_name = 'users/reset_password.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
token = request.GET.get('token')
|
||||
user = User.validate_reset_token(token)
|
||||
|
||||
if not user:
|
||||
kwargs.update({'errors': _('Token invalid or expired')})
|
||||
return super(UserResetPasswordView, self).get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
password = request.POST.get('password')
|
||||
password_confirm = request.POST.get('password-confirm')
|
||||
token = request.GET.get('token')
|
||||
|
||||
if password != password_confirm:
|
||||
return self.get(request, errors=_('Password not same'))
|
||||
|
||||
user = User.validate_reset_token(token)
|
||||
if not user:
|
||||
return self.get(request, errors=_('Token invalid or expired'))
|
||||
|
||||
user.reset_password(password)
|
||||
return HttpResponseRedirect(reverse('users:reset-password-success'))
|
||||
|
||||
|
||||
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||
template_name = 'users/first_login.html'
|
||||
form_list = [forms.UserInfoForm, forms.UserKeyForm]
|
||||
file_storage = default_storage
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_authenticated() and not request.user.is_first_login:
|
||||
return redirect(reverse('index'))
|
||||
return super(UserFirstLoginView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def done(self, form_list, form_dict, **kwargs):
|
||||
user = self.request.user
|
||||
for form in form_list:
|
||||
for field in form:
|
||||
if field.value():
|
||||
setattr(user, field.name, field.value())
|
||||
if field.name == 'enable_otp':
|
||||
user.enable_otp = field.value()
|
||||
user.is_first_login = False
|
||||
user.is_public_key_valid = True
|
||||
user.save()
|
||||
return redirect(reverse('index'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserFirstLoginView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('First Login')})
|
||||
return context
|
||||
|
||||
def get_form_initial(self, step):
|
||||
user = self.request.user
|
||||
if step == '0':
|
||||
return {
|
||||
'name': user.name or user.username,
|
||||
'enable_otp': user.enable_otp or True,
|
||||
'wechat': user.wechat or '',
|
||||
'phone': user.phone or ''
|
||||
}
|
||||
return super(UserFirstLoginView, self).get_form_initial(step)
|
||||
|
||||
def get_form(self, step=None, data=None, files=None):
|
||||
form = super(UserFirstLoginView, self).get_form(step, data, files)
|
||||
|
||||
if step is None:
|
||||
step = self.steps.current
|
||||
|
||||
if step == '1':
|
||||
form.user = self.request.user
|
||||
return form
|
|
@ -0,0 +1,300 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import uuid
|
||||
import json
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.writer.excel import save_virtual_workbook
|
||||
from openpyxl import load_workbook
|
||||
from django import forms
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView, FormMixin, \
|
||||
FormView
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_logger
|
||||
from perms.models import AssetPermission
|
||||
from ..models import User, UserGroup
|
||||
from ..utils import AdminUserRequiredMixin, user_add_success_next
|
||||
from .. import forms
|
||||
|
||||
__all__ = ['UserListView', 'UserCreateView', 'UserDetailView',
|
||||
'UserUpdateView', 'UserAssetPermissionCreateView',
|
||||
'UserAssetPermissionView', 'UserGrantedAssetView',
|
||||
'UserExportView', 'UserBulkImportView']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'users/user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserListView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Users'),
|
||||
'action': _('User list'),
|
||||
'groups': UserGroup.objects.all()
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = User
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_create.html'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
success_message = _('Create user <a href="%s">%s</a> successfully.')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserCreateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('Create user')})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
user = form.save(commit=False)
|
||||
user.created_by = self.request.user.username or 'System'
|
||||
user.save()
|
||||
user_add_success_next(user)
|
||||
return super(UserCreateView, self).form_valid(form)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return self.success_message % (
|
||||
reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk}),
|
||||
self.object.name,
|
||||
)
|
||||
|
||||
|
||||
class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = User
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_update.html'
|
||||
context_object_name = 'user_object'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
username = self.object.username
|
||||
user = form.save(commit=False)
|
||||
user.username = username
|
||||
user.save()
|
||||
password = self.request.POST.get('password', '')
|
||||
if password:
|
||||
user.set_password(password)
|
||||
return super(UserUpdateView, self).form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserUpdateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Users'), 'action': _('Update user')})
|
||||
return context
|
||||
|
||||
|
||||
class UserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_detail.html'
|
||||
context_object_name = "user"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('User detail'),
|
||||
'groups': groups
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class UserExportView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
spm = request.GET.get('spm', '')
|
||||
users_id = cache.get(spm)
|
||||
if not users_id and not isinstance(users_id, list):
|
||||
return HttpResponse('May be expired', status=404)
|
||||
|
||||
users = User.objects.filter(id__in=users_id)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'User'
|
||||
header = ["name", 'username', 'email', 'groups',
|
||||
"role", "phone", "wechat", "comment"]
|
||||
ws.append(header)
|
||||
|
||||
for user in users:
|
||||
ws.append([user.name, user.username, user.email,
|
||||
','.join([group.name for group in user.groups.all()]),
|
||||
user.role, user.phone, user.wechat, user.comment])
|
||||
|
||||
filename = 'users-{}.xlsx'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
response = HttpResponse(save_virtual_workbook(wb),
|
||||
content_type='applications/vnd.ms-excel')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
users_id = json.loads(request.body).get('users_id', [])
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().get_hex()
|
||||
cache.set(spm, users_id, 300)
|
||||
url = reverse('users:user-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
|
||||
|
||||
class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
|
||||
def form_invalid(self, form):
|
||||
try:
|
||||
error = form.errors.values()[-1][-1]
|
||||
except Exception as e:
|
||||
print e
|
||||
error = _('Invalid file.')
|
||||
data = {
|
||||
'success': False,
|
||||
'msg': error
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
wb = load_workbook(form.cleaned_data['file'])
|
||||
ws = wb.get_active_sheet()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
data = {'valid': False, 'msg': 'Not a valid Excel file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
rows = ws.rows
|
||||
header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
|
||||
header = [col.value for col in next(rows)]
|
||||
print(header)
|
||||
if header != header_need:
|
||||
data = {'valid': False, 'msg': 'Must be same format as template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created = []
|
||||
updated = []
|
||||
failed = []
|
||||
for row in rows:
|
||||
user_dict = dict(zip(header, [col.value for col in row]))
|
||||
groups_name = user_dict.pop('groups')
|
||||
if groups_name:
|
||||
groups_name = groups_name.split(',')
|
||||
groups = UserGroup.objects.filter(name__in=groups_name)
|
||||
else:
|
||||
groups = None
|
||||
try:
|
||||
user = User.objects.create(**user_dict)
|
||||
user_add_success_next(user)
|
||||
created.append(user_dict['username'])
|
||||
except User.IntegrityError as e:
|
||||
user = User.objects.filter(username=user_dict['username'])
|
||||
if not user:
|
||||
failed.append(user_dict['username'])
|
||||
continue
|
||||
user.update(**user_dict)
|
||||
user = user[0]
|
||||
updated.append(user_dict['username'])
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
failed.append(user_dict['username'])
|
||||
user = None
|
||||
|
||||
if user and groups:
|
||||
user.groups.add(*tuple(groups))
|
||||
user.save()
|
||||
|
||||
data = {
|
||||
'created': created,
|
||||
'created_info': 'Created {}'.format(len(created)),
|
||||
'updated': updated,
|
||||
'updated_info': 'Updated {}'.format(len(updated)),
|
||||
'failed': failed,
|
||||
'failed_info': 'Failed {}'.format(len(failed)),
|
||||
'valid': True,
|
||||
'msg': 'Created: {}. Updated: {}, Error: {}'.format(
|
||||
len(created), len(updated), len(failed))
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
||||
class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin,
|
||||
SingleObjectMixin, ListView):
|
||||
model = User
|
||||
template_name = 'users/user_asset_permission.html'
|
||||
context_object_name = 'user'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
'action': 'User asset permissions',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserAssetPermissionView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user = self.get_object(queryset=User.objects.all())
|
||||
return redirect(reverse('users:user-asset-permission',
|
||||
kwargs={'pk': user.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionCreateView, self)\
|
||||
.post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserAssetPermissionCreateView, self)\
|
||||
.get_form(form_class=form_class)
|
||||
form.user = self.user
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return redirect(reverse('users:user-asset-permission',
|
||||
kwargs={'pk': self.user.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-asset-permission',
|
||||
kwargs={'pk': self.user.id})
|
||||
|
||||
|
||||
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_granted_asset.html'
|
||||
context_object_name = 'user'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserGrantedAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User granted asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGrantedAssetView, self).get_context_data(**kwargs)
|
Loading…
Reference in New Issue