Add a simple pawword sharing module
This commit is contained in:
parent
8c601c23a2
commit
d7af4cd80c
17 changed files with 502 additions and 2 deletions
1
Pipfile
1
Pipfile
|
@ -9,6 +9,7 @@ django-bulma = "*"
|
||||||
django-npb = "*"
|
django-npb = "*"
|
||||||
markdown = "*"
|
markdown = "*"
|
||||||
python-decouple = "*"
|
python-decouple = "*"
|
||||||
|
cryptography = ">=2.7,<2.8"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
gunicorn = "*"
|
gunicorn = "*"
|
||||||
|
|
|
@ -49,6 +49,7 @@ INSTALLED_APPS = [
|
||||||
"npb.apps.NpbConfig",
|
"npb.apps.NpbConfig",
|
||||||
"nsfw.apps.NsfwConfig",
|
"nsfw.apps.NsfwConfig",
|
||||||
"pages.apps.PagesConfig",
|
"pages.apps.PagesConfig",
|
||||||
|
"pwdb.apps.PwdbConfig",
|
||||||
"static_extra.apps.KhaganatStaticFilesConfig",
|
"static_extra.apps.KhaganatStaticFilesConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ urlpatterns += i18n_patterns(
|
||||||
path("", index),
|
path("", index),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("account/", include("neluser.urls")),
|
path("account/", include("neluser.urls")),
|
||||||
path("page/", include("pages.urls")),
|
|
||||||
path("paste/", include("npb.urls", namespace="npb")),
|
|
||||||
path("chat/", include("chat.urls")),
|
path("chat/", include("chat.urls")),
|
||||||
path("nsfw/", include("nsfw.urls")),
|
path("nsfw/", include("nsfw.urls")),
|
||||||
|
path("page/", include("pages.urls")),
|
||||||
|
path("paste/", include("npb.urls", namespace="npb")),
|
||||||
|
path("password_share/", include("pwdb.urls")),
|
||||||
)
|
)
|
||||||
|
|
0
pwdb/__init__.py
Normal file
0
pwdb/__init__.py
Normal file
26
pwdb/admin.py
Normal file
26
pwdb/admin.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import SharedPassword, SharedPasswordAccess
|
||||||
|
from .forms import NewSharedPasswordForm, EditSharedPasswordForm
|
||||||
|
|
||||||
|
|
||||||
|
class SharedPasswordAdmin(admin.ModelAdmin):
|
||||||
|
form = NewSharedPasswordForm
|
||||||
|
exclude = ["iv", "encrypted_password"]
|
||||||
|
list_display = ("name", "users")
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
if obj is None:
|
||||||
|
kwargs["form"] = NewSharedPasswordForm
|
||||||
|
else:
|
||||||
|
kwargs["form"] = EditSharedPasswordForm
|
||||||
|
return super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(SharedPassword, SharedPasswordAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
class SharedPasswordAccessAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("password", "user")
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(SharedPasswordAccess, SharedPasswordAccessAdmin)
|
5
pwdb/apps.py
Normal file
5
pwdb/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PwdbConfig(AppConfig):
|
||||||
|
name = "pwdb"
|
75
pwdb/forms.py
Normal file
75
pwdb/forms.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django import forms
|
||||||
|
from .models import SharedPassword
|
||||||
|
|
||||||
|
|
||||||
|
class AuthForm(forms.Form):
|
||||||
|
pwdb_check = forms.CharField(widget=forms.PasswordInput, label="")
|
||||||
|
|
||||||
|
|
||||||
|
class NewSharedPasswordForm(forms.ModelForm):
|
||||||
|
name = forms.CharField(max_length=512, label=_("Name"))
|
||||||
|
url = forms.CharField(
|
||||||
|
max_length=512, widget=forms.URLInput, required=False, label="URL"
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
widget=forms.Textarea, required=False, label=_("Description")
|
||||||
|
)
|
||||||
|
password = forms.CharField(
|
||||||
|
max_length=1024, widget=forms.PasswordInput, label=_("Password")
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_m2m(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
if self.errors:
|
||||||
|
raise ValueError(
|
||||||
|
"The %s could not be %s because the data didn't validate."
|
||||||
|
% (
|
||||||
|
self.instance._meta.object_name,
|
||||||
|
"created" if self.instance._state.adding else "changed",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
password = SharedPassword.new(
|
||||||
|
self.cleaned_data["name"], self.cleaned_data["password"]
|
||||||
|
)
|
||||||
|
if self.cleaned_data["url"]:
|
||||||
|
password.url = self.cleaned_data["url"]
|
||||||
|
if self.cleaned_data["description"]:
|
||||||
|
password.description = self.cleaned_data["description"]
|
||||||
|
password.save()
|
||||||
|
return password
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SharedPassword
|
||||||
|
exclude = ["iv", "encrypted_password"]
|
||||||
|
|
||||||
|
|
||||||
|
class EditSharedPasswordForm(forms.ModelForm):
|
||||||
|
name = forms.CharField(max_length=512, label=_("Name"))
|
||||||
|
password = forms.CharField(
|
||||||
|
max_length=1024, widget=forms.PasswordInput, required=False, label=_("Password")
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_m2m(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
if self.errors:
|
||||||
|
raise ValueError(
|
||||||
|
"The %s could not be %s because the data didn't validate."
|
||||||
|
% (
|
||||||
|
self.instance._meta.object_name,
|
||||||
|
"created" if self.instance._state.adding else "changed",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
password = self.instance
|
||||||
|
if self.cleaned_data["password"]:
|
||||||
|
password.set_password(self.cleaned_data["password"])
|
||||||
|
password.save()
|
||||||
|
return password
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SharedPassword
|
||||||
|
exclude = ["iv", "encrypted_password"]
|
73
pwdb/locale/en/LC_MESSAGES/django.po
Normal file
73
pwdb/locale/en/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-07-27 16:41+0200\n"
|
||||||
|
"PO-Revision-Date: 2019-07-27 16:41+0200\n"
|
||||||
|
"Last-Translator: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language-Team: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: forms.py:11 forms.py:46 templates/pwdb/list_passwords.html:12
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms.py:13 templates/pwdb/list_passwords.html:13
|
||||||
|
msgid "Description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms.py:15 forms.py:48 templates/pwdb/list_passwords.html:15
|
||||||
|
msgid "Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: models.py:102
|
||||||
|
msgid "shared_password"
|
||||||
|
msgstr "Shared password"
|
||||||
|
|
||||||
|
#: models.py:103 templates/pwdb/list_passwords.html:4
|
||||||
|
msgid "shared_passwords"
|
||||||
|
msgstr "Shared passwords"
|
||||||
|
|
||||||
|
#: models.py:116
|
||||||
|
msgid "shared_password_access"
|
||||||
|
msgstr "Shared password access"
|
||||||
|
|
||||||
|
#: models.py:117
|
||||||
|
msgid "shared_passwords_access"
|
||||||
|
msgstr "Shared passwords access"
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:5
|
||||||
|
msgid "authenticate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:10
|
||||||
|
msgid "safety_enter_password"
|
||||||
|
msgstr "For safety reasons, please enter your password."
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:15
|
||||||
|
msgid "send"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:16
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:24
|
||||||
|
msgid "view_website"
|
||||||
|
msgstr "View website"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:26
|
||||||
|
msgid "copy_password"
|
||||||
|
msgstr "Copy"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:27
|
||||||
|
msgid "show_password"
|
||||||
|
msgstr "Show"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:28
|
||||||
|
msgid "hide_password"
|
||||||
|
msgstr "Hide"
|
73
pwdb/locale/fr/LC_MESSAGES/django.po
Normal file
73
pwdb/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-07-27 16:41+0200\n"
|
||||||
|
"PO-Revision-Date: 2019-07-27 16:41+0200\n"
|
||||||
|
"Last-Translator: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language-Team: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: forms.py:11 forms.py:46 templates/pwdb/list_passwords.html:12
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#: forms.py:13 templates/pwdb/list_passwords.html:13
|
||||||
|
msgid "Description"
|
||||||
|
msgstr "Description"
|
||||||
|
|
||||||
|
#: forms.py:15 forms.py:48 templates/pwdb/list_passwords.html:15
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Mot de passe"
|
||||||
|
|
||||||
|
#: models.py:102
|
||||||
|
msgid "shared_password"
|
||||||
|
msgstr "Mot de passe partagé"
|
||||||
|
|
||||||
|
#: models.py:103 templates/pwdb/list_passwords.html:4
|
||||||
|
msgid "shared_passwords"
|
||||||
|
msgstr "Mots de passe partagés"
|
||||||
|
|
||||||
|
#: models.py:116
|
||||||
|
msgid "shared_password_access"
|
||||||
|
msgstr "Accès au mot de passe partagé"
|
||||||
|
|
||||||
|
#: models.py:117
|
||||||
|
msgid "shared_passwords_access"
|
||||||
|
msgstr "Accès aux mots de passe partagés"
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:5
|
||||||
|
msgid "authenticate"
|
||||||
|
msgstr "Authentification"
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:10
|
||||||
|
msgid "safety_enter_password"
|
||||||
|
msgstr "À des fins de sécurité, veuillez entrer votre mot de passe."
|
||||||
|
|
||||||
|
#: templates/pwdb/authenticate.html:15
|
||||||
|
msgid "send"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:16
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Actions"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:24
|
||||||
|
msgid "view_website"
|
||||||
|
msgstr "Voir le site web"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:26
|
||||||
|
msgid "copy_password"
|
||||||
|
msgstr "Copier"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:27
|
||||||
|
msgid "show_password"
|
||||||
|
msgstr "Montrer"
|
||||||
|
|
||||||
|
#: templates/pwdb/list_passwords.html:28
|
||||||
|
msgid "hide_password"
|
||||||
|
msgstr "Masquer"
|
45
pwdb/migrations/0001_initial.py
Normal file
45
pwdb/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 2.2.3 on 2019-07-27 15:16
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SharedPassword',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=512)),
|
||||||
|
('url', models.CharField(blank=True, max_length=512)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('iv', models.BinaryField(max_length=16)),
|
||||||
|
('encrypted_password', models.BinaryField(max_length=2048)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'shared_password',
|
||||||
|
'verbose_name_plural': 'shared_passwords',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SharedPasswordAccess',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pwdb.SharedPassword')),
|
||||||
|
('user', models.ForeignKey(limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'shared_password_access',
|
||||||
|
'verbose_name_plural': 'shared_passwords_access',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
pwdb/migrations/__init__.py
Normal file
0
pwdb/migrations/__init__.py
Normal file
117
pwdb/models.py
Normal file
117
pwdb/models.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||||
|
from cryptography.hazmat.primitives import hashes, padding
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from neluser.models import NelUser
|
||||||
|
import secrets
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
KEY_LENGTH = 32
|
||||||
|
IV_LENGTH = 16
|
||||||
|
BLOCK_SIZE = 128
|
||||||
|
ENCODING = "UTF-8"
|
||||||
|
|
||||||
|
|
||||||
|
class SharedPassword(models.Model):
|
||||||
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
name = models.CharField(max_length=512)
|
||||||
|
url = models.CharField(max_length=512, blank=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
iv = models.BinaryField(max_length=IV_LENGTH)
|
||||||
|
encrypted_password = models.BinaryField(max_length=2048)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_key(pass_uuid, key=None):
|
||||||
|
backend = default_backend()
|
||||||
|
hkdf = HKDF(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=KEY_LENGTH,
|
||||||
|
salt=pass_uuid.bytes,
|
||||||
|
backend=backend,
|
||||||
|
info=None,
|
||||||
|
)
|
||||||
|
key = key or settings.SECRET_KEY
|
||||||
|
key = bytes(key, encoding=ENCODING)
|
||||||
|
return hkdf.derive(key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_cipher(key, iv):
|
||||||
|
backend = default_backend()
|
||||||
|
return Cipher(algorithms.Camellia(key), modes.CBC(iv), backend=backend)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def padd_password(clear_password):
|
||||||
|
clear_password = bytes(clear_password, encoding=ENCODING)
|
||||||
|
padder = padding.PKCS7(BLOCK_SIZE).padder()
|
||||||
|
padded_password = padder.update(clear_password) + padder.finalize()
|
||||||
|
return padded_password
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpadd_password(clear_password):
|
||||||
|
unpadder = padding.PKCS7(BLOCK_SIZE).unpadder()
|
||||||
|
unpadded_password = unpadder.update(clear_password) + unpadder.finalize()
|
||||||
|
return unpadded_password.decode()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new(name, clear_password):
|
||||||
|
password = SharedPassword(
|
||||||
|
uuid=uuid.uuid4(),
|
||||||
|
name=name,
|
||||||
|
url="",
|
||||||
|
description="",
|
||||||
|
iv=secrets.token_bytes(IV_LENGTH),
|
||||||
|
encrypted_password=b"",
|
||||||
|
)
|
||||||
|
password.set_password(clear_password)
|
||||||
|
return password
|
||||||
|
|
||||||
|
def set_password(self, clear_password):
|
||||||
|
clear_password = SharedPassword.padd_password(clear_password)
|
||||||
|
key = SharedPassword.get_key(self.uuid)
|
||||||
|
cipher = SharedPassword.get_cipher(key, self.iv)
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
self.encrypted_password = (
|
||||||
|
encryptor.update(clear_password) + encryptor.finalize()
|
||||||
|
)
|
||||||
|
|
||||||
|
def decrypt_password(self):
|
||||||
|
key = SharedPassword.get_key(self.uuid)
|
||||||
|
cipher = SharedPassword.get_cipher(key, self.iv)
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
clear_password = (
|
||||||
|
decryptor.update(self.encrypted_password) + decryptor.finalize()
|
||||||
|
)
|
||||||
|
clear_password = SharedPassword.unpadd_password(clear_password)
|
||||||
|
return clear_password
|
||||||
|
|
||||||
|
def users(self):
|
||||||
|
lst = (
|
||||||
|
SharedPasswordAccess.objects.select_related("user")
|
||||||
|
.filter(password=self)
|
||||||
|
.order_by("user__email")
|
||||||
|
)
|
||||||
|
return [e.user for e in lst]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("shared_password")
|
||||||
|
verbose_name_plural = _("shared_passwords")
|
||||||
|
|
||||||
|
|
||||||
|
class SharedPasswordAccess(models.Model):
|
||||||
|
user = models.ForeignKey(
|
||||||
|
NelUser, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}
|
||||||
|
)
|
||||||
|
password = models.ForeignKey(SharedPassword, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{}: {}".format(self.password, self.user)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("shared_password_access")
|
||||||
|
verbose_name_plural = _("shared_passwords_access")
|
17
pwdb/templates/pwdb/authenticate.html
Normal file
17
pwdb/templates/pwdb/authenticate.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "khaganat/centered_dialog.html" %}
|
||||||
|
{% load bulma_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "authenticate"|capfirst %}{% endblock %}
|
||||||
|
{% block dialog_class %}is-link{% endblock %}
|
||||||
|
|
||||||
|
{% block dialog %}
|
||||||
|
<p>
|
||||||
|
{% trans "safety_enter_password" %}
|
||||||
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit" class="button is-link">{% trans "send"|capfirst %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
35
pwdb/templates/pwdb/list_passwords.html
Normal file
35
pwdb/templates/pwdb/list_passwords.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends "khaganat/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "shared_passwords" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-bloc">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-striped is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Description" %}</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>{% trans "Password" %}</th>
|
||||||
|
<th colspan="3">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pass in passwords %}
|
||||||
|
<tr>
|
||||||
|
<td class="has-text-weight-semibold">{{ pass.name }}</td>
|
||||||
|
<td class="has-text-weight-light">{{ pass.description }}</td>
|
||||||
|
<td>{% if pass.url %}<a href="{{ pass.url }}" target="_blank">{{ pass.url|truncatechars:30 }}</a>{% endif %}</td>
|
||||||
|
<td><input class="input" type="password" id="password_{{ pass.uuid }}" name="password_{{ pass.uuid }}" value="{{ pass.decrypt_password }}" /></td>
|
||||||
|
<td><a class="button is-primary" onclick="navigator.clipboard.writeText(document.getElementById('password_{{ pass.uuid }}').value).then(function() { console.log('Password copied to clipboard'); }, function(err) { console.error('Unable to copy password to clipboard'); });">{% trans "copy_password" %}</a></td>
|
||||||
|
<td><a id="show_{{ pass.uuid }}" class="button is-warning" onclick="document.getElementById('password_{{ pass.uuid }}').type = 'text';">{% trans "show_password" %}</a></td>
|
||||||
|
<td><a id="hide_{{ pass.uuid }}" class="button is-success" onclick="document.getElementById('password_{{ pass.uuid }}').type = 'password';">{% trans "hide_password" %}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
3
pwdb/tests.py
Normal file
3
pwdb/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
5
pwdb/urls.py
Normal file
5
pwdb/urls.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [path("", views.list_passwords, name="pwdb")]
|
23
pwdb/views.py
Normal file
23
pwdb/views.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.shortcuts import render
|
||||||
|
from .models import SharedPasswordAccess
|
||||||
|
from .forms import AuthForm
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
def list_passwords(request):
|
||||||
|
try:
|
||||||
|
pwd = request.POST["pwdb_check"]
|
||||||
|
user = authenticate(username=request.user, password=pwd)
|
||||||
|
assert user is not None
|
||||||
|
lst = (
|
||||||
|
SharedPasswordAccess.objects.select_related("password")
|
||||||
|
.filter(user__pk=user.pk)
|
||||||
|
.order_by("password__name")
|
||||||
|
)
|
||||||
|
ctx = {"passwords": [e.password for e in lst]}
|
||||||
|
return render(request, "pwdb/list_passwords.html", ctx)
|
||||||
|
except (KeyError, AssertionError):
|
||||||
|
ctx = {"form": AuthForm()}
|
||||||
|
return render(request, "pwdb/authenticate.html", ctx)
|
Loading…
Reference in a new issue