diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 0e8487b2a..409cc2d03 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -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/ diff --git a/apps/users/forms.py b/apps/users/forms.py index c7f2a96e8..a24565f89 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -71,11 +71,20 @@ class UserGroupForm(forms.ModelForm): class UserInfoForm(forms.Form): - name = forms.CharField(max_length=20) - wechat = forms.CharField(max_length=30) - phone = forms.CharField(max_length=20) - enable_otp = forms.BooleanField() + 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) + 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 diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html index 6d51ca435..915a1f425 100644 --- a/apps/users/templates/users/first_login.html +++ b/apps/users/templates/users/first_login.html @@ -13,7 +13,7 @@
-
Basic Wizzard
+
{% trans 'First Login' %}
@@ -33,32 +30,34 @@ {% for step in wizard.steps.all %} {% endfor %}
-
+ {% 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 %} -
-
+ {{ 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 %}
- {% if wizard.steps.prev %} - - - {% endif %} - +
+ +
@@ -66,3 +65,16 @@ {% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/users/templates/users/first_login.old.html b/apps/users/templates/users/first_login.old.html deleted file mode 100644 index b6b1cf838..000000000 --- a/apps/users/templates/users/first_login.old.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
-
-
Basic Wizzard
- -
-
-

- This is basic example of Step -

-
-

First Step

-
-
-

Hello in Step 1

-

- This is the first content. -

-
-
- -

Second Step

-
-
-

This is step 2

-

- This content is diferent than the first one. -

-
-
- -

Third Step

-
-
-

This is step 3

-

- This is last content. -

-
-
-
- -
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/users/utils.py b/apps/users/utils.py index 7b3222c29..9e9a34321 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -1,18 +1,18 @@ # ~*~ 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, ) 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()]) diff --git a/apps/users/views.py b/apps/users/views.py index c34d5fcf8..4bc044b12 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -6,11 +6,12 @@ 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, render +from django.shortcuts import get_object_or_404, reverse, redirect from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ from django.urls import reverse_lazy @@ -53,7 +54,7 @@ class UserLoginView(FormView): def get_success_url(self): if self.request.user.is_first_login: - return '/firstlogin' + return reverse('users:user-first-login') return self.request.POST.get( self.redirect_field_name, @@ -300,16 +301,40 @@ class UserResetPasswordView(TemplateView): return HttpResponseRedirect(reverse('users:reset-password-success')) -class UserFirstLoginView(SessionWizardView): +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): - 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')) 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)