Merge branch 'release/0.10.0' into production
* release/0.10.0: update docs version, add release to changelog fix taskresults.tests add german translation for new strings add docstrings, restrict queryset of osusers.views implement caching for get_hosting_package add list, delete and edit comment of SSH public keys add view osusers.views.AddSshPublicKey repair osusers.tests.test_admin define readonly fields and own delete action for SSH key admin trigger tasks on SshPublicKey save and delete add administration form and admin class for SshPublicKey implement SshPublicKey model, manager and tests document HTML improvements use bootstrap alert classes for messages add api for set_file_ssh_authorized_keys task adapt comments to gvafile server side update Django version to 1.7.4
This commit is contained in:
commit
630d46b595
27 changed files with 1461 additions and 294 deletions
|
@ -1,6 +1,20 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
* :release:`0.10.0 <2015-02-01>`
|
||||
* :support:`-` move taskresults tests to tasksresults.tests and fix them
|
||||
* :support:`-` cache result of get_hosting_package method of
|
||||
gvawebcore.views.HostingPackageAndCustomerMixin to improve page loading
|
||||
performance
|
||||
* :feature:`-` add ability to add, list and delete SSH public keys assigned to
|
||||
a hosting package's operating system user and change their comments
|
||||
* :feature:`-` add ability to add SSH public keys for operating system users
|
||||
* :support:`-` make tests in osusers.tests work again
|
||||
* :support:`-` minor HTML improvements
|
||||
* :support:`-` add API for gvafile task set_file_ssh_authorized_keys (requires
|
||||
gvafile >= 0.5.0 on the fileserver side)
|
||||
* :support:`-` update to Django 1.7.4
|
||||
|
||||
* :release:`0.9.0 <2015-01-27>`
|
||||
* :feature:`-` setup nginx virtual host and PHP configuration for websites
|
||||
(requires gvaweb >= 0.1.0 on web server)
|
||||
|
|
|
@ -60,9 +60,9 @@ copyright = u'2014, 2015 Jan Dittberner'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.9'
|
||||
version = '0.10'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.9.0'
|
||||
release = '0.10.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[run]
|
||||
source = gnuviechadmin,managemails,osusers,domains
|
||||
source = gnuviechadmin,managemails,osusers,domains,taskresults,gvawebcore,userdbs
|
||||
|
||||
[report]
|
||||
omit = */migrations/*,*/tests/*.py,*/tests.py,gnuviechadmin/settings/local.py,gnuviechadmin/settings/production.py
|
||||
|
|
|
@ -73,7 +73,7 @@ def create_file_mailbox(username, mailboxname):
|
|||
|
||||
:param str username: the user name
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises GVAFileException: if the mailbox directory cannot be created
|
||||
:raises Exception: if the mailbox directory cannot be created
|
||||
:return: the created mailbox directory name
|
||||
:rtype: str
|
||||
|
||||
|
@ -87,7 +87,7 @@ def delete_file_mailbox(username, mailboxname):
|
|||
|
||||
:param str username: the user name
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises GVAFileException: if the mailbox directory cannot be deleted
|
||||
:raises Exception: if the mailbox directory cannot be deleted
|
||||
:return: the deleted mailbox directory name
|
||||
:rtype: str
|
||||
|
||||
|
@ -118,3 +118,18 @@ def delete_file_website_hierarchy(username, sitename):
|
|||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_file_ssh_authorized_keys(username, ssh_keys):
|
||||
"""
|
||||
This task sets the authorized keys for ssh logins.
|
||||
|
||||
:param str username: the user name
|
||||
:param list ssh_key: an ssh_key
|
||||
:raises Exception: if the update of the creation or update of ssh
|
||||
authorized_keys failed
|
||||
:return: the name of the authorized_keys file
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
|
|
@ -273,10 +273,10 @@ INSTALLED_APPS = DJANGO_APPS + ALLAUTH_APPS + LOCAL_APPS
|
|||
|
||||
MESSAGE_TAGS = {
|
||||
messages.DEBUG: '',
|
||||
messages.ERROR: 'text-danger',
|
||||
messages.INFO: 'text-info',
|
||||
messages.SUCCESS: 'text-success',
|
||||
messages.WARNING: 'text-warning',
|
||||
messages.ERROR: 'alert-danger',
|
||||
messages.INFO: 'alert-info',
|
||||
messages.SUCCESS: 'alert-success',
|
||||
messages.WARNING: 'alert-warning',
|
||||
}
|
||||
########## END APP CONFIGURATION
|
||||
|
||||
|
|
|
@ -17,10 +17,14 @@ class HostingPackageAndCustomerMixin(object):
|
|||
hosting_package_kwarg = 'package'
|
||||
"""Keyword argument used to find the hosting package in the URL."""
|
||||
|
||||
hostingpackage = None
|
||||
|
||||
def get_hosting_package(self):
|
||||
return get_object_or_404(
|
||||
CustomerHostingPackage,
|
||||
pk=int(self.kwargs[self.hosting_package_kwarg]))
|
||||
if self.hostingpackage is None:
|
||||
self.hostingpackage = get_object_or_404(
|
||||
CustomerHostingPackage,
|
||||
pk=int(self.kwargs[self.hosting_package_kwarg]))
|
||||
return self.hostingpackage
|
||||
|
||||
def get_customer_object(self):
|
||||
return self.get_hosting_package().customer
|
||||
|
|
|
@ -121,6 +121,7 @@ class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
|
|||
'domains': context['hostingpackage'].domains.all(),
|
||||
'mailboxes': context['hostingpackage'].mailboxes,
|
||||
})
|
||||
context['sshkeys'] = context['osuser'].sshpublickey_set.all()
|
||||
return context
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
|
|||
|
||||
|
||||
@shared_task
|
||||
def set_ldap_user_password(self, username, password):
|
||||
def set_ldap_user_password(username, password):
|
||||
"""
|
||||
This task sets the password of an existing :py:class:`LDAP user
|
||||
<ldapentities.models.LdapUser>`.
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gnuviechadmin\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"PO-Revision-Date: 2015-01-27 19:06+0100\n"
|
||||
"POT-Creation-Date: 2015-02-01 02:12+0100\n"
|
||||
"PO-Revision-Date: 2015-02-01 02:29+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -464,6 +464,10 @@ msgstr ""
|
|||
"Angemeldet als <a href=\"%(profile_url)s\" class=\"navbar-link\" title="
|
||||
"\"Mein Profil\">%(user_display)s</a>"
|
||||
|
||||
#: templates/base.html:87
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: templates/dashboard/index.html:3
|
||||
msgid "Welcome"
|
||||
msgstr "Willkommen"
|
||||
|
@ -524,14 +528,15 @@ msgid "Mailboxes"
|
|||
msgstr "Postfächer"
|
||||
|
||||
#: templates/dashboard/user_dashboard.html:18
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:176
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:177
|
||||
msgid "Databases"
|
||||
msgstr "Datenbanken"
|
||||
|
||||
#: templates/dashboard/user_dashboard.html:19
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:86
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:149
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:184
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:87
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:150
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:185
|
||||
#: templates/osusers/sshpublickey_list.html:27
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
|
||||
|
@ -666,6 +671,13 @@ msgstr "SFTP-Benutzername"
|
|||
msgid "SSH/SFTP username"
|
||||
msgstr "SSH/SFTP-Benutzername"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:42
|
||||
#, python-format
|
||||
msgid "There is an SSH public key set for this user."
|
||||
msgid_plural "There are %(counter)s SSH public keys set for this user."
|
||||
msgstr[0] "Es wurde ein SSH-Schlüssel für diesen Nutzer hinterlegt."
|
||||
msgstr[1] "Es wurden %(counter)s SSH-Schlüssel für diesen Nutzer hinterlegt."
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:43
|
||||
msgid "Upload server"
|
||||
msgstr "Uploadserver"
|
||||
|
@ -706,126 +718,137 @@ msgstr "SFTP-Passwort setzen"
|
|||
msgid "Set SSH/SFTP password"
|
||||
msgstr "SSH/SFTP-Passwort setzen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:78
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:71
|
||||
msgid "Add an SSH public key that can be used as an alternative for password"
|
||||
msgstr ""
|
||||
"Einen SSH-Schlüssel, der als Alternative zum Passwort genutzt werden kann, "
|
||||
"hinzufügen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:71
|
||||
#: templates/osusers/sshpublickey_list.html:46
|
||||
msgid "Add SSH public key"
|
||||
msgstr "SSH-Schlüssel hinzufügen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:79
|
||||
msgid "Domains"
|
||||
msgstr "Domains"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:83
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:84
|
||||
msgid "Domain name"
|
||||
msgstr "Domainname"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:84
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:147
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:85
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:148
|
||||
msgid "Mail addresses"
|
||||
msgstr "E-Mailadressen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:85
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:86
|
||||
msgid "Websites"
|
||||
msgstr "Webauftritte"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:86
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:87
|
||||
msgid "Domain actions"
|
||||
msgstr "Domainaktionen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:97
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:98
|
||||
msgid "Edit mail address targets"
|
||||
msgstr "E-Mailadressziele bearbeiten"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:98
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:99
|
||||
msgid "Delete mail address"
|
||||
msgstr "E-Mailadresse löschen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:103
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:115
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:104
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:116
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:110
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:111
|
||||
msgid "Delete website"
|
||||
msgstr "Webauftritt löschen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:119
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:120
|
||||
msgid "Add mail address"
|
||||
msgstr "E-Mailadresse hinzufügen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:122
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:123
|
||||
msgid "Add website"
|
||||
msgstr "Webauftritt anlegen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:130
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:131
|
||||
msgid "There are no domains assigned to this hosting package yet."
|
||||
msgstr "Diesem Paket sind noch keine Domains zugeordnet."
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:133
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:134
|
||||
msgid "Add domain"
|
||||
msgstr "Domain hinzufügen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:141
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:142
|
||||
msgid "E-Mail-Accounts"
|
||||
msgstr "E-Mailkonten"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:146
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:147
|
||||
msgid "Mailbox"
|
||||
msgstr "Postfach"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:148
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:157
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:149
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:158
|
||||
msgid "Active"
|
||||
msgstr "Aktiv"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:149
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:150
|
||||
msgid "Mailbox actions"
|
||||
msgstr "Postfachaktionen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:157
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:158
|
||||
msgid "inactive"
|
||||
msgstr "inaktiv"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:159
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:160
|
||||
msgid "Set mailbox password"
|
||||
msgstr "Postfachpasswort setzen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:165
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:166
|
||||
msgid "There are no mailboxes assigned to this hosting package yet."
|
||||
msgstr "Diesem Hostingpaket sind noch keine Postfächer zugeordnet."
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:168
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:169
|
||||
msgid "Add mailbox"
|
||||
msgstr "Postfach hinzufügen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:181
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:182
|
||||
msgid "Database name"
|
||||
msgstr "Datenbankname"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:182
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:183
|
||||
msgid "Database user"
|
||||
msgstr "Datenbanknutzer"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:183
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:184
|
||||
msgid "Database type"
|
||||
msgstr "Datenbanktyp"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:183
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:184
|
||||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:184
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:185
|
||||
msgid "Database actions"
|
||||
msgstr "Datenbankaktionen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:194
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:195
|
||||
msgid "Set database user password"
|
||||
msgstr "Datenbanknutzerpasswort setzen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:195
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:196
|
||||
msgid "Delete database"
|
||||
msgstr "Datenbank löschen"
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:202
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:203
|
||||
msgid "There are no databases assigned to this hosting package yet."
|
||||
msgstr "Diesem Hostingpaket sind noch keine Datenbanken zugeordnet."
|
||||
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:205
|
||||
#: templates/hostingpackages/customerhostingpackage_detail.html:206
|
||||
msgid "Add database"
|
||||
msgstr "Datenbank hinzufügen"
|
||||
|
||||
|
@ -856,12 +879,14 @@ msgid "Do you really want to delete the mail address %(mailaddress)s?"
|
|||
msgstr "Wollen Sie die E-Mailadresse %(mailaddress)s wirklich löschen?"
|
||||
|
||||
#: templates/managemails/mailaddress_confirm_delete.html:28
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:30
|
||||
#: templates/userdbs/userdatabase_confirm_delete.html:29
|
||||
#: templates/websites/website_confirm_delete.html:29
|
||||
msgid "Yes, do it!"
|
||||
msgstr "Ja, so soll es sein!"
|
||||
|
||||
#: templates/managemails/mailaddress_confirm_delete.html:29
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:31
|
||||
#: templates/userdbs/userdatabase_confirm_delete.html:30
|
||||
#: templates/websites/website_confirm_delete.html:30
|
||||
msgid "Cancel"
|
||||
|
@ -938,6 +963,177 @@ msgstr "Bitte geben Sie das neue Passwort für Ihr Postfach ein."
|
|||
msgid "Please specify the new password for the mailbox."
|
||||
msgstr "Bitte geben Sie das neue Passwort für das Postfach ein."
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:6
|
||||
#, python-format
|
||||
msgid "Delete SSH Public Key for Operating System User %(osuser)s"
|
||||
msgstr "SSH-Schlüssel des Betriebssystemnutzers %(osuser)s löschen"
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Delete SSH Public Key for Operating System User %(osuser)s of Customer "
|
||||
"%(full_name)s"
|
||||
msgstr ""
|
||||
"SSH-Schlüssel des Betriebssystemnutzers %(osuser)s des Kunden %(full_name)s "
|
||||
"löschen"
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:14
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Delete SSH Public Key <small>for Operating System User %(osuser)s</small>"
|
||||
msgstr ""
|
||||
"SSH-Schlüssel löschen <small>für Betriebssystemnutzer %(osuser)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Delete SSH Public Key <small>for Operating System User %(osuser)s of "
|
||||
"Customer %(full_name)s</small>"
|
||||
msgstr ""
|
||||
"SSH-Schlüssel löschen <small>von Betriebssystemnutzer %(osuser)s des Kunden "
|
||||
"%(full_name)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:23
|
||||
#, python-format
|
||||
msgid "Do you really want to delete the %(algorithm)s SSH public key?"
|
||||
msgstr "Wollen Sie den %(algorithm)s-SSH-Schlüssel wirklich löschen?"
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:26
|
||||
msgid ""
|
||||
"When you confirm the deletion of this key you will no longer be able to use "
|
||||
"the corresponding private key for authentication."
|
||||
msgstr ""
|
||||
"Wenn Sie die Löschung dieses Schlüssels bestätigen, werden Sie den "
|
||||
"dazugehörigen privaten Schlüssel nicht weiter für die Anmeldung verwenden "
|
||||
"können."
|
||||
|
||||
#: templates/osusers/sshpublickey_confirm_delete.html:31
|
||||
msgid "Cancel and go back to the SSH key list"
|
||||
msgstr "Abbrechen und zurückgehen zur Liste der SSH-Schlüssel"
|
||||
|
||||
#: templates/osusers/sshpublickey_create.html:6
|
||||
#, python-format
|
||||
msgid "Add new SSH Public Key for Operating System User %(osuser)s"
|
||||
msgstr "Neuen SSH-Schlüssel für Betriebssystemnutzer %(osuser)s hinterlegen"
|
||||
|
||||
#: templates/osusers/sshpublickey_create.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Add a new SSH Public Key for Operating System User %(osuser)s of Customer "
|
||||
"%(full_name)s"
|
||||
msgstr ""
|
||||
"Neuen SSH-Schlüssel für Betriebssystemnutzer %(osuser)s des Kunden "
|
||||
"%(full_name)s hinterlegen"
|
||||
|
||||
#: templates/osusers/sshpublickey_create.html:14
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Add new SSH Public Key <small>for Operating System User %(osuser)s</small>"
|
||||
msgstr ""
|
||||
"Neuen SSH-Schlüssel hinterlegen <small>für Betriebssystemnutzer %(osuser)s</"
|
||||
"small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_create.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Add a new SSH Public Key <small>for Operating System User %(osuser)s of "
|
||||
"Customer %(full_name)s</small>"
|
||||
msgstr ""
|
||||
"Neuen SSH-Schlüssel hinterlegen <small>für Betriebssystemnutzer %(osuser)s "
|
||||
"der Kunden %(full_name)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_edit_comment.html:6
|
||||
#, python-format
|
||||
msgid "Edit Comment of SSH Public Key for Operating System User %(osuser)s"
|
||||
msgstr ""
|
||||
"Kommentar eines SSH-Schlüssels für Betriebssystemnutzer %(osuser)s ändern"
|
||||
|
||||
#: templates/osusers/sshpublickey_edit_comment.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Edit Comment of SSH Public Key for Operating System User %(osuser)s of "
|
||||
"Customer %(full_name)s"
|
||||
msgstr ""
|
||||
"Kommentar des SSH-Schlüssels des Betriebssystemnutzers %(osuser)s des Kunden "
|
||||
"%(full_name)s ändern"
|
||||
|
||||
#: templates/osusers/sshpublickey_edit_comment.html:14
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Edit Comment of Public Key <small>for Operating System User %(osuser)s</"
|
||||
"small>"
|
||||
msgstr ""
|
||||
"Kommentar eines SSH-Schlüssels ändern <small>für Betriebssystemnutzer "
|
||||
"%(osuser)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_edit_comment.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Edit Comment of SSH Public Key <small>for Operating System User %(osuser)s "
|
||||
"of Customer %(full_name)s</small>"
|
||||
msgstr ""
|
||||
"Kommentar des SSH-Schlüssels ändern <small>für Betriebssystemnutzer "
|
||||
"%(osuser)s des Kunden %(full_name)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:6
|
||||
#, python-format
|
||||
msgid "SSH Public Keys for Operating System User %(osuser)s"
|
||||
msgstr "SSH-Schlüssel für Betriebssystemnutzer %(osuser)s"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
"SSH Public Keys for Operating System User %(osuser)s of Customer "
|
||||
"%(full_name)s"
|
||||
msgstr ""
|
||||
"SSH-Schlüssel des Betriebssystemnutzers %(osuser)s des Kunden %(full_name)s"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:14
|
||||
#, python-format
|
||||
msgid "SSH Public Keys <small>for Operating System User %(osuser)s</small>"
|
||||
msgstr "SSH-Schlüssel <small>für Betriebssystemnutzer %(osuser)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"SSH Public Keys <small>for Operating System User %(osuser)s of Customer "
|
||||
"%(full_name)s</small>"
|
||||
msgstr ""
|
||||
"SSH-Schlüssel <small>des Betriebssystemnutzers %(osuser)s des Kunden "
|
||||
"%(full_name)s</small>"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:25
|
||||
msgid "Algorithm"
|
||||
msgstr "Algorithmus"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:26
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:27
|
||||
msgid "SSH public key actions"
|
||||
msgstr "Aktionen für SSH-Schlüssel"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:36
|
||||
msgid "Delete this SSH public key"
|
||||
msgstr "Diesen SSH-Schlüssel löschen"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:36
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:37
|
||||
msgid "Edit this SSH public key's comment"
|
||||
msgstr "Den Kommentar dieses SSH-Schlüssels ändern"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:37
|
||||
msgid "Edit Comment"
|
||||
msgstr "Kommentar ändern"
|
||||
|
||||
#: templates/osusers/sshpublickey_list.html:44
|
||||
msgid "There are now SSH public keys set for this operating system user yet."
|
||||
msgstr "Diesem Betriebssytemnutzer wurden noch keine SSH-Schlüssel zugeordnet."
|
||||
|
||||
#: templates/osusers/user_setpassword.html:5
|
||||
#: templates/osusers/user_setpassword.html:13
|
||||
#, python-format
|
||||
|
|
|
@ -3,16 +3,24 @@ This module contains the Django admin classes of the :py:mod:`osusers` app.
|
|||
|
||||
"""
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import admin
|
||||
|
||||
from fileservertasks.tasks import set_file_ssh_authorized_keys
|
||||
from gvawebcore.forms import (
|
||||
PASSWORD_MISMATCH_ERROR
|
||||
)
|
||||
from taskresults.models import TaskResult
|
||||
|
||||
from .forms import (
|
||||
INVALID_SSH_PUBLIC_KEY,
|
||||
DUPLICATE_SSH_PUBLIC_KEY_FOR_USER,
|
||||
)
|
||||
from .models import (
|
||||
AdditionalGroup,
|
||||
Group,
|
||||
Shadow,
|
||||
SshPublicKey,
|
||||
User,
|
||||
)
|
||||
|
||||
|
@ -216,5 +224,158 @@ class GroupAdmin(admin.ModelAdmin):
|
|||
return actions
|
||||
|
||||
|
||||
class SshPublicKeyCreationForm(forms.ModelForm):
|
||||
"""
|
||||
A form for creating :py:class:`SSH public keys
|
||||
<osusers.models.SshPublicKey>`.
|
||||
|
||||
"""
|
||||
publickeytext = forms.CharField(
|
||||
label=_('Key text'), widget=forms.Textarea,
|
||||
help_text=_('A SSH public key in either OpenSSH or RFC 4716 format'))
|
||||
|
||||
class Meta:
|
||||
model = SshPublicKey
|
||||
fields = ['user']
|
||||
|
||||
def clean_publickeytext(self):
|
||||
keytext = self.cleaned_data.get('publickeytext')
|
||||
try:
|
||||
SshPublicKey.objects.parse_keytext(keytext)
|
||||
except:
|
||||
raise forms.ValidationError(INVALID_SSH_PUBLIC_KEY)
|
||||
return keytext
|
||||
|
||||
def clean(self):
|
||||
user = self.cleaned_data.get('user')
|
||||
keytext = self.cleaned_data.get('publickeytext')
|
||||
if user and keytext:
|
||||
alg, data, comment = SshPublicKey.objects.parse_keytext(keytext)
|
||||
if SshPublicKey.objects.filter(
|
||||
user=user, algorithm=alg, data=data
|
||||
).exists():
|
||||
self.add_error(
|
||||
'publickeytext',
|
||||
forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER))
|
||||
|
||||
def save(self, commit=True):
|
||||
"""
|
||||
Save the provided ssh public key in properly split format.
|
||||
|
||||
:param boolean commit: whether to save the created public key
|
||||
:return: ssh public key instance
|
||||
:rtype: :py:class:`osusers.models.SshPublicKey`
|
||||
|
||||
"""
|
||||
algorithm, keydata, comment = SshPublicKey.objects.parse_keytext(
|
||||
self.cleaned_data.get('publickeytext'))
|
||||
self.instance.algorithm = algorithm
|
||||
self.instance.data = keydata
|
||||
self.instance.comment = comment
|
||||
return super(SshPublicKeyCreationForm, self).save(commit)
|
||||
|
||||
|
||||
class SshPublicKeyAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin class for working with :py:class:`SSH public keys
|
||||
<osusers.models.SshPublicKey>`.
|
||||
|
||||
"""
|
||||
actions = ['perform_delete_selected']
|
||||
add_form = SshPublicKeyCreationForm
|
||||
list_display = ['user', 'algorithm', 'comment']
|
||||
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('user', 'publickeytext')}),
|
||||
)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Use special form for ssh public key creation."
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param obj: either a :py:class:`SshPublicKey
|
||||
<osusers.models.SshPublicKey>` instance or None for a new SSH
|
||||
public key
|
||||
:param kwargs: keyword arguments to be passed to
|
||||
:py:meth:`django.contrib.admin.ModelAdmin.get_form`
|
||||
:return: form instance
|
||||
|
||||
"""
|
||||
defaults = {}
|
||||
if obj is None:
|
||||
defaults.update({
|
||||
'form': self.add_form,
|
||||
'fields': admin.options.flatten_fieldsets(self.add_fieldsets),
|
||||
})
|
||||
defaults.update(kwargs)
|
||||
return super(SshPublicKeyAdmin, self).get_form(
|
||||
request, obj, **defaults)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
"""
|
||||
Make sure that algorithm and data of SSH public keys are not editable.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param obj: either a :py:class:`SshPublicKey
|
||||
<osusers.models.SshPublicKey>` instance or None for a new SSH
|
||||
public key
|
||||
:return: a list of fields
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
if obj:
|
||||
return ['algorithm', 'data']
|
||||
return []
|
||||
|
||||
def perform_delete_selected(self, request, queryset):
|
||||
"""
|
||||
Action to delete a list of selected ssh keys.
|
||||
|
||||
This action makes sure that the ssh keys of all users affected by the
|
||||
current deletion are refreshed on the file server.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param queryset: Django ORM queryset representing the selected ssh keys
|
||||
|
||||
"""
|
||||
users = set([
|
||||
item['user'] for item in
|
||||
queryset.values('user').distinct()
|
||||
])
|
||||
queryset.delete()
|
||||
for user in users:
|
||||
TaskResult.objects.create_task_result(
|
||||
set_file_ssh_authorized_keys.delay(
|
||||
User.objects.get(uid=user).username, [
|
||||
str(key) for key in SshPublicKey.objects.filter(
|
||||
user_id=user)
|
||||
]),
|
||||
'set_file_ssh_authorized_keys'
|
||||
)
|
||||
perform_delete_selected.short_description = _(
|
||||
'Delete selected SSH public keys')
|
||||
|
||||
def get_actions(self, request):
|
||||
"""
|
||||
Get the available actions for SSH public keys.
|
||||
|
||||
This overrides the default behavior to remove the default
|
||||
`delete_selected` action.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:return: list of actions
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
actions = super(SshPublicKeyAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(SshPublicKey, SshPublicKeyAdmin)
|
||||
|
|
|
@ -13,7 +13,14 @@ from crispy_forms.layout import Submit
|
|||
|
||||
from gvawebcore.forms import PasswordModelFormMixin
|
||||
|
||||
from .models import User
|
||||
from .models import (
|
||||
SshPublicKey,
|
||||
User,
|
||||
)
|
||||
|
||||
INVALID_SSH_PUBLIC_KEY = _('Invalid SSH public key data format.')
|
||||
DUPLICATE_SSH_PUBLIC_KEY_FOR_USER = _(
|
||||
'This SSH public key is already assigned to this user.')
|
||||
|
||||
|
||||
class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm):
|
||||
|
@ -43,3 +50,85 @@ class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm):
|
|||
"""
|
||||
self.instance.set_password(self.cleaned_data['password1'])
|
||||
return super(ChangeOsUserPasswordForm, self).save(commit=commit)
|
||||
|
||||
|
||||
class AddSshPublicKeyForm(forms.ModelForm):
|
||||
"""
|
||||
A form for creating :py:class:`SSH public keys
|
||||
<osusers.models.SshPublicKey>`.
|
||||
|
||||
"""
|
||||
publickeytext = forms.CharField(
|
||||
label=_('Key text'), widget=forms.Textarea,
|
||||
help_text=_('A SSH public key in either OpenSSH or RFC 4716 format'))
|
||||
|
||||
class Meta:
|
||||
model = SshPublicKey
|
||||
fields = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
hosting_package = kwargs.pop('hostingpackage')
|
||||
self.osuser = hosting_package.osuser
|
||||
super(AddSshPublicKeyForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_action = reverse(
|
||||
'add_ssh_key', kwargs={'package': hosting_package.id})
|
||||
self.helper.add_input(Submit('submit', _('Add SSH public key')))
|
||||
|
||||
def clean_publickeytext(self):
|
||||
keytext = self.cleaned_data.get('publickeytext')
|
||||
try:
|
||||
SshPublicKey.objects.parse_keytext(keytext)
|
||||
except:
|
||||
raise forms.ValidationError(INVALID_SSH_PUBLIC_KEY)
|
||||
return keytext
|
||||
|
||||
def clean(self):
|
||||
keytext = self.cleaned_data.get('publickeytext')
|
||||
alg, data, comment = SshPublicKey.objects.parse_keytext(keytext)
|
||||
if SshPublicKey.objects.filter(
|
||||
user=self.osuser, algorithm=alg, data=data
|
||||
).exists():
|
||||
self.add_error(
|
||||
'publickeytext',
|
||||
forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER)
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
"""
|
||||
Save the provided ssh public key in properly split format.
|
||||
|
||||
:param boolean commit: whether to save the created public key
|
||||
:return: ssh public key instance
|
||||
:rtype: :py:class:`osusers.models.SshPublicKey`
|
||||
|
||||
"""
|
||||
algorithm, keydata, comment = SshPublicKey.objects.parse_keytext(
|
||||
self.cleaned_data.get('publickeytext'))
|
||||
self.instance.user = self.osuser
|
||||
self.instance.algorithm = algorithm
|
||||
self.instance.data = keydata
|
||||
self.instance.comment = comment
|
||||
return super(AddSshPublicKeyForm, self).save(commit)
|
||||
|
||||
|
||||
class EditSshPublicKeyCommentForm(forms.ModelForm):
|
||||
"""
|
||||
A form for editing :py:class:`SSH public key
|
||||
<osusers.models.SshPublicKey>` comment fields.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
model = SshPublicKey
|
||||
fields = ['comment']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
hosting_package = kwargs.pop('hostingpackage')
|
||||
self.osuser = hosting_package.osuser
|
||||
super(EditSshPublicKeyCommentForm, self).__init__(*args, **kwargs)
|
||||
self.fields['comment'].widget = forms.TextInput()
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_action = reverse(
|
||||
'edit_ssh_key_comment',
|
||||
kwargs={'package': hosting_package.id, 'pk': self.instance.id})
|
||||
self.helper.add_input(Submit('submit', _('Change Comment')))
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: osusers\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"PO-Revision-Date: 2015-01-24 18:25+0100\n"
|
||||
"POT-Creation-Date: 2015-02-01 02:12+0100\n"
|
||||
"PO-Revision-Date: 2015-02-01 02:17+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -19,128 +19,158 @@ msgstr ""
|
|||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: osusers/admin.py:45
|
||||
#: osusers/admin.py:53
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: osusers/admin.py:49
|
||||
#: osusers/admin.py:57
|
||||
msgid "Password (again)"
|
||||
msgstr "Passwortwiederholung"
|
||||
|
||||
#: osusers/admin.py:158
|
||||
#: osusers/admin.py:166
|
||||
msgid "Delete selected users"
|
||||
msgstr "Ausgewählte Nutzer löschen"
|
||||
|
||||
#: osusers/admin.py:199
|
||||
#: osusers/admin.py:207
|
||||
msgid "Delete selected groups"
|
||||
msgstr "Ausgewählte Gruppen löschen"
|
||||
|
||||
#: osusers/admin.py:234 osusers/forms.py:62
|
||||
msgid "Key text"
|
||||
msgstr "Schlüsseltext"
|
||||
|
||||
#: osusers/admin.py:235 osusers/forms.py:63
|
||||
msgid "A SSH public key in either OpenSSH or RFC 4716 format"
|
||||
msgstr ""
|
||||
"Öffentlicher Teil eines SSH-Schlüssels entweder im OpenSSH- oder im RFC-4716-"
|
||||
"Format"
|
||||
|
||||
#: osusers/admin.py:359
|
||||
msgid "Delete selected SSH public keys"
|
||||
msgstr "Ausgewählte SSH-Schlüssel löschen"
|
||||
|
||||
#: osusers/apps.py:17
|
||||
msgid "Operating System Users and Groups"
|
||||
msgstr "Betriebssystemnutzer- und Gruppen"
|
||||
|
||||
#: osusers/forms.py:33
|
||||
#: osusers/forms.py:21
|
||||
msgid "Invalid SSH public key data format."
|
||||
msgstr "Ungültiges Format für den öffentlichen Teil eines SSH-Schlüssels."
|
||||
|
||||
#: osusers/forms.py:23
|
||||
msgid "This SSH public key is already assigned to this user."
|
||||
msgstr "Dieser SSH-Schlüssel wurde diesem Nutzer bereits zugeordnet."
|
||||
|
||||
#: osusers/forms.py:40
|
||||
msgid "Set password"
|
||||
msgstr "Passwort setzen"
|
||||
|
||||
#: osusers/models.py:47
|
||||
#: osusers/forms.py:76
|
||||
msgid "Add SSH public key"
|
||||
msgstr "SSH-Schlüssel hinzufügen"
|
||||
|
||||
#: osusers/forms.py:134
|
||||
msgid "Change Comment"
|
||||
msgstr "Kommentar ändern"
|
||||
|
||||
#: osusers/models.py:50
|
||||
msgid "You can not use a user's primary group."
|
||||
msgstr "Sie können nicht die primäre Gruppe des Nutzers verwenden."
|
||||
|
||||
#: osusers/models.py:77
|
||||
#: osusers/models.py:80
|
||||
msgid "Group name"
|
||||
msgstr "Gruppenname"
|
||||
|
||||
#: osusers/models.py:79
|
||||
#: osusers/models.py:82
|
||||
msgid "Group ID"
|
||||
msgstr "Gruppen-ID"
|
||||
|
||||
#: osusers/models.py:80
|
||||
#: osusers/models.py:83
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: osusers/models.py:82
|
||||
#: osusers/models.py:85
|
||||
msgid "Group password"
|
||||
msgstr "Gruppenpasswort"
|
||||
|
||||
#: osusers/models.py:87 osusers/models.py:221
|
||||
#: osusers/models.py:90 osusers/models.py:224
|
||||
msgid "Group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: osusers/models.py:88
|
||||
#: osusers/models.py:91
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: osusers/models.py:218
|
||||
#: osusers/models.py:221
|
||||
msgid "User name"
|
||||
msgstr "Nutzername"
|
||||
|
||||
#: osusers/models.py:220
|
||||
#: osusers/models.py:223
|
||||
msgid "User ID"
|
||||
msgstr "Nutzer-ID"
|
||||
|
||||
#: osusers/models.py:222
|
||||
#: osusers/models.py:225
|
||||
msgid "Gecos field"
|
||||
msgstr "GECOS-Feld"
|
||||
|
||||
#: osusers/models.py:223
|
||||
#: osusers/models.py:226
|
||||
msgid "Home directory"
|
||||
msgstr "Home-Verzeichnis"
|
||||
|
||||
#: osusers/models.py:224
|
||||
#: osusers/models.py:227
|
||||
msgid "Login shell"
|
||||
msgstr "Loginshell"
|
||||
|
||||
#: osusers/models.py:230 osusers/models.py:370
|
||||
#: osusers/models.py:233 osusers/models.py:373 osusers/models.py:566
|
||||
msgid "User"
|
||||
msgstr "Nutzer"
|
||||
|
||||
#: osusers/models.py:231
|
||||
#: osusers/models.py:234
|
||||
msgid "Users"
|
||||
msgstr "Nutzer"
|
||||
|
||||
#: osusers/models.py:371
|
||||
#: osusers/models.py:374
|
||||
msgid "Encrypted password"
|
||||
msgstr "Verschlüsseltes Passwort"
|
||||
|
||||
#: osusers/models.py:373
|
||||
#: osusers/models.py:376
|
||||
msgid "Date of last change"
|
||||
msgstr "Datum der letzten Änderung"
|
||||
|
||||
#: osusers/models.py:374
|
||||
#: osusers/models.py:377
|
||||
msgid "This is expressed in days since Jan 1, 1970"
|
||||
msgstr "Ausgedrückt als Tage seit dem 1. Januar 1970"
|
||||
|
||||
#: osusers/models.py:377
|
||||
#: osusers/models.py:380
|
||||
msgid "Minimum age"
|
||||
msgstr "Minimales Alter"
|
||||
|
||||
#: osusers/models.py:378
|
||||
#: osusers/models.py:381
|
||||
msgid "Minimum number of days before the password can be changed"
|
||||
msgstr "Minmale Anzahl von Tagen bevor das Passwort geändert werden kann"
|
||||
|
||||
#: osusers/models.py:382
|
||||
#: osusers/models.py:385
|
||||
msgid "Maximum age"
|
||||
msgstr "Maximales Alter"
|
||||
|
||||
#: osusers/models.py:383
|
||||
#: osusers/models.py:386
|
||||
msgid "Maximum number of days after which the password has to be changed"
|
||||
msgstr ""
|
||||
"Maximale Anzahl von Tagen, nach denen das Passwort geändert werden muss"
|
||||
|
||||
#: osusers/models.py:387
|
||||
#: osusers/models.py:390
|
||||
msgid "Grace period"
|
||||
msgstr "Duldungsperiode"
|
||||
|
||||
#: osusers/models.py:388
|
||||
#: osusers/models.py:391
|
||||
msgid "The number of days before the password is going to expire"
|
||||
msgstr "Anzahl von Tagen nach denen das Passwort verfällt"
|
||||
|
||||
#: osusers/models.py:392
|
||||
#: osusers/models.py:395
|
||||
msgid "Inactivity period"
|
||||
msgstr "Inaktivitätsperiode"
|
||||
|
||||
#: osusers/models.py:393
|
||||
#: osusers/models.py:396
|
||||
msgid ""
|
||||
"The number of days after the password has expired during which the password "
|
||||
"should still be accepted"
|
||||
|
@ -148,36 +178,65 @@ msgstr ""
|
|||
"Die Anzahl von Tagen für die ein verfallenes Passwort noch akzeptiert werden "
|
||||
"soll"
|
||||
|
||||
#: osusers/models.py:397
|
||||
#: osusers/models.py:400
|
||||
msgid "Account expiration date"
|
||||
msgstr "Kontoverfallsdatum"
|
||||
|
||||
#: osusers/models.py:398
|
||||
#: osusers/models.py:401
|
||||
msgid ""
|
||||
"The date of expiration of the account, expressed as number of days since Jan "
|
||||
"1, 1970"
|
||||
msgstr "Kontoverfallsdatum in Tagen seit dem 1. Januar 1970"
|
||||
|
||||
#: osusers/models.py:405
|
||||
#: osusers/models.py:408
|
||||
msgid "Shadow password"
|
||||
msgstr "Shadow-Passwort"
|
||||
|
||||
#: osusers/models.py:406
|
||||
#: osusers/models.py:409
|
||||
msgid "Shadow passwords"
|
||||
msgstr "Shadow-Passwörter"
|
||||
|
||||
#: osusers/models.py:432
|
||||
#: osusers/models.py:435
|
||||
msgid "Additional group"
|
||||
msgstr "Weitere Gruppe"
|
||||
|
||||
#: osusers/models.py:433
|
||||
#: osusers/models.py:436
|
||||
msgid "Additional groups"
|
||||
msgstr "Weitere Gruppen"
|
||||
|
||||
#: osusers/views.py:42
|
||||
#: osusers/models.py:567
|
||||
msgid "Algorithm"
|
||||
msgstr "Algorithmus"
|
||||
|
||||
#: osusers/models.py:568
|
||||
msgid "Key bytes"
|
||||
msgstr "Schlüsselbytes"
|
||||
|
||||
#: osusers/models.py:569
|
||||
msgid "Base64 encoded key bytes"
|
||||
msgstr "Base64-kodierte Schlüsselbytes"
|
||||
|
||||
#: osusers/models.py:570
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: osusers/models.py:575
|
||||
msgid "SSH public key"
|
||||
msgstr "Öffentlicher SSH-Schlüssel"
|
||||
|
||||
#: osusers/models.py:576
|
||||
msgid "SSH public keys"
|
||||
msgstr "Öffentliche SSH-Schlüssel"
|
||||
|
||||
#: osusers/views.py:56
|
||||
#, python-brace-format
|
||||
msgid "New password for {username} has been set successfully."
|
||||
msgstr "Für {username} wurde erfolgreich ein neues Passwort gesetzt."
|
||||
|
||||
#: osusers/views.py:92
|
||||
#, python-brace-format
|
||||
msgid "Successfully added new {algorithm} SSH public key"
|
||||
msgstr "Neuer {algorithm}-SSH-Schlüssel erfolgreich hinzugefügt"
|
||||
|
||||
#~ msgid "Passwords don't match"
|
||||
#~ msgstr "Passwörter stimmen nicht überein"
|
||||
|
|
37
gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py
Normal file
37
gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osusers', '0004_auto_20150104_1751'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SshPublicKey',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('algorithm', models.CharField(max_length=20, verbose_name='Algorithm')),
|
||||
('data', models.TextField(help_text='Base64 encoded key bytes', verbose_name='Key bytes')),
|
||||
('comment', models.TextField(verbose_name='Comment', blank=True)),
|
||||
('user', models.ForeignKey(verbose_name='User', to='osusers.User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'SSH public key',
|
||||
'verbose_name_plural': 'SSH public keys',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='sshpublickey',
|
||||
unique_together=set([('user', 'algorithm', 'data')]),
|
||||
),
|
||||
]
|
|
@ -4,9 +4,11 @@ This module defines the database models of operating system users.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
from datetime import date
|
||||
import logging
|
||||
import os
|
||||
import six
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.conf import settings
|
||||
|
@ -35,6 +37,7 @@ from ldaptasks.tasks import (
|
|||
from fileservertasks.tasks import (
|
||||
delete_file_mail_userdir,
|
||||
delete_file_sftp_userdir,
|
||||
set_file_ssh_authorized_keys,
|
||||
setup_file_mail_userdir,
|
||||
setup_file_sftp_userdir,
|
||||
)
|
||||
|
@ -477,3 +480,124 @@ class AdditionalGroup(TimeStampedModel, models.Model):
|
|||
'remove_ldap_user_from_group'
|
||||
)
|
||||
super(AdditionalGroup, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
class SshPublicKeyManager(models.Manager):
|
||||
"""
|
||||
Default manager for :py:class:`SSH public key
|
||||
<osusers.models.SshPublicKey>` instances.
|
||||
|
||||
"""
|
||||
|
||||
def parse_keytext(self, keytext):
|
||||
"""
|
||||
Parse a SSH public key in OpenSSH or :rfc:`4716` format into its
|
||||
components algorithm, key data and comment.
|
||||
|
||||
:param str keytext: key text
|
||||
:return: triple of algorithm name, key data and comment
|
||||
:rtype: triple of str
|
||||
|
||||
"""
|
||||
if keytext.startswith('---- BEGIN SSH2 PUBLIC KEY ----'):
|
||||
comment = ''
|
||||
data = ''
|
||||
continued = ''
|
||||
headers = {}
|
||||
for line in keytext.splitlines():
|
||||
if line == '---- BEGIN SSH2 PUBLIC KEY ----':
|
||||
continue
|
||||
elif ':' in line: # a header line
|
||||
header_tag, header_value = [
|
||||
item.strip() for item in line.split(':', 1)]
|
||||
if header_value.endswith('\\'):
|
||||
continued = header_value[:-1]
|
||||
else:
|
||||
headers[header_tag.lower()] = header_value
|
||||
elif continued:
|
||||
if line.endswith('\\'):
|
||||
continued += line[:-1]
|
||||
continue
|
||||
header_value = continued + line
|
||||
headers[header_tag.lower()] = header_value
|
||||
continued = ''
|
||||
elif line == '---- END SSH2 PUBLIC KEY ----':
|
||||
break
|
||||
elif line: # ignore empty lines
|
||||
data += line
|
||||
if 'comment' in headers:
|
||||
comment = headers['comment']
|
||||
else:
|
||||
parts = keytext.split()
|
||||
if len(parts) > 3:
|
||||
raise ValueError("unsupported key format")
|
||||
data = parts[1]
|
||||
comment = len(parts) == 3 and parts[2] or ""
|
||||
keybytes = base64.b64decode(data)
|
||||
parts = keybytes.split(b'\x00' * 3)
|
||||
alglength = six.byte2int(parts[1])
|
||||
algname = parts[1][1:1+alglength]
|
||||
return algname, data, comment
|
||||
|
||||
def create_ssh_public_key(self, user, keytext):
|
||||
"""
|
||||
Create a new :py:class:`SSH public key <osusers.models.SshPublicKey>`
|
||||
for a user from the given key text representation. The text can be
|
||||
either in openssh format or :rfc:`4716` format.
|
||||
|
||||
:param user: :py:class:`operating system user <osusers.models.User>`
|
||||
:param str keytext: key text
|
||||
:return: public key
|
||||
:retype: :py:class:`osusers.models.SshPublicKey`
|
||||
|
||||
"""
|
||||
algorithm, data, comment = self.parse_keytext(keytext)
|
||||
return self.create(
|
||||
user=user, algorithm=algorithm, data=data, comment=comment)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SshPublicKey(TimeStampedModel):
|
||||
"""
|
||||
This entity class represents single SSH keys for an :py:class:`operating
|
||||
system user <osusers.models.User>`.
|
||||
|
||||
"""
|
||||
user = models.ForeignKey(User, verbose_name=_('User'))
|
||||
algorithm = models.CharField(_('Algorithm'), max_length=20)
|
||||
data = models.TextField(_('Key bytes'),
|
||||
help_text=_('Base64 encoded key bytes'))
|
||||
comment = models.TextField(_('Comment'), blank=True)
|
||||
|
||||
objects = SshPublicKeyManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('SSH public key')
|
||||
verbose_name_plural = _('SSH public keys')
|
||||
unique_together = [('user', 'algorithm', 'data')]
|
||||
|
||||
def __str__(self):
|
||||
return "{algorithm} {data} {comment}".format(
|
||||
algorithm=self.algorithm, data=self.data, comment=self.comment
|
||||
).strip()
|
||||
|
||||
def save(self, **kwargs):
|
||||
key = super(SshPublicKey, self).save(**kwargs)
|
||||
TaskResult.objects.create_task_result(
|
||||
set_file_ssh_authorized_keys.delay(
|
||||
self.user.username, [
|
||||
str(key) for key in
|
||||
SshPublicKey.objects.filter(user=self.user)]),
|
||||
'set_file_ssh_authorized_keys'
|
||||
)
|
||||
return key
|
||||
|
||||
def delete(self, **kwargs):
|
||||
super(SshPublicKey, self).delete(**kwargs)
|
||||
TaskResult.objects.create_task_result(
|
||||
set_file_ssh_authorized_keys.delay(
|
||||
self.user.username, [
|
||||
str(key) for key in
|
||||
SshPublicKey.objects.filter(user=self.user)]),
|
||||
'set_file_ssh_authorized_keys'
|
||||
)
|
||||
|
|
|
@ -3,7 +3,9 @@ from django.contrib.admin import AdminSite
|
|||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from mock import patch, Mock
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from mock import Mock
|
||||
|
||||
from osusers.models import (
|
||||
Group,
|
||||
|
@ -16,31 +18,24 @@ from osusers.admin import (
|
|||
UserCreationForm,
|
||||
)
|
||||
|
||||
Customer = get_user_model()
|
||||
|
||||
class TaskResultInlineTest(TestCase):
|
||||
|
||||
class CustomerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
super(TaskResultInlineTest, self).setUp()
|
||||
|
||||
def test_get_queryset_calls_update_taskstatus(self):
|
||||
with patch('osusers.admin.admin.TabularInline.get_queryset') as mock:
|
||||
entrymock = Mock(name='entry')
|
||||
mock.return_value = [entrymock]
|
||||
requestmock = Mock(name='request')
|
||||
UserTaskResultInline(User, self.site).get_queryset(requestmock)
|
||||
entrymock.update_taskstatus.assert_calledwith()
|
||||
|
||||
def test_has_add_permissions_returns_false(self):
|
||||
self.assertFalse(
|
||||
UserTaskResultInline(User, self.site).has_add_permission(
|
||||
self, Mock(name='request'))
|
||||
)
|
||||
self.customer = Customer.objects.create_user('test')
|
||||
super(CustomerTestCase, self).setUp()
|
||||
|
||||
|
||||
class UserCreationFormTest(TestCase):
|
||||
class UserCreationFormTest(CustomerTestCase):
|
||||
|
||||
def test_clean_password2_same(self):
|
||||
form = UserCreationForm()
|
||||
form.cleaned_data = {'password1': 'secret', 'password2': 'secret'}
|
||||
form.cleaned_data = {
|
||||
'customer': self.customer,
|
||||
'password1': 'secret',
|
||||
'password2': 'secret'
|
||||
}
|
||||
self.assertEqual(form.clean_password2(), 'secret')
|
||||
|
||||
def test_clean_password2_empty(self):
|
||||
|
@ -50,7 +45,11 @@ class UserCreationFormTest(TestCase):
|
|||
|
||||
def test_clean_password2_mismatch(self):
|
||||
form = UserCreationForm()
|
||||
form.cleaned_data = {'password1': 'secretx', 'password2': 'secrety'}
|
||||
form.cleaned_data = {
|
||||
'customer': self.customer,
|
||||
'password1': 'secretx',
|
||||
'password2': 'secrety'
|
||||
}
|
||||
with self.assertRaises(forms.ValidationError) as cm:
|
||||
form.clean_password2()
|
||||
self.assertEqual(cm.exception.message, PASSWORD_MISMATCH_ERROR)
|
||||
|
@ -62,7 +61,11 @@ class UserCreationFormTest(TestCase):
|
|||
)
|
||||
def test_save_commit(self):
|
||||
form = UserCreationForm()
|
||||
form.cleaned_data = {'password1': 'secret', 'password2': 'secret'}
|
||||
form.cleaned_data = {
|
||||
'customer': self.customer,
|
||||
'password1': 'secret',
|
||||
'password2': 'secret'
|
||||
}
|
||||
user = form.save()
|
||||
self.assertIsNotNone(user)
|
||||
self.assertEqual(User.objects.get(pk=user.uid), user)
|
||||
|
@ -72,7 +75,7 @@ class UserCreationFormTest(TestCase):
|
|||
self.assertIsNone(form.save_m2m())
|
||||
|
||||
|
||||
class UserAdminTest(TestCase):
|
||||
class UserAdminTest(CustomerTestCase):
|
||||
def setUp(self):
|
||||
site = AdminSite()
|
||||
self.uadmin = UserAdmin(User, site)
|
||||
|
@ -91,11 +94,12 @@ class UserAdminTest(TestCase):
|
|||
BROKER_BACKEND='memory'
|
||||
)
|
||||
def test_get_form_with_object(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
form = self.uadmin.get_form(Mock(name='request'), user)
|
||||
self.assertEqual(
|
||||
form.Meta.fields,
|
||||
['username', 'group', 'gecos', 'homedir', 'shell', 'uid']
|
||||
['username', 'group', 'gecos', 'homedir', 'shell', 'customer',
|
||||
'uid']
|
||||
)
|
||||
|
||||
def test_get_inline_instances_without_object(self):
|
||||
|
@ -108,7 +112,7 @@ class UserAdminTest(TestCase):
|
|||
BROKER_BACKEND='memory'
|
||||
)
|
||||
def test_get_inline_instances_with_object(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
inlines = self.uadmin.get_inline_instances(
|
||||
Mock(name='request'), user)
|
||||
self.assertEqual(len(inlines), len(UserAdmin.inlines))
|
||||
|
@ -138,27 +142,3 @@ class GroupAdminTest(TestCase):
|
|||
self.assertEqual(len(inlines), len(GroupAdmin.inlines))
|
||||
for index in range(len(inlines)):
|
||||
self.assertIsInstance(inlines[index], GroupAdmin.inlines[index])
|
||||
|
||||
|
||||
class DeleteTaskResultAdminTest(TestCase):
|
||||
def setUp(self):
|
||||
site = AdminSite()
|
||||
self.dtradmin = DeleteTaskResultAdmin(DeleteTaskResult, site)
|
||||
super(DeleteTaskResultAdminTest, self).setUp()
|
||||
|
||||
def test_has_add_permission_returns_false_without_object(self):
|
||||
self.assertFalse(
|
||||
self.dtradmin.has_add_permission(Mock(name='request')))
|
||||
|
||||
def test_has_add_permission_returns_false_with_object(self):
|
||||
self.assertFalse(
|
||||
self.dtradmin.has_add_permission(Mock(name='request'),
|
||||
Mock(name='test')))
|
||||
|
||||
def test_get_queryset_calls_update_taskstatus(self):
|
||||
with patch('osusers.admin.admin.ModelAdmin.get_queryset') as mock:
|
||||
entrymock = Mock(name='entry')
|
||||
mock.return_value = [entrymock]
|
||||
requestmock = Mock(name='request')
|
||||
self.dtradmin.get_queryset(requestmock)
|
||||
entrymock.update_taskstatus.assert_calledwith()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from datetime import date
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
|
||||
from mock import patch, MagicMock
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from passlib.hash import sha512_crypt
|
||||
|
||||
|
@ -14,8 +14,84 @@ from osusers.models import (
|
|||
AdditionalGroup,
|
||||
Group,
|
||||
Shadow,
|
||||
SshPublicKey,
|
||||
User,
|
||||
)
|
||||
from taskresults.models import TaskResult
|
||||
|
||||
|
||||
EXAMPLE_KEY_1_RFC4716 = """---- BEGIN SSH2 PUBLIC KEY ----
|
||||
Comment: "1024-bit RSA, converted from OpenSSH by me@example.com"
|
||||
x-command: /home/me/bin/lock-in-guest.sh
|
||||
AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb
|
||||
YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ
|
||||
5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=
|
||||
---- END SSH2 PUBLIC KEY ----"""
|
||||
|
||||
EXAMPLE_KEY_2_RFC4716 = """---- BEGIN SSH2 PUBLIC KEY ----
|
||||
Comment: This is my public key for use on \
|
||||
servers which I don't like.
|
||||
AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
|
||||
W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
|
||||
YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
|
||||
vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
|
||||
J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
|
||||
vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
|
||||
AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
|
||||
n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
|
||||
sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
|
||||
---- END SSH2 PUBLIC KEY ----"""
|
||||
|
||||
EXAMPLE_KEY_3_RFC4716 = """---- BEGIN SSH2 PUBLIC KEY ----
|
||||
Comment: DSA Public Key for use with MyIsp
|
||||
AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
|
||||
W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
|
||||
YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
|
||||
vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
|
||||
J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
|
||||
vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
|
||||
AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
|
||||
n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
|
||||
sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
|
||||
---- END SSH2 PUBLIC KEY ----"""
|
||||
|
||||
EXAMPLE_KEY_4_OPENSSH = "".join((
|
||||
"ssh-rsa ",
|
||||
"AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb",
|
||||
"YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ",
|
||||
"5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE="
|
||||
))
|
||||
|
||||
EXAMPLE_KEY_5_RFC4716_MULTILINE = """---- BEGIN SSH2 PUBLIC KEY ----
|
||||
Comment: DSA Public Key \\
|
||||
for use with \\
|
||||
MyIsp
|
||||
AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
|
||||
W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
|
||||
YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
|
||||
vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
|
||||
J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
|
||||
vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
|
||||
AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
|
||||
n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
|
||||
sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
|
||||
---- END SSH2 PUBLIC KEY ----"""
|
||||
|
||||
EXAMPLE_KEY_6_RFC4716_EMPTY_LINE = """---- BEGIN SSH2 PUBLIC KEY ----
|
||||
Comment: DSA Public Key for use with MyIsp
|
||||
|
||||
AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
|
||||
W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
|
||||
YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
|
||||
vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
|
||||
J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
|
||||
vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
|
||||
AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
|
||||
n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
|
||||
sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
|
||||
---- END SSH2 PUBLIC KEY ----"""
|
||||
|
||||
Customer = get_user_model()
|
||||
|
||||
|
||||
@override_settings(
|
||||
|
@ -29,9 +105,10 @@ class TestCaseWithCeleryTasks(TestCase):
|
|||
|
||||
class AdditionalGroupTest(TestCaseWithCeleryTasks):
|
||||
def setUp(self):
|
||||
customer = Customer.objects.create(username='test')
|
||||
self.group1 = Group.objects.create(groupname='test1', gid=1000)
|
||||
self.user = User.objects.create(
|
||||
username='test', uid=1000, group=self.group1,
|
||||
customer=customer, username='test', uid=1000, group=self.group1,
|
||||
homedir='/home/test', shell='/bin/bash')
|
||||
|
||||
def test_clean_primary_group(self):
|
||||
|
@ -48,13 +125,11 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks):
|
|||
|
||||
def test_save(self):
|
||||
group2 = Group.objects.create(groupname='test2', gid=1001)
|
||||
GroupTaskResult.objects.all().delete()
|
||||
addgroup = AdditionalGroup(user=self.user, group=group2)
|
||||
addgroup.save()
|
||||
taskres = GroupTaskResult.objects.all()
|
||||
taskres = TaskResult.objects.all()
|
||||
self.assertTrue(len(taskres), 1)
|
||||
self.assertEqual(taskres[0].task_name, 'add_ldap_user_to_group')
|
||||
self.assertEqual(taskres[0].group, group2)
|
||||
self.assertEqual(taskres[0].task_name, 'setup_file_sftp_userdir')
|
||||
|
||||
def test_delete(self):
|
||||
group2 = Group.objects.create(groupname='test2', gid=1001)
|
||||
|
@ -86,26 +161,27 @@ class GroupTest(TestCaseWithCeleryTasks):
|
|||
def test_save(self):
|
||||
group = Group(gid=10000, groupname='test')
|
||||
self.assertIs(group.save(), group)
|
||||
taskres = GroupTaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 1)
|
||||
self.assertEqual(taskres[0].group, group)
|
||||
self.assertEqual(taskres[0].task_name, 'create_ldap_group')
|
||||
|
||||
def test_delete(self):
|
||||
group = Group.objects.create(gid=10000, groupname='test')
|
||||
self.assertEqual(len(Group.objects.all()), 1)
|
||||
self.assertEqual(len(GroupTaskResult.objects.all()), 1)
|
||||
group.delete()
|
||||
self.assertEqual(len(Group.objects.all()), 0)
|
||||
self.assertEqual(len(GroupTaskResult.objects.all()), 0)
|
||||
self.assertEqual(len(TaskResult.objects.all()), 1)
|
||||
tr = TaskResult.objects.first()
|
||||
self.assertEqual(tr.task_name, 'delete_ldap_group')
|
||||
|
||||
|
||||
class ShadowManagerTest(TestCaseWithCeleryTasks):
|
||||
def setUp(self):
|
||||
self.customer = Customer.objects.create(username='test')
|
||||
super(ShadowManagerTest, self).setUp()
|
||||
|
||||
def test_create_shadow(self):
|
||||
user = User(
|
||||
username='test', uid=1000,
|
||||
group=Group(gid=1000, groupname='test'),
|
||||
homedir='/home/test', shell='/bin/fooshell')
|
||||
customer=self.customer, username='test', uid=1000,
|
||||
group=Group(gid=1000, groupname='test'), homedir='/home/test',
|
||||
shell='/bin/fooshell')
|
||||
shadow = Shadow.objects.create_shadow(user, 'test')
|
||||
self.assertTrue(sha512_crypt.verify('test', shadow.passwd))
|
||||
self.assertEqual(shadow.changedays,
|
||||
|
@ -119,12 +195,16 @@ class ShadowManagerTest(TestCaseWithCeleryTasks):
|
|||
|
||||
|
||||
class ShadowTest(TestCaseWithCeleryTasks):
|
||||
def setUp(self):
|
||||
self.customer = Customer.objects.create(username='test')
|
||||
super(ShadowTest, self).setUp()
|
||||
|
||||
def test___str__(self):
|
||||
group = Group.objects.create(
|
||||
groupname='test', gid=1000)
|
||||
user = User.objects.create(
|
||||
username='test', uid=1000, group=group, homedir='/home/test',
|
||||
shell='/bin/bash')
|
||||
customer=self.customer, username='test', uid=1000, group=group,
|
||||
homedir='/home/test', shell='/bin/bash')
|
||||
shadow = Shadow(user=user)
|
||||
self.assertEqual(str(shadow), 'for user test (1000)')
|
||||
|
||||
|
@ -132,90 +212,13 @@ class ShadowTest(TestCaseWithCeleryTasks):
|
|||
group = Group.objects.create(
|
||||
groupname='test', gid=1000)
|
||||
user = User.objects.create(
|
||||
username='test', uid=1000, group=group, homedir='/home/test',
|
||||
shell='/bin/bash')
|
||||
customer=self.customer, username='test', uid=1000, group=group,
|
||||
homedir='/home/test', shell='/bin/bash')
|
||||
shadow = Shadow(user=user)
|
||||
shadow.set_password('test')
|
||||
self.assertTrue(sha512_crypt.verify('test', shadow.passwd))
|
||||
|
||||
|
||||
TEST_TASK_UUID = '3120f6a8-2665-4fa3-a785-79efd28bfe92'
|
||||
TEST_TASK_NAME = 'test.task'
|
||||
TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5'
|
||||
|
||||
|
||||
class TaskResultTest(TestCase):
|
||||
def test__set_result_fields_not_ready(self):
|
||||
mock = MagicMock(task_id=TEST_TASK_UUID, task_name=TEST_TASK_NAME)
|
||||
mock.ready.return_value = False
|
||||
tr = DeleteTaskResult.objects.create(mock, TEST_TASK_NAME)
|
||||
self.assertFalse(tr.is_finished)
|
||||
self.assertFalse(tr.is_success)
|
||||
self.assertEqual(tr.state, '')
|
||||
self.assertEqual(tr.result_body, '')
|
||||
|
||||
def test__set_result_fields_ready(self):
|
||||
mock = MagicMock(task_id=TEST_TASK_UUID, task_name=TEST_TASK_NAME,
|
||||
state='SUCCESS', result=TEST_TASK_RESULT)
|
||||
mock.ready.return_value = True
|
||||
tr = DeleteTaskResult.objects.create(mock, TEST_TASK_NAME)
|
||||
self.assertTrue(tr.is_finished)
|
||||
self.assertTrue(tr.is_success)
|
||||
self.assertEqual(tr.state, 'SUCCESS')
|
||||
self.assertEqual(tr.result_body, TEST_TASK_RESULT)
|
||||
|
||||
def test__set_result_fields_exception(self):
|
||||
mock = MagicMock(task_id=TEST_TASK_UUID, task_name=TEST_TASK_NAME,
|
||||
state='FAILURE', result=Exception('Fail'))
|
||||
mock.ready.return_value = True
|
||||
tr = DeleteTaskResult.objects.create(mock, TEST_TASK_NAME)
|
||||
self.assertTrue(tr.is_finished)
|
||||
self.assertFalse(tr.is_success)
|
||||
self.assertEqual(tr.state, 'FAILURE')
|
||||
self.assertEqual(tr.result_body, 'Fail')
|
||||
|
||||
@patch('osusers.models.AsyncResult')
|
||||
def test_update_taskstatus_unfinished(self, asyncres):
|
||||
mock = MagicMock(task_id=TEST_TASK_UUID, task_name=TEST_TASK_NAME)
|
||||
mock.ready.return_value = False
|
||||
tr = DeleteTaskResult.objects.create(mock, TEST_TASK_NAME)
|
||||
self.assertFalse(tr.is_finished)
|
||||
mymock = asyncres(TEST_TASK_UUID)
|
||||
mymock.ready.return_value = True
|
||||
mymock.state = 'SUCCESS'
|
||||
mymock.result = TEST_RESULT
|
||||
tr.update_taskstatus()
|
||||
mymock.ready.assert_called_with()
|
||||
self.assertTrue(tr.is_finished)
|
||||
|
||||
@patch('osusers.models.AsyncResult')
|
||||
def test_update_taskstatus_finished(self, asyncres):
|
||||
mock = MagicMock(task_id=TEST_TASK_UUID, task_name=TEST_TASK_NAME)
|
||||
mock.ready.return_value = True
|
||||
mock.state = 'SUCCESS'
|
||||
mock.result = TEST_RESULT
|
||||
tr = DeleteTaskResult.objects.create(mock, TEST_TASK_NAME)
|
||||
self.assertTrue(tr.is_finished)
|
||||
mymock = asyncres(TEST_TASK_UUID)
|
||||
tr.update_taskstatus()
|
||||
self.assertFalse(mymock.ready.called)
|
||||
self.assertTrue(tr.is_finished)
|
||||
|
||||
|
||||
TEST_RESULT = MagicMock()
|
||||
TEST_RESULT.task_id = TEST_TASK_UUID
|
||||
TEST_RESULT.task_name = TEST_TASK_NAME
|
||||
TEST_RESULT.ready.return_value = False
|
||||
|
||||
|
||||
class TaskResultManagerTest(TestCase):
|
||||
def test_create(self):
|
||||
tr = DeleteTaskResult.objects.create(TEST_RESULT, TEST_TASK_NAME)
|
||||
self.assertIsInstance(tr, DeleteTaskResult)
|
||||
self.assertEqual(tr.task_uuid, TEST_TASK_UUID)
|
||||
self.assertEqual(tr.task_name, TEST_TASK_NAME)
|
||||
|
||||
|
||||
@override_settings(
|
||||
OSUSER_MINUID=10000, OSUSER_MINGID=10000, OSUSER_USERNAME_PREFIX='test',
|
||||
OSUSER_HOME_BASEPATH='/home', OSUSER_DEFAULT_SHELL='/bin/fooshell'
|
||||
|
@ -224,13 +227,18 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
def _create_group(self):
|
||||
return Group.objects.create(gid=10000, groupname='foo')
|
||||
|
||||
def setUp(self):
|
||||
self.customer = Customer.objects.create(username='test')
|
||||
super(UserManagerTest, self).setUp()
|
||||
|
||||
def test_get_next_uid_first(self):
|
||||
self.assertEqual(User.objects.get_next_uid(), 10000)
|
||||
|
||||
def test_get_next_uid_second(self):
|
||||
User.objects.create(
|
||||
uid=10010, username='foo', group=self._create_group(),
|
||||
homedir='/home/foo', shell='/bin/fooshell')
|
||||
customer=self.customer, uid=10010, username='foo',
|
||||
group=self._create_group(), homedir='/home/foo',
|
||||
shell='/bin/fooshell')
|
||||
self.assertEqual(User.objects.get_next_uid(), 10011)
|
||||
|
||||
def test_get_next_username_first(self):
|
||||
|
@ -238,22 +246,23 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
|
||||
def test_get_next_username_second(self):
|
||||
User.objects.create(
|
||||
uid=10000, username='test01', group=self._create_group(),
|
||||
homedir='/home/foo', shell='/bin/fooshell')
|
||||
customer=self.customer, uid=10000, username='test01',
|
||||
group=self._create_group(), homedir='/home/foo',
|
||||
shell='/bin/fooshell')
|
||||
self.assertEqual(User.objects.get_next_username(), 'test02')
|
||||
|
||||
def test_get_next_username_gaps(self):
|
||||
group = self._create_group()
|
||||
User.objects.create(
|
||||
uid=10000, username='test01', group=group,
|
||||
customer=self.customer, uid=10000, username='test01', group=group,
|
||||
homedir='/home/foo', shell='/bin/fooshell')
|
||||
User.objects.create(
|
||||
uid=10002, username='test03', group=group,
|
||||
customer=self.customer, uid=10002, username='test03', group=group,
|
||||
homedir='/home/foo', shell='/bin/fooshell')
|
||||
self.assertEqual(User.objects.get_next_username(), 'test02')
|
||||
|
||||
def test_create_user_first(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
self.assertIsInstance(user, User)
|
||||
self.assertEqual(user.uid, 10000)
|
||||
self.assertEqual(user.group.gid, 10000)
|
||||
|
@ -264,15 +273,16 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
self.assertIsNotNone(user.shadow)
|
||||
|
||||
def test_create_user_tasks(self):
|
||||
user = User.objects.create_user()
|
||||
gtaskres = GroupTaskResult.objects.all()
|
||||
self.assertEqual(len(gtaskres), 1)
|
||||
self.assertEqual(gtaskres[0].task_name, 'create_ldap_group')
|
||||
self.assertEqual(gtaskres[0].group, user.group)
|
||||
User.objects.create_user(customer=self.customer)
|
||||
taskres = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 2)
|
||||
tasknames = [r.task_name for r in taskres]
|
||||
self.assertEqual(tasknames.count('setup_file_sftp_userdir'), 1)
|
||||
self.assertEqual(tasknames.count('setup_file_mail_userdir'), 1)
|
||||
|
||||
def test_create_user_second(self):
|
||||
User.objects.create_user()
|
||||
user = User.objects.create_user()
|
||||
User.objects.create_user(customer=self.customer)
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
self.assertIsInstance(user, User)
|
||||
self.assertEqual(user.uid, 10001)
|
||||
self.assertEqual(user.group.gid, 10001)
|
||||
|
@ -284,7 +294,8 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
self.assertEqual(len(User.objects.all()), 2)
|
||||
|
||||
def test_create_user_known_password(self):
|
||||
user = User.objects.create_user(password='foobar')
|
||||
user = User.objects.create_user(
|
||||
customer=self.customer, password='foobar')
|
||||
self.assertIsInstance(user, User)
|
||||
self.assertEqual(user.uid, 10000)
|
||||
self.assertEqual(user.group.gid, 10000)
|
||||
|
@ -296,7 +307,8 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
self.assertTrue(sha512_crypt.verify('foobar', user.shadow.passwd))
|
||||
|
||||
def test_create_user_predefined_username(self):
|
||||
user = User.objects.create_user(username='tester')
|
||||
user = User.objects.create_user(
|
||||
customer=self.customer, username='tester')
|
||||
self.assertIsInstance(user, User)
|
||||
self.assertEqual(user.uid, 10000)
|
||||
self.assertEqual(user.group.gid, 10000)
|
||||
|
@ -307,7 +319,7 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
self.assertIsNotNone(user.shadow)
|
||||
|
||||
def test_create_user_commit(self):
|
||||
user = User.objects.create_user(commit=True)
|
||||
user = User.objects.create_user(customer=self.customer, commit=True)
|
||||
self.assertIsInstance(user, User)
|
||||
self.assertEqual(user.uid, 10000)
|
||||
self.assertEqual(user.group.gid, 10000)
|
||||
|
@ -323,55 +335,176 @@ class UserManagerTest(TestCaseWithCeleryTasks):
|
|||
OSUSER_HOME_BASEPATH='/home', OSUSER_DEFAULT_SHELL='/bin/fooshell'
|
||||
)
|
||||
class UserTest(TestCaseWithCeleryTasks):
|
||||
def setUp(self):
|
||||
self.customer = Customer.objects.create_user('test')
|
||||
super(UserTest, self).setUp()
|
||||
|
||||
def test___str__(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(self.customer)
|
||||
self.assertEqual(str(user), 'test01 (10000)')
|
||||
|
||||
def test_set_password(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(self.customer)
|
||||
self.assertFalse(sha512_crypt.verify('test', user.shadow.passwd))
|
||||
UserTaskResult.objects.all().delete()
|
||||
user.set_password('test')
|
||||
self.assertTrue(sha512_crypt.verify('test', user.shadow.passwd))
|
||||
taskres = UserTaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 1)
|
||||
self.assertEqual(taskres[0].user, user)
|
||||
self.assertEqual(taskres[0].task_name, 'create_ldap_user')
|
||||
|
||||
def test_save(self):
|
||||
user = User.objects.create_user()
|
||||
UserTaskResult.objects.all().delete()
|
||||
user = User.objects.create_user(self.customer)
|
||||
TaskResult.objects.all().delete()
|
||||
user.save()
|
||||
taskres = UserTaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 1)
|
||||
self.assertEqual(taskres[0].user, user)
|
||||
self.assertEqual(taskres[0].task_name, 'create_ldap_user')
|
||||
taskres = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 2)
|
||||
task_names = [r.task_name for r in taskres]
|
||||
self.assertIn('setup_file_sftp_userdir', task_names)
|
||||
self.assertIn('setup_file_mail_userdir', task_names)
|
||||
|
||||
def test_delete_only_user(self):
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(self.customer)
|
||||
TaskResult.objects.all().delete()
|
||||
user.delete()
|
||||
taskres = DeleteTaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 2)
|
||||
self.assertIn('delete_ldap_user',
|
||||
[r.task_name for r in taskres])
|
||||
self.assertIn('delete_ldap_group_if_empty',
|
||||
[r.task_name for r in taskres])
|
||||
taskres = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 3)
|
||||
tasknames = [r.task_name for r in taskres]
|
||||
self.assertEqual(tasknames.count('delete_file_mail_userdir'), 1)
|
||||
self.assertEqual(tasknames.count('delete_file_sftp_userdir'), 1)
|
||||
self.assertEqual(tasknames.count('delete_ldap_group'), 1)
|
||||
self.assertEqual(len(User.objects.all()), 0)
|
||||
|
||||
def test_delete_additional_groups(self):
|
||||
group1 = Group.objects.create(gid=2000, groupname='group1')
|
||||
group2 = Group.objects.create(gid=2001, groupname='group2')
|
||||
user = User.objects.create_user()
|
||||
user = User.objects.create_user(self.customer)
|
||||
for group in [group1, group2]:
|
||||
user.additionalgroup_set.add(
|
||||
AdditionalGroup.objects.create(user=user, group=group))
|
||||
TaskResult.objects.all().delete()
|
||||
user.delete()
|
||||
taskres = DeleteTaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 4)
|
||||
taskres = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskres), 3)
|
||||
tasknames = [t.task_name for t in taskres]
|
||||
self.assertEqual(tasknames.count('remove_ldap_user_from_group'), 2)
|
||||
self.assertEqual(tasknames.count('delete_ldap_user'), 1)
|
||||
self.assertEqual(tasknames.count('delete_ldap_group_if_empty'), 1)
|
||||
self.assertEqual(tasknames.count('delete_file_mail_userdir'), 1)
|
||||
self.assertEqual(tasknames.count('delete_file_sftp_userdir'), 1)
|
||||
self.assertEqual(tasknames.count('delete_ldap_group'), 1)
|
||||
self.assertEqual(len(User.objects.all()), 0)
|
||||
self.assertEqual(len(AdditionalGroup.objects.all()), 0)
|
||||
|
||||
def test_is_sftp_user(self):
|
||||
user = User.objects.create_user(self.customer)
|
||||
self.assertFalse(user.is_sftp_user())
|
||||
|
||||
sftp_group = Group.objects.create(
|
||||
gid=2000, groupname=settings.OSUSER_SFTP_GROUP)
|
||||
user.additionalgroup_set.add(
|
||||
AdditionalGroup.objects.create(user=user, group=sftp_group))
|
||||
self.assertTrue(user.is_sftp_user())
|
||||
|
||||
|
||||
class SshPublicKeyManagerTest(TestCaseWithCeleryTasks):
|
||||
def test_parse_keytext_rfc4716_1(self):
|
||||
res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_1_RFC4716)
|
||||
self.assertEqual(len(res), 3)
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(res[0], 'ssh-rsa')
|
||||
self.assertEqual(
|
||||
res[2], '"1024-bit RSA, converted from OpenSSH by me@example.com"')
|
||||
|
||||
def test_parse_keytext_rfc4716_2(self):
|
||||
res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_2_RFC4716)
|
||||
self.assertEqual(len(res), 3)
|
||||
self.assertEqual(res[0], 'ssh-dss')
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(
|
||||
res[2],
|
||||
"This is my public key for use on servers which I don't like.")
|
||||
|
||||
def test_parse_keytext_rfc4716_3(self):
|
||||
res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_3_RFC4716)
|
||||
self.assertEqual(len(res), 3)
|
||||
self.assertEqual(res[0], 'ssh-dss')
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(res[2], "DSA Public Key for use with MyIsp")
|
||||
|
||||
def test_parse_keytext_openssh(self):
|
||||
res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_4_OPENSSH)
|
||||
self.assertEquals(len(res), 3)
|
||||
self.assertEqual(res[0], 'ssh-rsa')
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(res[2], '')
|
||||
|
||||
def test_parse_keytext_invalid(self):
|
||||
with self.assertRaises(ValueError):
|
||||
SshPublicKey.objects.parse_keytext("\r\n".join(["xx"]*10))
|
||||
|
||||
def test_parse_keytext_empty_line(self):
|
||||
res = SshPublicKey.objects.parse_keytext(
|
||||
EXAMPLE_KEY_6_RFC4716_EMPTY_LINE)
|
||||
self.assertEqual(len(res), 3)
|
||||
self.assertEqual(res[0], 'ssh-dss')
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(res[2], "DSA Public Key for use with MyIsp")
|
||||
|
||||
def test_parse_keytext_multiline_comment(self):
|
||||
res = SshPublicKey.objects.parse_keytext(
|
||||
EXAMPLE_KEY_5_RFC4716_MULTILINE)
|
||||
self.assertEqual(len(res), 3)
|
||||
self.assertEqual(res[0], 'ssh-dss')
|
||||
self.assertGreater(len(res[1]), 40)
|
||||
self.assertEqual(res[2], "DSA Public Key for use with MyIsp")
|
||||
|
||||
def test_create_ssh_public_key(self):
|
||||
customer = Customer.objects.create_user('test')
|
||||
user = User.objects.create_user(customer)
|
||||
key = SshPublicKey.objects.create_ssh_public_key(
|
||||
user, EXAMPLE_KEY_4_OPENSSH)
|
||||
self.assertIsInstance(key, SshPublicKey)
|
||||
self.assertEqual(key.user, user)
|
||||
self.assertEqual(key.algorithm, 'ssh-rsa')
|
||||
self.assertEqual(key.data, EXAMPLE_KEY_4_OPENSSH.split()[1])
|
||||
self.assertEqual(key.comment, '')
|
||||
|
||||
|
||||
class SshPublicKeyTest(TestCaseWithCeleryTasks):
|
||||
def setUp(self):
|
||||
super(SshPublicKeyTest, self).setUp()
|
||||
customer = Customer.objects.create_user('test')
|
||||
self.user = User.objects.create_user(customer)
|
||||
TaskResult.objects.all().delete()
|
||||
|
||||
def test__str__rfc4716(self):
|
||||
res = SshPublicKey.objects.create_ssh_public_key(
|
||||
self.user, EXAMPLE_KEY_3_RFC4716)
|
||||
self.assertEqual(
|
||||
str(res), 'ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxae'
|
||||
'hvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7St'
|
||||
'xyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3g'
|
||||
'Jq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4'
|
||||
'KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/F'
|
||||
'XPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVc'
|
||||
'GcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LA'
|
||||
'o2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWK'
|
||||
'jjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP'
|
||||
'+CDqzCM4loWgV DSA Public Key for use with MyIsp')
|
||||
|
||||
def test__str__openssh(self):
|
||||
res = SshPublicKey.objects.create_ssh_public_key(
|
||||
self.user, EXAMPLE_KEY_4_OPENSSH)
|
||||
self.assertEqual(str(res), EXAMPLE_KEY_4_OPENSSH)
|
||||
|
||||
def test_call_tasks_on_save(self):
|
||||
SshPublicKey.objects.create_ssh_public_key(
|
||||
self.user, EXAMPLE_KEY_4_OPENSSH)
|
||||
taskresults = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskresults), 1)
|
||||
self.assertEqual(
|
||||
taskresults[0].task_name, 'set_file_ssh_authorized_keys')
|
||||
|
||||
def test_call_tasks_on_delete(self):
|
||||
key = SshPublicKey.objects.create_ssh_public_key(
|
||||
self.user, EXAMPLE_KEY_4_OPENSSH)
|
||||
TaskResult.objects.all().delete()
|
||||
key.delete()
|
||||
taskresults = TaskResult.objects.all()
|
||||
self.assertEqual(len(taskresults), 1)
|
||||
self.assertEqual(
|
||||
taskresults[0].task_name, 'set_file_ssh_authorized_keys')
|
||||
|
|
|
@ -6,11 +6,25 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import SetOsUserPassword
|
||||
from .views import (
|
||||
AddSshPublicKey,
|
||||
DeleteSshPublicKey,
|
||||
EditSshPublicKeyComment,
|
||||
ListSshPublicKeys,
|
||||
SetOsUserPassword,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^(?P<slug>[\w0-9@.+-_]+)/setpassword$', SetOsUserPassword.as_view(),
|
||||
name='set_osuser_password'),
|
||||
url(r'^(?P<package>\d+)/ssh-keys/$', ListSshPublicKeys.as_view(),
|
||||
name='list_ssh_keys'),
|
||||
url(r'^(?P<package>\d+)/ssh-keys/add$', AddSshPublicKey.as_view(),
|
||||
name='add_ssh_key'),
|
||||
url(r'^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/edit-comment$',
|
||||
EditSshPublicKeyComment.as_view(), name='edit_ssh_key_comment'),
|
||||
url(r'^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/delete$',
|
||||
DeleteSshPublicKey.as_view(), name='delete_ssh_key'),
|
||||
)
|
||||
|
|
|
@ -4,15 +4,29 @@ This module defines the views for gnuviechadmin operating system user handling.
|
|||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import UpdateView
|
||||
from django.views.generic import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
ListView,
|
||||
UpdateView,
|
||||
)
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import messages
|
||||
|
||||
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
|
||||
from gvawebcore.views import HostingPackageAndCustomerMixin
|
||||
|
||||
from .forms import ChangeOsUserPasswordForm
|
||||
from .models import User
|
||||
from .forms import (
|
||||
AddSshPublicKeyForm,
|
||||
ChangeOsUserPasswordForm,
|
||||
EditSshPublicKeyCommentForm,
|
||||
)
|
||||
from .models import (
|
||||
SshPublicKey,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
|
||||
|
@ -43,3 +57,131 @@ class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
|
|||
username=osuser.username
|
||||
))
|
||||
return redirect(osuser.customerhostingpackage)
|
||||
|
||||
|
||||
class AddSshPublicKey(
|
||||
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, CreateView
|
||||
):
|
||||
"""
|
||||
This view is used to add an SSH key for an existing hosting account's
|
||||
operating system user.
|
||||
|
||||
"""
|
||||
model = SshPublicKey
|
||||
context_object_name = 'key'
|
||||
template_name_suffix = '_create'
|
||||
form_class = AddSshPublicKeyForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(AddSshPublicKey, self).get_form_kwargs()
|
||||
kwargs['hostingpackage'] = self.get_hosting_package()
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddSshPublicKey, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'customer': self.get_customer_object(),
|
||||
'osuser': self.get_hosting_package().osuser.username,
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
key = form.save()
|
||||
messages.success(
|
||||
self.request,
|
||||
_('Successfully added new {algorithm} SSH public key').format(
|
||||
algorithm=key.algorithm)
|
||||
)
|
||||
return redirect(self.get_hosting_package())
|
||||
|
||||
|
||||
class ListSshPublicKeys(
|
||||
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, ListView
|
||||
):
|
||||
"""
|
||||
This view is used for showing the list of :py:class:`SSH public keys
|
||||
<osusers.models.SshPublicKey>` assigned to the hosting package specified
|
||||
via URL parameter 'pattern'.
|
||||
|
||||
"""
|
||||
model = SshPublicKey
|
||||
context_object_name = 'keys'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ListSshPublicKeys, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'hostingpackage': self.get_hosting_package(),
|
||||
'customer': self.get_customer_object(),
|
||||
'osuser': self.get_hosting_package().osuser.username,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class DeleteSshPublicKey(
|
||||
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, DeleteView
|
||||
):
|
||||
"""
|
||||
This view is used for delete confirmation of a :py:class:`SSH public key
|
||||
<osusers.models.SshPublicKey>`.
|
||||
|
||||
"""
|
||||
|
||||
model = SshPublicKey
|
||||
context_object_name = 'key'
|
||||
|
||||
def get_queryset(self):
|
||||
return super(DeleteSshPublicKey, self).get_queryset().filter(
|
||||
user=self.get_hosting_package().osuser)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DeleteSshPublicKey, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'hostingpackage': self.get_hosting_package(),
|
||||
'customer': self.get_customer_object(),
|
||||
'osuser': self.get_hosting_package().osuser.username,
|
||||
})
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(
|
||||
'list_ssh_keys', kwargs={'package': self.get_hosting_package().id}
|
||||
)
|
||||
|
||||
|
||||
class EditSshPublicKeyComment(
|
||||
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, UpdateView
|
||||
):
|
||||
"""
|
||||
This view is used for editing the comment field of a :py:class:`SSH public
|
||||
key <osusers.models.SshPublicKey>`.
|
||||
|
||||
"""
|
||||
model = SshPublicKey
|
||||
context_object_name = 'key'
|
||||
fields = ['comment']
|
||||
template_name_suffix = '_edit_comment'
|
||||
form_class = EditSshPublicKeyCommentForm
|
||||
|
||||
def get_queryset(self):
|
||||
return super(EditSshPublicKeyComment, self).get_queryset().filter(
|
||||
user=self.get_hosting_package().osuser)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(EditSshPublicKeyComment, self).get_form_kwargs()
|
||||
kwargs['hostingpackage'] = self.get_hosting_package()
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditSshPublicKeyComment, self).get_context_data(
|
||||
**kwargs)
|
||||
context.update({
|
||||
'hostingpackage': self.get_hosting_package(),
|
||||
'customer': self.get_customer_object(),
|
||||
'osuser': self.get_hosting_package().osuser.username,
|
||||
})
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(
|
||||
'list_ssh_keys', kwargs={'package': self.get_hosting_package().id}
|
||||
)
|
||||
|
|
0
gnuviechadmin/taskresults/tests/__init__.py
Normal file
0
gnuviechadmin/taskresults/tests/__init__.py
Normal file
54
gnuviechadmin/taskresults/tests/test_models.py
Normal file
54
gnuviechadmin/taskresults/tests/test_models.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from django.test import TestCase
|
||||
|
||||
from mock import patch, MagicMock
|
||||
from taskresults.models import TaskResult
|
||||
|
||||
|
||||
TEST_TASK_UUID = '3120f6a8-2665-4fa3-a785-79efd28bfe92'
|
||||
TEST_TASK_NAME = 'test.task'
|
||||
TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5'
|
||||
|
||||
|
||||
class TaskResultTest(TestCase):
|
||||
@patch('taskresults.models.app')
|
||||
def test_update_taskstatus_unfinished(self, app):
|
||||
mock = MagicMock(id=TEST_TASK_UUID, task_name=TEST_TASK_NAME)
|
||||
mock.ready.return_value = False
|
||||
tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME)
|
||||
self.assertFalse(tr.finished)
|
||||
mymock = app.AsyncResult(TEST_TASK_UUID)
|
||||
mymock.state = 'SUCCESS'
|
||||
mymock.get.return_value = TEST_RESULT
|
||||
tr.fetch_result()
|
||||
mymock.get.assert_called_with(no_ack=True, timeout=1)
|
||||
self.assertTrue(tr.finished)
|
||||
|
||||
@patch('taskresults.models.app')
|
||||
def test_update_taskstatus_finished(self, app):
|
||||
mock = MagicMock(id=TEST_TASK_UUID, task_name=TEST_TASK_NAME)
|
||||
mock.ready.return_value = True
|
||||
mock.state = 'SUCCESS'
|
||||
mock.result = TEST_RESULT
|
||||
tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME)
|
||||
tr.fetch_result()
|
||||
self.assertTrue(tr.finished)
|
||||
mymock = app.AsyncResult(TEST_TASK_UUID)
|
||||
tr.fetch_result()
|
||||
self.assertEqual(mymock.get.call_count, 1)
|
||||
self.assertTrue(tr.finished)
|
||||
|
||||
|
||||
TEST_RESULT = MagicMock()
|
||||
TEST_RESULT.task_id = TEST_TASK_UUID
|
||||
TEST_RESULT.task_name = TEST_TASK_NAME
|
||||
TEST_RESULT.ready.return_value = False
|
||||
|
||||
|
||||
class TaskResultManagerTest(TestCase):
|
||||
def test_create_task_result(self):
|
||||
mock = MagicMock(id=TEST_TASK_UUID)
|
||||
tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME)
|
||||
self.assertIsInstance(tr, TaskResult)
|
||||
self.assertEqual(tr.task_id, TEST_TASK_UUID)
|
||||
self.assertEqual(tr.task_name, TEST_TASK_NAME)
|
|
@ -82,11 +82,12 @@
|
|||
<h1>{% block page_title %}Example Base Template{% endblock page_title %}</h1>
|
||||
|
||||
{% if messages %}
|
||||
<ul class="list-unstyled">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-dismissible {{ message.tags }}" role="alert">
|
||||
<button type="close" class="close" data-dismiss="alert" aria-label="{% trans "Close" %}"><span aria-hidden="true">×</span></button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<dd>{% blocktrans with num=hostingpackage.used_mailbox_count total=hostingpackage.mailbox_count %}{{ num }} of {{ total }} in use{% endblocktrans %} <span class="glyphicon
|
||||
glyphicon-info-sign" title="{% blocktrans with mailboxcount=hostingpackage.mailboxcount %}The package provides {{ mailboxcount }} mailboxes the difference comes from mailbox options.{% endblocktrans %}"></span></dd>
|
||||
<dt>{% if osuser.is_sftp_user %}{% trans "SFTP username" %}{% else %}{% trans "SSH/SFTP username" %}{% endif %}</dt>
|
||||
<dd>{{ osuser.username }}</dd>
|
||||
<dd>{{ osuser.username }}{% if sshkeys %} <a href="{% url 'list_ssh_keys' package=hostingpackage.id %}" class="badge" title="{% blocktrans count counter=sshkeys|length %}There is an SSH public key set for this user.{% plural %}There are {{ counter }} SSH public keys set for this user.{% endblocktrans %}"><i class="fa fa-key"></i> {{ sshkeys|length }}</a>{% endif %}</dd>
|
||||
<dt>{% trans "Upload server" %}</dt>
|
||||
<dd>{{ uploadserver }}</dd>
|
||||
</dl>
|
||||
|
@ -68,6 +68,7 @@
|
|||
<ul class="list-group">
|
||||
<li class="list-group-item"><a href="#" title="{% trans "Edit Hosting Package Description" %}">{% trans "Edit description" %}</a></li>
|
||||
<li class="list-group-item"><a href="{% url "set_osuser_password" slug=osuser.username %}">{% if osuser.is_sftp_user %}{% trans "Set SFTP password" %}{% else %}{% trans "Set SSH/SFTP password" %}{% endif %}</a></li>
|
||||
<li class="list-group-item"><a href="{% url "add_ssh_key" package=hostingpackage.id %}" title="{% blocktrans %}Add an SSH public key that can be used as an alternative for password{% endblocktrans %}">{% trans "Add SSH public key" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "osusers/base.html" %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Delete SSH Public Key for Operating System User {{ osuser }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Delete SSH Public Key for Operating System User {{ osuser }} of Customer {{ full_name }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock title %}
|
||||
|
||||
{% block page_title %}{% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Delete SSH Public Key <small>for Operating System User {{ osuser }}</small>{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Delete SSH Public Key <small>for Operating System User {{ osuser }} of Customer {{ full_name }}</small>{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
{% blocktrans with algorithm=key.algorithm %}Do you really want to delete the {{ algorithm }} SSH public key?{% endblocktrans %}
|
||||
</div>
|
||||
<div class="panel-body form">
|
||||
<p >{% blocktrans %}When you confirm the deletion of this key you will no longer be able to use the corresponding private key for authentication.{% endblocktrans %}</p>
|
||||
<pre>{{ key }}</pre>
|
||||
<form action="{% url 'delete_ssh_key' package=hostingpackage.id pk=key.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input class="btn btn-warning" type="submit" value="{% trans "Yes, do it!" %}" />
|
||||
<a class="btn btn-default" href="{% url 'list_ssh_keys' package=hostingpackage.id %}" title="{% trans "Cancel and go back to the SSH key list" %}">{% trans "Cancel" %}</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
30
gnuviechadmin/templates/osusers/sshpublickey_create.html
Normal file
30
gnuviechadmin/templates/osusers/sshpublickey_create.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends "osusers/base.html" %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Add new SSH Public Key for Operating System User {{ osuser }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Add a new SSH Public Key for Operating System User {{ osuser }} of Customer {{ full_name }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock title %}
|
||||
|
||||
{% block page_title %}{% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Add new SSH Public Key <small>for Operating System User {{ osuser }}</small>{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Add a new SSH Public Key <small>for Operating System User {{ osuser }} of Customer {{ full_name }}</small>{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
{% crispy form %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('textarea').first().focus();
|
||||
});
|
||||
</script>
|
||||
{% endblock extra_js %}
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "osusers/base.html" %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Edit Comment of SSH Public Key for Operating System User {{ osuser }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Edit Comment of SSH Public Key for Operating System User {{ osuser }} of Customer {{ full_name }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock title %}
|
||||
|
||||
{% block page_title %}{% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}Edit Comment of Public Key <small>for Operating System User {{ osuser }}</small>{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}Edit Comment of SSH Public Key <small>for Operating System User {{ osuser }} of Customer {{ full_name }}</small>{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
{% crispy form %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('input[type=text]').first().focus().select();
|
||||
});
|
||||
</script>
|
||||
{% endblock extra_js %}
|
47
gnuviechadmin/templates/osusers/sshpublickey_list.html
Normal file
47
gnuviechadmin/templates/osusers/sshpublickey_list.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
{% extends "osusers/base.html" %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}SSH Public Keys for Operating System User {{ osuser }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}SSH Public Keys for Operating System User {{ osuser }} of Customer {{ full_name }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock title %}
|
||||
|
||||
{% block page_title %}{% spaceless %}
|
||||
{% if user == customer %}
|
||||
{% blocktrans %}SSH Public Keys <small>for Operating System User {{ osuser }}</small>{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with full_name=customer.get_full_name %}SSH Public Keys <small>for Operating System User {{ osuser }} of Customer {{ full_name }}</small>{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endspaceless %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
{% if keys %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name-column">{% trans "Algorithm" %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th title="{% trans "SSH public key actions" %}" class="actions-column"><span class="sr-only">{% trans "Actions" %}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key in keys %}
|
||||
<tr>
|
||||
<td>{{ key.algorithm }}</td>
|
||||
<td>{{ key.comment }}</td>
|
||||
<td>
|
||||
<a href="{% url 'delete_ssh_key' package=hostingpackage.id pk=key.id %}" title="{% trans "Delete this SSH public key" %}"><i class="glyphicon glyphicon-trash"></i><span class="sr-only"> {% trans "Delete" %}</span></a>
|
||||
<a href="{% url 'edit_ssh_key_comment' package=hostingpackage.id pk=key.id %}" title="{% trans "Edit this SSH public key's comment" %}"><i class="glyphicon glyphicon-pencil"></i><span class="sr-only"> {% trans "Edit Comment" %}</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="bg-warning">{% trans "There are now SSH public keys set for this operating system user yet." %}</p>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'add_ssh_key' package=hostingpackage.id %}" class="btn btn-primary">{% trans "Add SSH public key" %}</a></p>
|
||||
{% endblock content %}
|
|
@ -1,4 +1,4 @@
|
|||
Django==1.7.3
|
||||
Django==1.7.4
|
||||
bpython==0.13.1
|
||||
django-braces==1.4.0
|
||||
django-model-utils==2.2
|
||||
|
|
Loading…
Reference in a new issue