Commit 89923333 authored by xiaokong1937@gmail.com's avatar xiaokong1937@gmail.com

#8 user first login view

parents 6069b894 a7e3f9c4
.DS_Store
*.pyc
*.pyo
*.swp
env
env*
dist
......
......@@ -108,6 +108,7 @@ TEMPLATES = [
# WSGI_APPLICATION = 'jumpserver.wsgi.application'
LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('users:login')
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
......@@ -227,7 +228,7 @@ USE_L10N = True
USE_TZ = True
# I18N translation
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'),]
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'), ]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
......
......@@ -18,6 +18,7 @@ class UserLoginForm(AuthenticationForm):
class UserCreateForm(forms.ModelForm):
class Meta:
model = User
fields = [
......@@ -67,3 +68,23 @@ class UserGroupForm(forms.ModelForm):
help_texts = {
'name': '* required'
}
class UserInfoForm(forms.Form):
name = forms.CharField(max_length=20, label=_('name'))
avatar = forms.ImageField(label=_('avatar'), required=False)
wechat = forms.CharField(max_length=30, label=_('wechat'), required=False)
phone = forms.CharField(max_length=20, label=_('phone'), required=False)
enable_otp = forms.BooleanField(required=False, label=_('enable otp'))
class UserKeyForm(forms.Form):
private_key = forms.CharField(max_length=5000, widget=forms.Textarea, label=_('private key'))
def clean_private_key(self):
from users.utils import validate_ssh_pk
ssh_pk = self.cleaned_data['private_key']
checked, reason = validate_ssh_pk(ssh_pk)
if not checked:
raise forms.ValidationError(_('Not a valid ssh private key.'))
return ssh_pk
......@@ -2,19 +2,17 @@
from __future__ import unicode_literals
import datetime
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.utils import timezone
from django.db import models
from django.contrib.auth.models import AbstractUser, Permission
from django.contrib.auth.models import AbstractUser
from django.core import signing
from django.db import models, IntegrityError
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import IntegrityError
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from rest_framework.authtoken.models import Token
from django.core import signing
from common.utils import encrypt, decrypt
......@@ -44,16 +42,15 @@ class UserGroup(models.Model):
@classmethod
def generate_fake(cls, count=100):
from random import seed, randint, choice
from random import seed, choice
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
group = cls(name=forgery_py.name.full_name(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by=choice(User.objects.all()).username
)
)
try:
group.save()
except IntegrityError:
......@@ -84,7 +81,7 @@ class User(AbstractUser):
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
_public_key = models.CharField(max_length=1000, blank=True, verbose_name=_('ssh public key'))
comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
is_first_login = models.BooleanField(default=False)
is_first_login = models.BooleanField(default=True)
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True,
verbose_name=_('Date expired'))
created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))
......@@ -235,7 +232,7 @@ class User(AbstractUser):
wechat=forgery_py.internet.user_name(True),
comment=forgery_py.lorem_ipsum.sentence(),
created_by=choice(cls.objects.all()).username,
)
)
try:
user.save()
except IntegrityError:
......@@ -264,4 +261,3 @@ def create_auth_token(sender, instance=None, created=False, **kwargs):
Token.objects.create(user=instance)
except IntegrityError:
pass
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% load bootstrap %}
{% block custom_head_css_js %}
{{ wizard.form.media }}
<link href="{% static 'css/plugins/steps/jquery.steps.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox">
<div class="ibox-title">
<h5>{% trans 'First Login' %}</h5>
<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>
</div>
</div>
<div class="ibox-content">
<div class="wizard">
<div class="steps clearfix">
<ul role="tablist">
{% for step in wizard.steps.all %}
<li role="tab" class="{% ifequal step wizard.steps.first %}first{% endifequal %} {% ifequal step wizard.steps.current %}current{% else %}disabled{% endifequal %} {% ifequal step wizard.steps.last %}last{% endifequal %}"
aria-disabled="false" aria-selected="true">
<a href="javascript:void(0)"><span class="number">{% trans 'Step' %} {{ step }}</span></a>
</li>
{% endfor %}
</ul>
</div>
<div class="content clearfix">
<form action="" method="post" class="form col-lg-8 p-m" id="fl_form">
{% csrf_token %}
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form|bootstrap }}
{% endfor %}
{% else %}
{{ wizard.form|bootstrap }}
{% endif %}
</form>
</div>
<div class="actions clearfix">
<ul>
{% if wizard.steps.prev %}
<li><a class="fl_goto" data-goto="{{ wizard.steps.first }}">{% trans "first step" %}</a></li>
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "prev step" %}</a></li>
{% endif %}
<li><a id="fl_submit">{% trans "submit" %}</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).on('click', ".fl_goto", function(){
var $form = $('#fl_form');
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
$form.submit();
return false;
}).on('click', '#fl_submit', function(){
$('#fl_form').submit();
return false;
})
</script>
{% endblock %}
......@@ -16,6 +16,8 @@ urlpatterns = [
name='reset-password-success'),
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'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
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]+)/delete$', views.UserDeleteView.as_view(), name='user-delete'),
......
# ~*~ coding: utf-8 ~*~
#
from __future__ import unicode_literals
import os
import logging
import os
import re
from paramiko.rsakey import RSAKey
from django.contrib.auth.mixins import UserPassesTestMixin
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from paramiko.rsakey import RSAKey
from common.tasks import send_mail_async
from common.utils import reverse
from users.models import User
try:
......@@ -125,5 +125,56 @@ def send_reset_password_mail(user):
send_mail_async.delay(subject, message, recipient_list, html_message=message)
def validate_ssh_pk(text):
"""
Expects a SSH private key as string.
Returns a boolean and a error message.
If the text is parsed as private key successfully,
(True,'') is returned. Otherwise,
(False, <message describing the error>) is returned.
from https://github.com/githubnemo/SSH-private-key-validator/blob/master/validate.py
"""
if not text:
return False, 'No text given'
startPattern = re.compile("^-----BEGIN [A-Z]+ PRIVATE KEY-----")
optionPattern = re.compile("^.+: .+")
contentPattern = re.compile("^([a-zA-Z0-9+/]{64}|[a-zA-Z0-9+/]{1,64}[=]{0,2})$")
endPattern = re.compile("^-----END [A-Z]+ PRIVATE KEY-----")
def contentState(text):
for i in range(0, len(text)):
line = text[i]
if endPattern.match(line):
if i == len(text) - 1 or len(text[i + 1]) == 0:
return True, ''
else:
return False, 'At end but content coming'
elif not contentPattern.match(line):
return False, 'Wrong string in content section'
return False, 'No content or missing end line'
def optionState(text):
for i in range(0, len(text)):
line = text[i]
if line[-1:] == '\\':
return optionState(text[i + 2:])
if not optionPattern.match(line):
return contentState(text[i + 1:])
return False, 'Expected option, found nothing'
def startState(text):
if len(text) == 0 or not startPattern.match(text[0]):
return False, 'Header is wrong'
return optionState(text[1:])
return startState([n.strip() for n in text.splitlines()])
......@@ -6,7 +6,9 @@ import logging
from django.conf import settings
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.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, reverse, redirect
......@@ -21,10 +23,12 @@ from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView
from django.views.generic.detail import DetailView
from formtools.wizard.views import SessionWizardView
from common.utils import get_object_or_none
from .models import User, UserGroup
from .forms import UserCreateForm, UserUpdateForm, UserGroupForm, UserLoginForm
from .forms import (UserCreateForm, UserUpdateForm, UserGroupForm, UserLoginForm, UserInfoForm, UserKeyForm)
from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_password_mail
......@@ -49,6 +53,9 @@ class UserLoginView(FormView):
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')))
......@@ -292,3 +299,42 @@ class UserResetPasswordView(TemplateView):
user.reset_password(password)
return HttpResponseRedirect(reverse('users:reset-password-success'))
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
template_name = 'users/first_login.html'
form_list = [UserInfoForm, 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.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)
......@@ -17,3 +17,4 @@ paramiko==2.0.2
celery==3.1.23
ansible==2.1.1.0
django-simple-captcha==0.5.2
django-formtools==1.0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment