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.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ 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.domains.models import Domain, Record
from orchestra.contrib.mailboxes.models import Address, Mailbox from orchestra.contrib.mailboxes.models import Address, Mailbox
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.contrib.musician.validators import ValidateZoneMixin from orchestra.contrib.musician.validators import ValidateZoneMixin
from . import api from . import api
@ -27,6 +30,42 @@ class LoginForm(AuthenticationForm):
return self.cleaned_data 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 MailForm(forms.ModelForm):
class Meta: class Meta:
@ -53,37 +92,13 @@ class MailForm(forms.ModelForm):
return instance return instance
class MailboxChangePasswordForm(forms.ModelForm): class MailboxChangePasswordForm(ChangePasswordForm):
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: class Meta:
fields = ("password",) fields = ("password",)
model = Mailbox 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): class MailboxCreateForm(forms.ModelForm):
error_messages = { error_messages = {
'password_mismatch': _('The two password fields didnt match.'), 'password_mismatch': _('The two password fields didnt match.'),
@ -120,7 +135,7 @@ class MailboxCreateForm(forms.ModelForm):
self.error_messages['password_mismatch'], self.error_messages['password_mismatch'],
code='password_mismatch', code='password_mismatch',
) )
return password2 return password
def save(self, commit=True): def save(self, commit=True):
instance = super().save(commit=False) instance = super().save(commit=False)
@ -169,3 +184,14 @@ class RecordUpdateForm(ValidateZoneMixin, forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.domain = self.instance.domain 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('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
path('databases/', views.DatabaseListView.as_view(), name='database-list'), path('databases/', views.DatabaseListView.as_view(), name='database-list'),
path('saas/', views.SaasListView.as_view(), name='saas-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.mailboxes.models import Address, Mailbox
from orchestra.contrib.resources.models import Resource, ResourceData from orchestra.contrib.resources.models import Resource, ResourceData
from orchestra.contrib.saas.models import SaaS from orchestra.contrib.saas.models import SaaS
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.utils.html import html_to_pdf from orchestra.utils.html import html_to_pdf
from .auth import logout as auth_logout from .auth import logout as auth_logout
from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm, from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm,
MailboxSearchForm, MailboxUpdateForm, MailForm, MailboxSearchForm, MailboxUpdateForm, MailForm,
RecordCreateForm, RecordUpdateForm) RecordCreateForm, RecordUpdateForm, WebappUsersChangePasswordForm,
SystemUsersChangePasswordForm)
from .mixins import (CustomContextMixin, ExtendedPaginationMixin, from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin) UserTokenRequiredMixin)
from .models import Address as AddressService from .models import Address as AddressService
@ -612,3 +614,39 @@ class LogoutView(RedirectView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Logout may be done via POST.""" """Logout may be done via POST."""
return self.get(request, *args, **kwargs) 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)