#8 user first login view

parent d8fe59de
...@@ -108,6 +108,7 @@ TEMPLATES = [ ...@@ -108,6 +108,7 @@ TEMPLATES = [
# WSGI_APPLICATION = 'jumpserver.wsgi.application' # WSGI_APPLICATION = 'jumpserver.wsgi.application'
LOGIN_REDIRECT_URL = reverse_lazy('index') LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('users:login')
# Database # Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
...@@ -227,7 +228,7 @@ USE_L10N = True ...@@ -227,7 +228,7 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# I18N translation # I18N translation
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'),] LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'), ]
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/ # https://docs.djangoproject.com/en/1.10/howto/static-files/
......
...@@ -71,11 +71,20 @@ class UserGroupForm(forms.ModelForm): ...@@ -71,11 +71,20 @@ class UserGroupForm(forms.ModelForm):
class UserInfoForm(forms.Form): class UserInfoForm(forms.Form):
name = forms.CharField(max_length=20) name = forms.CharField(max_length=20, label=_('name'))
wechat = forms.CharField(max_length=30) avatar = forms.ImageField(label=_('avatar'), required=False)
phone = forms.CharField(max_length=20) wechat = forms.CharField(max_length=30, label=_('wechat'), required=False)
enable_otp = forms.BooleanField() phone = forms.CharField(max_length=20, label=_('phone'), required=False)
enable_otp = forms.BooleanField(required=False, label=_('enable otp'))
class UserKeyForm(forms.Form): class UserKeyForm(forms.Form):
private_key = forms.CharField(max_length=5000, widget=forms.Textarea) 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
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="ibox"> <div class="ibox">
<div class="ibox-title"> <div class="ibox-title">
<h5>Basic Wizzard</h5> <h5>{% trans 'First Login' %}</h5>
<div class="ibox-tools"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -21,9 +21,6 @@ ...@@ -21,9 +21,6 @@
<a class="dropdown-toggle" data-toggle="dropdown" href="#"> <a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i> <i class="fa fa-wrench"></i>
</a> </a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
...@@ -33,32 +30,34 @@ ...@@ -33,32 +30,34 @@
{% for step in wizard.steps.all %} {% 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 %}" <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"> aria-disabled="false" aria-selected="true">
<a aria-controls="wizard-p-{{ step }}" href="#wizard-h-{{ step }}" id="wizard-t-{{ step }}"><span class="number">{% trans 'Step' %} {{ step }}</span></a> <a href="javascript:void(0)"><span class="number">{% trans 'Step' %} {{ step }}</span></a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="content clearfix"> <div class="content clearfix">
<form action="" method="post" class="form col-lg-8 p-m"> <form action="" method="post" class="form col-lg-8 p-m" id="fl_form">
{% csrf_token %} {% csrf_token %}
{{ wizard.management_form }} {{ wizard.management_form }}
{% if wizard.form.forms %} {% if wizard.form.forms %}
{{ wizard.form.management_form }} {{ wizard.form.management_form }}
{% for form in wizard.form.forms %} {% for form in wizard.form.forms %}
{{ form|bootstrap }} {{ form|bootstrap }}
{% endfor %} {% endfor %}
{% else %} {% else %}
{{ wizard.form|bootstrap }} {{ wizard.form|bootstrap }}
{% endif %} {% endif %}
</div>
</div>
</form> </form>
</div> </div>
{% if wizard.steps.prev %} <div class="actions clearfix">
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button> <ul>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button> {% if wizard.steps.prev %}
{% endif %} <li><a class="fl_goto" data-goto="{{ wizard.steps.first }}">{% trans "first step" %}</a></li>
<input type="submit" value="{% trans "submit" %}"/> <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>
...@@ -66,3 +65,16 @@ ...@@ -66,3 +65,16 @@
</div> </div>
</div> </div>
{% endblock %} {% 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 %}
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/steps/jquery.steps.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/steps/jquery.steps.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>Basic Wizzard</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>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<p>
This is basic example of Step
</p>
<div id="wizard">
<h1>First Step</h1>
<div class="step-content">
<div class="text-center m-t-md">
<h2>Hello in Step 1</h2>
<p>
This is the first content.
</p>
</div>
</div>
<h1>Second Step</h1>
<div class="step-content">
<div class="text-center m-t-md">
<h2>This is step 2</h2>
<p>
This content is diferent than the first one.
</p>
</div>
</div>
<h1>Third Step</h1>
<div class="step-content">
<div class="text-center m-t-md">
<h2>This is step 3</h2>
<p>
This is last content.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
$("#wizard").steps();
})
</script>
{% endblock %}
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import logging import logging
import os
import re
from paramiko.rsakey import RSAKey
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from paramiko.rsakey import RSAKey
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse from common.utils import reverse
from users.models import User
try: try:
...@@ -125,5 +125,56 @@ def send_reset_password_mail(user): ...@@ -125,5 +125,56 @@ def send_reset_password_mail(user):
send_mail_async.delay(subject, message, recipient_list, html_message=message) 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,11 +6,12 @@ import logging ...@@ -6,11 +6,12 @@ import logging
from django.conf import settings from django.conf import settings
from django.contrib.auth import login as auth_login, logout as auth_logout 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.contrib.messages.views import SuccessMessageMixin
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, reverse, redirect, render from django.shortcuts import get_object_or_404, reverse, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
...@@ -53,7 +54,7 @@ class UserLoginView(FormView): ...@@ -53,7 +54,7 @@ class UserLoginView(FormView):
def get_success_url(self): def get_success_url(self):
if self.request.user.is_first_login: if self.request.user.is_first_login:
return '/firstlogin' return reverse('users:user-first-login')
return self.request.POST.get( return self.request.POST.get(
self.redirect_field_name, self.redirect_field_name,
...@@ -300,16 +301,40 @@ class UserResetPasswordView(TemplateView): ...@@ -300,16 +301,40 @@ class UserResetPasswordView(TemplateView):
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))
class UserFirstLoginView(SessionWizardView): class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
template_name = 'users/first_login.html' template_name = 'users/first_login.html'
form_list = [UserInfoForm, UserKeyForm] form_list = [UserInfoForm, UserKeyForm]
file_storage = default_storage 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): def done(self, form_list, form_dict, **kwargs):
print form_list 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')) return redirect(reverse('index'))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UserFirstLoginView, self).get_context_data(**kwargs) context = super(UserFirstLoginView, self).get_context_data(**kwargs)
context.update({'app': _('Users'), 'action': _('First Login')}) context.update({'app': _('Users'), 'action': _('First Login')})
return context 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)
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