musician add webappusers and systemusers

This commit is contained in:
Jorge Pastor 2024-04-16 20:49:11 +02:00
parent ee7cf07294
commit 5c0c82d50b
8 changed files with 237 additions and 29 deletions

View file

@ -3,8 +3,11 @@ from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password
from orchestra.contrib.domains.models import Domain, Record
from orchestra.contrib.mailboxes.models import Address, Mailbox
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.contrib.musician.validators import ValidateZoneMixin
from . import api
@ -27,6 +30,42 @@ class LoginForm(AuthenticationForm):
return self.cleaned_data
class ChangePasswordForm(forms.ModelForm):
error_messages = {
'password_mismatch': _('The two password fields didnt match.'),
}
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
class Meta:
fields = ("password",)
model = WebappUsers
def clean_password2(self):
password = self.cleaned_data.get("password")
password2 = self.cleaned_data.get("password2")
if password and password2 and password != password2:
raise ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
cleaned_data['password'] = make_password(password)
return cleaned_data
class MailForm(forms.ModelForm):
class Meta:
@ -53,36 +92,12 @@ class MailForm(forms.ModelForm):
return instance
class MailboxChangePasswordForm(forms.ModelForm):
error_messages = {
'password_mismatch': _('The two password fields didnt match.'),
}
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
class MailboxChangePasswordForm(ChangePasswordForm):
class Meta:
fields = ("password",)
model = Mailbox
def clean_password2(self):
password = self.cleaned_data.get("password")
password2 = self.cleaned_data.get("password2")
if password and password2 and password != password2:
raise ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
class MailboxCreateForm(forms.ModelForm):
error_messages = {
@ -120,7 +135,7 @@ class MailboxCreateForm(forms.ModelForm):
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
return password
def save(self, commit=True):
instance = super().save(commit=False)
@ -169,3 +184,14 @@ class RecordUpdateForm(ValidateZoneMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.domain = self.instance.domain
class WebappUsersChangePasswordForm(ChangePasswordForm):
class Meta:
fields = ("password",)
model = WebappUsers
class SystemUsersChangePasswordForm(ChangePasswordForm):
class Meta:
fields = ("password",)
model = SystemUser

View file

@ -0,0 +1,15 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:systemuser-list' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "musician/users_base.html" %}
{% load bootstrap4 i18n %}
{% block tabcontent %}
<p></p>
<p>{% trans "The main user is your system's main user on each server. You'll be able to view the logs of your websites at (/home/account/logs) and all web content, but you'll never be able to edit content on a website." %}</p>
<p>{% trans "This user only has write permissions in their own directory." %}</p>
<table class="table service-list">
<colgroup>
<col span="1" style="width: 15%;">
<col span="1" style="width: 25%;">
<col span="1" style="width: 60%;">
</colgroup>
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Username" %}</th>
<th scope="col">{% trans "Path" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for systemuser in object_list %}
{% if systemuser.is_main %}
<tr>
<td>{{ systemuser.username }}</td>
<td>{{ systemuser.home }}/{{ systemuser.username }}</td>
<td>
<div class="d-flex justify-content-end">
<a class="btn btn-outline-warning" href="{% url 'musician:systemuser-password' systemuser.id %}">
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
</div>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
{% if active_domain %}
<a class="btn-arrow-left" href="{% url 'musician:systemuser-list' %}">{% trans "Go to global" %}</a>
{% endif %}
<h1 class="service-name">{{ service.verbose_name }}
{% if active_domain %}<span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}
</h1>
<p class="service-description">{{ service.description }}</p>
{% with request.resolver_match.url_name as url_name %}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link {% if url_name == 'systemuser-list' %}active{% endif %}" href="{% url 'musician:systemuser-list' %}" role="tab"
aria-selected="{% if url_name == 'systemuser-list' %}true{% else %}false{% endif %}">{% trans "Main User" %}</a>
</li>
<li class="nav-item">
<a class="nav-link {% if url_name == 'webappuser-list' %}active{% endif %}" href="{% url 'musician:webappuser-list' %}" role="tab"
aria-selected="{% if url_name == 'webappuser-list' %}true{% else %}false{% endif %}">{% trans "SFTP Users" %}</a>
</li>
</ul>
{% endwith %}
<div class="tab-content" id="myTabContent">
{% block tabcontent %}
{% endblock %}
{% endblock %}

View file

@ -0,0 +1,15 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:webappuser-list' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -0,0 +1,37 @@
{% extends "musician/users_base.html" %}
{% load bootstrap4 i18n %}
{% block tabcontent %}
<table class="table service-list">
<colgroup>
<col span="1" style="width: 15%;">
<col span="1" style="width: 25%;">
<col span="1" style="width: 40%;">
<col span="1" style="width: 20%;">
</colgroup>
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Username" %}</th>
<th scope="col">{% trans "Path" %}</th>
<th scope="col">{% trans "Server" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for webappuser in object_list %}
<tr>
<td>{{ webappuser.username }}</td>
<td>/home/{{ webappuser.account }}/webapps/{{ webappuser.home }}</td>
<td>{{ webappuser.target_server }}</td>
<td>
<a class="btn btn-outline-warning" href="{% url 'musician:webappuser-password' webappuser.id %}">
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
{% include "musician/components/table_paginator.html" %}
</table>
{% endblock %}

View file

@ -39,4 +39,9 @@ urlpatterns = [
path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
path('databases/', views.DatabaseListView.as_view(), name='database-list'),
path('saas/', views.SaasListView.as_view(), name='saas-list'),
path('webappusers/', views.WebappUserListView.as_view(), name='webappuser-list'),
path('webappuser/<int:pk>/change-password/', views.WebappUserChangePasswordView.as_view(), name='webappuser-password'),
path('systemusers/', views.SystemUserListView.as_view(), name='systemuser-list'),
path('systemuser/<int:pk>/change-password/', views.SystemUserChangePasswordView.as_view(), name='systemuser-password'),
]

View file

@ -32,12 +32,14 @@ from orchestra.contrib.lists.models import List
from orchestra.contrib.mailboxes.models import Address, Mailbox
from orchestra.contrib.resources.models import Resource, ResourceData
from orchestra.contrib.saas.models import SaaS
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.utils.html import html_to_pdf
from .auth import logout as auth_logout
from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm,
MailboxSearchForm, MailboxUpdateForm, MailForm,
RecordCreateForm, RecordUpdateForm)
RecordCreateForm, RecordUpdateForm, WebappUsersChangePasswordForm,
SystemUsersChangePasswordForm)
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin)
from .models import Address as AddressService
@ -612,3 +614,39 @@ class LogoutView(RedirectView):
def post(self, request, *args, **kwargs):
"""Logout may be done via POST."""
return self.get(request, *args, **kwargs)
class WebappUserListView(ServiceListView):
model = WebappUsers
template_name = "musician/webappuser_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Webapp users'),
}
class WebappUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
template_name = "musician/webappuser_change_password.html"
model = WebappUsers
form_class = WebappUsersChangePasswordForm
success_url = reverse_lazy("musician:webappuser-list")
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
class SystemUserListView(ServiceListView):
model = SystemUser
template_name = "musician/systemuser_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Main users'),
}
class SystemUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
template_name = "musician/systemuser_change_password.html"
model = SystemUser
form_class = SystemUsersChangePasswordForm
success_url = reverse_lazy("musician:systemuser-list")
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)