提交 8a5a4f33 编写于 作者: X xiaoyu

fix #14

上级 8cdc4674
@import url("https://fonts.useso.com/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.useso.com/css?family=Roboto:400,300,500,700");
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700");
/*
*
* INSPINIA - Responsive Admin Theme
......
此差异已折叠。
!function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("<div/>").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'<button type="button">&times;</button>',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("<div/>"),M=e("<div/>"),B=e("<div/>"),q=e("<div/>"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)});
//# sourceMappingURL=toastr.js.map
{% load static %}
{% load static i18n %}
<!DOCTYPE html>
<html>
<head>
......@@ -20,6 +20,25 @@
<div id="page-wrapper" class="gray-bg">
{% include '_header_bar.html' %}
{% include '_message.html' %}
{% block first_login_message %}
{% if user.is_authenticated and user.is_first_login %}
<div class="alert alert-danger" style="margin: 20px auto 0px">
{% url 'users:user-first-login' as the_url %}
{% blocktrans %}
Your information was incomplete. Please click <a href="{{ the_url }}"> this link </a>to complete your information.
{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block update_public_key_message %}
{% if user.is_authenticated and not user.is_public_key_valid %}
<div class="alert alert-danger" style="margin: 20px auto 0px">
{% blocktrans %}
Your ssh-public-key has been expired. Please click <a href="#"> this link </a>to update your ssh-public-key.
{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block content %}{% endblock %}
{% include '_footer.html' %}
</div>
......@@ -28,4 +47,4 @@
</body>
{% include '_foot_js.html' %}
{% block custom_foot_js %} {% endblock %}
</html>
\ No newline at end of file
</html>
......@@ -5,7 +5,8 @@ import logging
from rest_framework import generics
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer, \
UserPKUpdateSerializer
from .models import User, UserGroup
......@@ -72,7 +73,17 @@ class UserResetPKApi(generics.UpdateAPIView):
def perform_update(self, serializer):
user = self.get_object()
user._public_key = ''
user.is_public_key_valid = False
user.save()
from .utils import send_reset_ssh_key_mail
send_reset_ssh_key_mail(user)
class UserUpdatePKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserPKUpdateSerializer
def perform_update(self, serializer):
user = self.get_object()
user.private_key = serializer.validated_data['_public_key']
user.save()
......@@ -79,9 +79,11 @@ class UserKeyForm(forms.Form):
help_text=_('Paste your id_ras.pub here.'))
def clean_public_key(self):
public_key = self.cleaned_data['public_key']
if self.user._public_key and public_key == self.user.public_key:
raise forms.ValidationError(_('Public key should not be the same as your old one.'))
from sshpubkeys import SSHKey
from sshpubkeys.exceptions import InvalidKeyException
public_key = self.cleaned_data['public_key']
ssh = SSHKey(public_key)
try:
ssh.parse()
......
......@@ -80,6 +80,7 @@ class User(AbstractUser):
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'))
is_public_key_valid = models.BooleanField(default=False)
@property
def password_raw(self):
......
......@@ -46,11 +46,18 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', '_private_key']
def validate__private_key(self, value):
from users.utils import validate_ssh_pk
checked, reason = validate_ssh_pk(value)
if not checked:
raise serializers.ValidationError(_('Not a valid ssh private key.'))
fields = ['id', '_public_key']
def validate__public_key(self, value):
from sshpubkeys import SSHKey
from sshpubkeys.exceptions import InvalidKeyException
ssh = SSHKey(value)
try:
ssh.parse()
except InvalidKeyException as e:
print e
raise serializers.ValidationError(_('Not a valid ssh public key'))
except NotImplementedError as e:
print e
raise serializers.ValidationError(_('Not a valid ssh public key'))
return value
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}user_reset_pk_modal{% endblock %}
{% block modal_title%}{% trans 'Reset User SSH Private Key' %}{% endblock %}
{% block modal_id %}user_update_pk_modal{% endblock %}
{% block modal_title%}{% trans "Update User SSH Public Key" %}{% endblock %}
{% block modal_body %}
<textarea id="txt_pk" class="form-control" cols="30" rows="10" placeholder="-----BEGIN RSA PRIVATE KEY-----"></textarea>
<textarea id="txt_pk" class="form-control" cols="30" rows="10" placeholder="ssh-rsa AAAAB3NzaC1yc2EAA....."></textarea>
{% endblock %}
{% block modal_confirm_id %}btn_user_reset_pk{% endblock %}
{% block modal_confirm_id %}btn_user_update_pk{% endblock %}
......@@ -3,10 +3,12 @@
{% 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 first_login_message %}{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
......
......@@ -160,6 +160,14 @@
</span>
</td>
</tr>
<tr>
<td>{% trans 'Update ssh key' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn_update_pk" style="width: 54px;" data-toggle="modal" data-target="#user_update_pk_modal">{% trans 'Update' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
......@@ -207,6 +215,7 @@
</div>
</div>
</div>
{% include 'users/_user_update_pk_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -352,6 +361,33 @@ $(document).ready(function() {
}, function() {
doReset();
});
}).on('click', '#btn_user_update_pk', function(){
var $this = $(this);
var pk = $('#txt_pk').val();
var the_url = '{% url "users:user-update-pk-api" pk=user_object.id %}';
var body = {'_public_key': pk};
var success = function() {
$('#txt_pk').val('');
$this.closest('.modal').modal('hide');
var msg = "{% trans 'Successfully updated the SSH public key.' %}";
swal("{% trans 'User SSH Public Key Update' %}", msg, "success");
};
var fail = function() {
var msg = "{% trans 'Failed to update the user\'s SSH public key.' %}";
swal({
title: "{% trans 'User SSH Public Key Update' %}",
text: msg,
type: "error",
showCancelButton: false,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: true
}, function () {
$('#txt_pk').focus();
}
);
}
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
});
</script>
{% endblock %}
......@@ -43,6 +43,7 @@ urlpatterns += [
api.UserAttributeApi.as_view(), name='user-patch-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
url(r'^v1/users/(?P<pk>\d+)/update-pk/$', api.UserUpdatePKApi.as_view(), name='user-update-pk-api'),
url(r'^v1/user-groups$', api.UserGroupListAddApi.as_view(), name='user-group-list-api'),
url(r'^v1/user-groups/(?P<pk>[0-9]+)$',
api.UserGroupDetailDeleteUpdateApi.as_view(), name='user-group-detail-api'),
......
......@@ -19,7 +19,7 @@ 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, ModelFormMixin, ProcessFormView, BaseCreateView
FormMixin
from django.views.generic.detail import DetailView
from formtools.wizard.views import SessionWizardView
......@@ -332,6 +332,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
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'))
......@@ -351,6 +352,16 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
}
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):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
......@@ -376,7 +387,7 @@ class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMix
def get_queryset(self):
asset_permissions = set(self.object.asset_permissions.all()) \
| self.get_asset_permission_inherit_from_user_group()
| self.get_asset_permission_inherit_from_user_group()
return list(asset_permissions)
def get_context_data(self, **kwargs):
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册