1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-24 15:15:22 +00:00

Implemented OTP #341

This commit is contained in:
Real-Gecko 2020-10-08 17:57:51 +06:00
parent cbac82ba07
commit 0052323190
7 changed files with 129 additions and 19 deletions

View file

@ -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 %}

View 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>

View file

@ -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
View 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

View file

@ -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

View file

@ -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

View file

@ -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