mirror of
https://github.com/retspen/webvirtcloud
synced 2024-10-31 19:44:16 +00:00
Implemented OTP #341
This commit is contained in:
parent
cbac82ba07
commit
0052323190
7 changed files with 129 additions and 19 deletions
|
@ -1,6 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load icons %}
|
||||
{% load qr_code %}
|
||||
|
||||
{% block title %}{% trans "User Profile" %} - {{ user }}{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
|
@ -23,6 +26,11 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#public-keys">{% trans "Public Keys" %}</a>
|
||||
</li>
|
||||
{% if totp_url %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#otp">{% trans "OTP QR Code" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
@ -79,5 +87,12 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if totp_url %}
|
||||
<div class="tab-pane fade" id="otp">
|
||||
<div class="text-center">
|
||||
{% qr_from_text totp_url image_format="png" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
62
accounts/templates/accounts/otp_login.html
Normal file
62
accounts/templates/accounts/otp_login.html
Normal file
|
@ -0,0 +1,62 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap4 %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="WebVirtMgr panel for manage virtual machine">
|
||||
<meta name="author" content="anatoliy.guskov@gmail.com">
|
||||
|
||||
<title>{% trans "WebVirtCloud" %} - {% trans "Sign In" %}</title>
|
||||
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="{% static "css/wvc-main.min.css" %}" rel="stylesheet" id="wvc_css">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div>
|
||||
<div class="page-header text-center">
|
||||
<a class="" href="/"><h1>WebVirtCloud</h1></a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 offset-3" role="main">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if form.errors %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% endif %}
|
||||
<form class="form-signin" method="post" role="form" aria-label="Login form">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.username layout='horizontal' %}
|
||||
{% bootstrap_field form.password layout='horizontal' %}
|
||||
{% bootstrap_field form.otp_token layout='horizontal' %}
|
||||
<button class="btn btn-lg btn-success btn-block" type="submit">{% trans "Sign In" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{% static "js/jquery.js" %}"></script>
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script src="{% static "js/bootstrap.bundle.min.js" %}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,11 +1,12 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
from django.urls import path
|
||||
from django_otp.forms import OTPAuthenticationForm
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(template_name='logout.html'), name='logout'),
|
||||
path('logout/', LogoutView.as_view(template_name='logout.html'), name='logout'),
|
||||
path('profile/', views.profile, name='profile'),
|
||||
path('profile/<int:user_id>/', views.account, name='account'),
|
||||
path('change_password/', views.change_password, name='change_password'),
|
||||
|
@ -13,3 +14,12 @@ urlpatterns = [
|
|||
path('user_instance/<int:pk>/update/', views.user_instance_update, name='user_instance_update'),
|
||||
path('user_instance/<int:pk>/delete/', views.user_instance_delete, name='user_instance_delete'),
|
||||
]
|
||||
|
||||
if settings.OTP_ENABLED:
|
||||
urlpatterns += path(
|
||||
'login/',
|
||||
LoginView.as_view(template_name='accounts/otp_login.html', authentication_form=OTPAuthenticationForm),
|
||||
name='login',
|
||||
),
|
||||
else:
|
||||
urlpatterns += path('login/', LoginView.as_view(template_name='login.html'), name='login'),
|
||||
|
|
9
accounts/utils.py
Normal file
9
accounts/utils.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django_otp import devices_for_user
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
|
||||
|
||||
def get_user_totp_device(user):
|
||||
devices = devices_for_user(user)
|
||||
for device in devices:
|
||||
if isinstance(device, TOTPDevice):
|
||||
return device
|
|
@ -1,20 +1,18 @@
|
|||
import os
|
||||
|
||||
from admin.decorators import superuser_only
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.core.validators import ValidationError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from accounts.models import *
|
||||
from admin.decorators import superuser_only
|
||||
from instances.models import Instance
|
||||
|
||||
from accounts.models import *
|
||||
|
||||
from . import forms
|
||||
from .utils import get_user_totp_device
|
||||
|
||||
|
||||
def profile(request):
|
||||
|
@ -44,7 +42,10 @@ def profile(request):
|
|||
error_messages.append(msg)
|
||||
if not error_messages:
|
||||
addkeypublic = UserSSHKey(
|
||||
user_id=request.user.id, keyname=keyname, keypublic=keypublic)
|
||||
user_id=request.user.id,
|
||||
keyname=keyname,
|
||||
keypublic=keypublic,
|
||||
)
|
||||
addkeypublic.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if "keydelete" in request.POST:
|
||||
|
@ -62,6 +63,11 @@ def account(request, user_id):
|
|||
user_insts = UserInstance.objects.filter(user_id=user_id)
|
||||
instances = Instance.objects.all().order_by("name")
|
||||
publickeys = UserSSHKey.objects.filter(user_id=user_id)
|
||||
if settings.OTP_ENABLED:
|
||||
device = get_user_totp_device(user)
|
||||
if not device:
|
||||
device = user.totpdevice_set.create()
|
||||
totp_url = device.config_url
|
||||
|
||||
return render(request, "account.html", locals())
|
||||
|
||||
|
@ -79,11 +85,7 @@ def change_password(request):
|
|||
messages.error(request, _("Wrong Data Provided"))
|
||||
else:
|
||||
form = PasswordChangeForm(request.user)
|
||||
return render(
|
||||
request,
|
||||
"accounts/change_password_form.html",
|
||||
{"form": form}
|
||||
)
|
||||
return render(request, "accounts/change_password_form.html", {"form": form})
|
||||
|
||||
|
||||
@superuser_only
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
beautifulsoup4==4.9.1
|
||||
beautifulsoup4==4.9.3
|
||||
Django==2.2.16
|
||||
django-bootstrap4==2.2.0
|
||||
django-icons==2.1.1
|
||||
django-login-required-middleware==0.5.0
|
||||
django-otp==1.0.1
|
||||
django-qr-code==1.3.1
|
||||
gunicorn==20.0.4
|
||||
importlib-metadata==1.7.0
|
||||
libsass==0.20.1
|
||||
libvirt-python==6.7.0
|
||||
libvirt-python==6.8.0
|
||||
lxml==4.5.2
|
||||
numpy==1.18.5
|
||||
numpy==1.19.2
|
||||
pytz==2020.1
|
||||
qrcode==6.1
|
||||
rwlock==0.0.7
|
||||
six==1.15.0
|
||||
soupsieve==2.0.1
|
||||
sqlparse==0.3.1
|
||||
sqlparse==0.4.1
|
||||
websockify==0.9.0
|
||||
zipp==3.3.0
|
||||
|
|
|
@ -23,6 +23,8 @@ INSTALLED_APPS = [
|
|||
'django.contrib.staticfiles',
|
||||
'bootstrap4',
|
||||
'django_icons',
|
||||
'django_otp',
|
||||
'django_otp.plugins.otp_totp',
|
||||
'accounts',
|
||||
'admin',
|
||||
'appsettings',
|
||||
|
@ -36,6 +38,7 @@ INSTALLED_APPS = [
|
|||
'storages',
|
||||
'secrets',
|
||||
'logs',
|
||||
'qr_code',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -45,6 +48,7 @@ MIDDLEWARE = [
|
|||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django_otp.middleware.OTPMiddleware',
|
||||
'login_required.middleware.LoginRequiredMiddleware',
|
||||
'django.contrib.auth.middleware.RemoteUserMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
|
@ -176,3 +180,6 @@ LIBVIRT_KEEPALIVE_COUNT = 5
|
|||
ALLOW_EMPTY_PASSWORD = False
|
||||
NEW_USER_DEFAULT_INSTANCES = []
|
||||
SHOW_PROFILE_EDIT_PASSWORD = True
|
||||
|
||||
OTP_ENABLED = False
|
||||
|
||||
|
|
Loading…
Reference in a new issue