diff --git a/openslides/application/models.py b/openslides/application/models.py index 5894fe542..c75cc5024 100644 --- a/openslides/application/models.py +++ b/openslides/application/models.py @@ -323,7 +323,7 @@ class Application(models.Model): if self.status == "pub" \ and user != self.submitter \ and user not in self.supporter.all() \ - and user.profile: + and getattr(user, 'profile', None): actions.append("support") except Profile.DoesNotExist: pass diff --git a/openslides/application/views.py b/openslides/application/views.py index 1435967cf..95a61f374 100644 --- a/openslides/application/views.py +++ b/openslides/application/views.py @@ -9,9 +9,9 @@ :copyright: 2011 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ +from __future__ import with_statement import csv - from django.shortcuts import redirect from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -441,6 +441,9 @@ def application_import(request): return redirect(reverse('application_overview')) except Profile.DoesNotExist: pass + except AttributeError: + # AnonymousUser + pass if request.method == 'POST': form = ApplicationImportForm(request.POST, request.FILES) diff --git a/openslides/default.settings.py b/openslides/default.settings.py index 59190e2a5..ecc441257 100644 --- a/openslides/default.settings.py +++ b/openslides/default.settings.py @@ -18,6 +18,7 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG AUTH_PROFILE_MODULE = 'participant.Profile' +AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', 'openslides.system.auth.AnonymousAuth',) LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/agenda/' @@ -137,4 +138,5 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', 'django.core.context_processors.i18n', 'utils.utils.revision', + 'openslides.system.auth.anonymous_context_additions', ) diff --git a/openslides/locale/de/LC_MESSAGES/django.mo b/openslides/locale/de/LC_MESSAGES/django.mo index 1cb82239d..12f0ffe41 100644 Binary files a/openslides/locale/de/LC_MESSAGES/django.mo and b/openslides/locale/de/LC_MESSAGES/django.mo differ diff --git a/openslides/locale/de/LC_MESSAGES/django.po b/openslides/locale/de/LC_MESSAGES/django.po index 636cbd984..644c763e9 100644 --- a/openslides/locale/de/LC_MESSAGES/django.po +++ b/openslides/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-11-09 22:57+0100\n" +"POT-Creation-Date: 2011-11-10 23:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,11 +17,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -#: default.settings.py:59 settings.py:59 +#: default.settings.py:60 settings.py:60 msgid "German" msgstr "Deutsch" -#: default.settings.py:60 settings.py:60 +#: default.settings.py:61 settings.py:61 msgid "English" msgstr "Englisch" @@ -53,7 +53,7 @@ msgstr "Ja, mit allen Kindelementen." msgid "No" msgstr "Nein" -#: agenda/api.py:67 participant/views.py:164 participant/views.py:242 +#: agenda/api.py:67 participant/views.py:165 participant/views.py:268 #: utils/utils.py:40 #, python-format msgid "Do you really want to delete %s?" @@ -145,9 +145,9 @@ msgstr "Eintrag wurde erfolgreich geändert." msgid "Agenda item modified" msgstr "Tagesordnungseintrag geändert" -#: agenda/views.py:283 application/views.py:526 participant/views.py:141 -#: participant/views.py:227 participant/views.py:256 participant/views.py:313 -#: system/views.py:32 system/views.py:74 +#: agenda/views.py:283 application/views.py:526 participant/views.py:142 +#: participant/views.py:253 participant/views.py:282 participant/views.py:339 +#: system/views.py:48 system/views.py:91 msgid "Please check the form for errors." msgstr "Bitte kontrollieren Sie das Formular nach Fehlern." @@ -727,7 +727,7 @@ msgstr "FEHLER beim Zurückweisen der Version." msgid "Do you really want to reject version %s?" msgstr "Soll Version %s wirklich zurückgewiesen werden?" -#: application/views.py:440 participant/views.py:272 +#: application/views.py:440 participant/views.py:298 msgid "" "The import function is available for the superuser (without user profile) " "only." @@ -1359,62 +1359,67 @@ msgstr "Amt" msgid "First Password" msgstr "Erstes Passwort" -#: participant/views.py:133 +#: participant/views.py:134 msgid "New participant was successfully created." msgstr "Neue/r Teilnehmer/in wurde erfolgreich angelegt." -#: participant/views.py:135 +#: participant/views.py:136 msgid "Participant was successfully modified." msgstr "Teilnehmer/in wurde erfolgreich geändert." -#: participant/views.py:162 +#: participant/views.py:163 #, python-format msgid "Participant %s was successfully deleted." msgstr "Teilnehmer/in %s wurde erfolgreich gelöscht." -#: participant/views.py:174 +#: participant/views.py:175 #, python-format msgid "Participant %s is now a normal user." msgstr "Teilnehmer/in %s ist jetzt ein normaler Nutzer." -#: participant/views.py:178 +#: participant/views.py:179 #, python-format msgid "Participant %s is now administrator." msgstr "Teilnehmer/in %s ist jetzt ein Administrator." -#: participant/views.py:188 +#: participant/views.py:189 #, python-format msgid "Participant %s was successfully deactivated." msgstr "Teilnehmer/in %s wurde erfolgreich deaktiviert." -#: participant/views.py:192 +#: participant/views.py:193 #, python-format msgid "Participant %s was successfully activated." msgstr "Teilnehmer/in %s wurde erfolgreich aktiviert." -#: participant/views.py:219 +#: participant/views.py:231 +#, python-format +msgid "Group name \"%s\" is reserved for internal use." +msgstr "Der Gruppenname \"%s\" ist für interne Verwendung reserviert." + +#: participant/views.py:245 msgid "New group was successfully created." msgstr "Neue Gruppe wurde erfolgreich angelegt." -#: participant/views.py:221 +#: participant/views.py:247 msgid "Group was successfully modified." msgstr "Gruppe wurde erfolgreich geändert." -#: participant/views.py:240 +#: participant/views.py:266 #, python-format msgid "Group %s was successfully deleted." msgstr "Gruppe %s wurde erfolgreich gelöscht." -#: participant/views.py:254 +#: participant/views.py:280 msgid "User settings successfully saved." msgstr "Nutzereinstellungen wurden erfolgreich gespeichert." -#: participant/views.py:311 +#: participant/views.py:337 #, python-format msgid "%d new participants were successfully imported." msgstr "%d neue Teilnehmer/innen wurden erfolgreich importiert." -#: participant/views.py:315 +#: participant/views.py:341 msgid "" "Attention: All existing participants will be removed if you import new " "participants." @@ -1422,14 +1427,14 @@ msgstr "" "Achtung: Alle existierenden Teilnehmer/innen werden gelöscht, wenn Sie neue " "Teilnehmer/innen importieren." -#: participant/views.py:326 +#: participant/views.py:352 #, python-format msgid "%s Password was successfully generated." msgid_plural "%s Passwords were successfully generated." msgstr[0] "Es wurde %s Passwort erfolgreich generiert." msgstr[1] "Es wurden %s Passwörter erfolgreich generiert." -#: participant/views.py:328 +#: participant/views.py:354 msgid "" "There are no participants which need a first time password. No passwords " "generated." @@ -1437,12 +1442,12 @@ msgstr "" "Es sind keine Teilnehmer/innen ohne Erst-Passwort vorhanden. Es wurden keine " "Passwörter generiert." -#: participant/views.py:337 +#: participant/views.py:363 #, python-format msgid "The Password for %s was successfully reset." msgstr "Das Passwort für %s wurde erfolgreich zurückgesetzt." -#: participant/views.py:339 +#: participant/views.py:365 #, python-format msgid "Do you really want to reset the password for %s?" msgstr "Soll das Passwort für %s wirklich zurückgesetzt werden?" @@ -1528,11 +1533,11 @@ msgstr "Aktionen" msgid "Edit group" msgstr "Benutzergruppe bearbeiten" -#: participant/templates/participant/group_overview.html:15 +#: participant/templates/participant/group_overview.html:16 msgid "Delete group" msgstr "Benutzergruppe löschen" -#: participant/templates/participant/group_overview.html:20 +#: participant/templates/participant/group_overview.html:22 #: participant/templates/participant/overview.html:84 msgid "No participants available." msgstr "Keine Teilnehmer/innen vorhanden." @@ -1551,7 +1556,7 @@ msgstr "" #: participant/templates/participant/login.html:5 #: participant/templates/participant/login.html:13 -#: participant/templates/participant/login.html:43 templates/base.html:26 +#: participant/templates/participant/login.html:42 templates/base.html:26 msgid "Login" msgstr "Anmelden" @@ -1564,6 +1569,10 @@ msgid "Your username and password didn't match. Please try again." msgstr "" "Benutzername und Passwort stimmen nicht überein. Bitte noch einmal versuchen." +#: participant/templates/participant/login.html:46 +msgid "Continue as guest" +msgstr "Weiter als Gast" + #: participant/templates/participant/overview.html:42 utils/pdf.py:263 msgid "First Name" msgstr "Vorname" @@ -1637,79 +1646,95 @@ msgstr "System URL" msgid "Welcome text (for password PDF)" msgstr "Willkommenstext (für Passwort-PDF-Liste)" -#: system/forms.py:29 +#: system/forms.py:24 +msgid "Access for anonymous / guest users" +msgstr "Zugriff für anonyme oder Gast-Nutzer" + +#: system/forms.py:24 +msgid "Allow access for guest users" +msgstr "Zugriff für Gast-Nutzer aktivieren" + +#: system/forms.py:30 msgid "Event name" msgstr "Veranstaltungsname" -#: system/forms.py:30 +#: system/forms.py:31 msgid "Short description of event" msgstr "Kurzbeschreibung der Veranstaltung" -#: system/forms.py:31 +#: system/forms.py:32 msgid "Event date" msgstr "Veranstaltungszeitraum" -#: system/forms.py:32 +#: system/forms.py:33 msgid "Event location" msgstr "Veranstaltungsort" -#: system/forms.py:33 +#: system/forms.py:34 msgid "Event organizer" msgstr "Veranstalter" -#: system/forms.py:39 +#: system/forms.py:40 msgid "Countdown (in seconds)" msgstr "Countdown (in Sekunden)" -#: system/forms.py:45 +#: system/forms.py:46 msgid "Number of (minimum) required supporters for a application" msgstr "Mindestanzahl erforderlicher Unterstützer/innen für einen Antrag" -#: system/forms.py:46 +#: system/forms.py:47 msgid "Application preamble" msgstr "Antragseinleitung" -#: system/forms.py:47 system/forms.py:58 +#: system/forms.py:48 system/forms.py:59 msgid "Number of ballot papers (selection)" msgstr "Anzahl der Wahlscheine (Vorauswahl)" -#: system/forms.py:47 system/forms.py:58 +#: system/forms.py:48 system/forms.py:59 msgid "Number of all delegates" msgstr "Anzahl aller Delegierten" -#: system/forms.py:47 system/forms.py:58 +#: system/forms.py:48 system/forms.py:59 msgid "Number of all participants" msgstr "Anzahl aller Teilnehmer/innen" -#: system/forms.py:47 system/forms.py:58 +#: system/forms.py:48 system/forms.py:59 msgid "Use the following custum number" msgstr "Verwende die folgende benutzerdefinierte Anzahl" -#: system/forms.py:48 system/forms.py:59 +#: system/forms.py:49 system/forms.py:60 msgid "Custom number of ballot papers" msgstr "Benutzerdefinierte Anzahl von Wahlscheinen" -#: system/forms.py:49 +#: system/forms.py:50 msgid "Title for PDF document (all applications)" msgstr "Titel für PDF-Dokuemt (alle Anträge)" -#: system/forms.py:50 +#: system/forms.py:51 msgid "Preamble text for PDF document (all applications)" msgstr "Einleitungstext für PDF-Dokument (alle Anträge)" -#: system/forms.py:56 +#: system/forms.py:57 msgid "Title for PDF document (all elections)" msgstr "Titel für PDF-Dokument (alle Wahlen)" -#: system/forms.py:57 +#: system/forms.py:58 msgid "Preamble text for PDF document (all elections)" msgstr "Einleitungstext für PDF-Dokument (alle Wahlen) " -#: system/views.py:30 +#: system/views.py:42 +msgid "" +"Anonymous access enabled. Please modify the \"Anonymous\" group to fit your " +"required permissions." +msgstr "" +"Anonymer Zugriff aktiviert. Bitte setzen Sie die Rechte der Gruppe \"Anonymous\" " +"passend zum gewünschten Zugriffslevel." + +#: system/views.py:46 msgid "System settings successfully saved." msgstr "Systemeinstellungen erfolgreich gespeichert." -#: system/views.py:72 +#: system/views.py:89 msgid "General settings successfully saved." msgstr "Allgemeine Einstellungen erfolgreich gespeichert." diff --git a/openslides/participant/templates/participant/group_overview.html b/openslides/participant/templates/participant/group_overview.html index 2f59c826c..c96172ce7 100644 --- a/openslides/participant/templates/participant/group_overview.html +++ b/openslides/participant/templates/participant/group_overview.html @@ -12,7 +12,9 @@ {{ group.name }} + {% if group.name != 'Anonymous' %} + {% endif %} {% empty %} diff --git a/openslides/participant/templates/participant/login.html b/openslides/participant/templates/participant/login.html index 6e9310601..01e71f1d9 100644 --- a/openslides/participant/templates/participant/login.html +++ b/openslides/participant/templates/participant/login.html @@ -25,7 +25,6 @@ }); {% endif %} -
{% csrf_token %} @@ -42,7 +41,20 @@ + {% if os_enable_anonymous_login %} + + {% endif %}

+ {% if os_enable_anonymous_login %} + + {% endif %} {% endblock %} diff --git a/openslides/participant/views.py b/openslides/participant/views.py index ed6609859..0c6875eb5 100644 --- a/openslides/participant/views.py +++ b/openslides/participant/views.py @@ -32,6 +32,7 @@ from participant.api import gen_username from participant.forms import UserNewForm, UserEditForm, ProfileForm, UsersettingsForm, UserImportForm, GroupForm, AdminPasswordChangeForm from utils.utils import template, permission_required, gen_confirm_form from utils.pdf import print_userlist, print_passwords +from system.api import config_get from django.db.models import Avg, Max, Min, Count @@ -195,7 +196,10 @@ def user_set_active(request, user_id): @permission_required('participant.can_manage_participant') @template('participant/group_overview.html') def get_group_overview(request): - groups = Group.objects.all() + if config_get('system_enable_anonymous', False): + groups = Group.objects.all() + else: + groups = Group.objects.exclude(name='Anonymous') return { 'groups': groups, } @@ -214,7 +218,29 @@ def group_edit(request, group_id=None): if request.method == 'POST': form = GroupForm(request.POST, instance=group) if form.is_valid(): + group_name = form.cleaned_data['name'].lower() + + try: + anonymous_group = Group.objects.get(name='Anonymous') + except Group.DoesNotExist: + anonymous_group = None + + # special handling for anonymous auth + if group is None and group_name.strip().lower() == 'anonymous': + # don't allow to create this group + messages.error(request, _('Group name "%s" is reserved for internal use.') % group_name) + return { + 'form' : form, + 'group': group + } + group = form.save() + if anonymous_group is not None and \ + anonymous_group.id == group.id: + # prevent name changes - XXX: I'm sure this could be done as *one* group.save() + group.name = 'Anonymous' + group.save() + if group_id is None: messages.success(request, _('New group was successfully created.')) else: @@ -273,6 +299,9 @@ def user_import(request): return redirect(reverse('user_overview')) except Profile.DoesNotExist: pass + except AttributeError: + # AnonymousUser + pass if request.method == 'POST': form = UserImportForm(request.POST, request.FILES) diff --git a/openslides/system/auth/AnonymousAuth.py b/openslides/system/auth/AnonymousAuth.py new file mode 100644 index 000000000..63b7575c6 --- /dev/null +++ b/openslides/system/auth/AnonymousAuth.py @@ -0,0 +1,82 @@ +from django.contrib.auth.models import Permission +from openslides.system.api import config_get + +class AnonymousAuth(object): + """ + Authenticates the AnonymousUser against the Permission-Group 'Anonymous'. + No rights are granted unless the group is defined and contains them. + """ + supports_anonymous_user = True + supports_inactive_user = True + supports_object_permissions = False + + def authenticate(self, username=None, password=None): + """ + Authenticate a user based in username / password. + + - always return None as anonymous can't login.. + """ + return None + + def get_group_permissions(self, user_obj, obj=None): + """ + Return the permissions a user is graneted by his group membership(s). + + - try to return the permissions for the 'Anonymous' group + """ + if not user_obj.is_anonymous() or obj is not None or \ + not config_get('system_enable_anonymous', False): + return set() + + perms = Permission.objects.filter(group__name='Anonymous') + if perms is None: + return set() + perms = perms.values_list('content_type__app_label', 'codename').order_by() + return set([u'%s.%s' % (ct, name) for ct, name in perms]) + + def get_all_permissions(self, user_obj, obj=None): + """ + Return all permissions a user is granted including goup permissions. + + - for anonymous it's identical to get_group_permissions + """ + return self.get_group_permissions(user_obj, obj) + + def has_perm(self, user_obj, perm, obj=None): + """ + Check if the user as a specific permission + """ + if not user_obj.is_anonymous() or obj is not None or \ + not config_get('system_enable_anonymous', False): + return False + + return (perm in self.get_all_permissions(user_obj)) + + def has_module_perm(self, user_obj, app_label): + """ + Check if the user has permissions on the module app_label + """ + if not user_obj.is_anonymous() or \ + not config_get('system_enable_anonymous', False): + return False + + for perm in self.get_all_permissions(user_obj): + if perm[:perm.index('.')] == app_label: + return True + return False + + def get_user(self, user_id): + """ + Return the User object for user_id + + - for anonymous it's always None + """ + return None + +def anonymous_context_additions(RequestContext): + """ + Add a variable to the request context that will indicate + if anonymous login is possible at all. + """ + return { 'os_enable_anonymous_login' : config_get('system_enable_anonymous', False) } + diff --git a/openslides/system/auth/__init__.py b/openslides/system/auth/__init__.py new file mode 100644 index 000000000..d08d31ba1 --- /dev/null +++ b/openslides/system/auth/__init__.py @@ -0,0 +1 @@ +from AnonymousAuth import * diff --git a/openslides/system/forms.py b/openslides/system/forms.py index 13812fb7a..5281453d1 100644 --- a/openslides/system/forms.py +++ b/openslides/system/forms.py @@ -21,6 +21,7 @@ class SystemConfigForm(Form): #user_registration = BooleanField(label=_("User registration"), required=False) system_url = CharField(widget=TextInput(), required=False, label=_("System URL")) system_welcometext = CharField(widget=Textarea(), required=False, label=_("Welcome text (for password PDF)")) + system_enable_anonymous = BooleanField(required=False, label=_("Access for anonymous / guest users"), help_text=_("Allow access for guest users")) class EventConfigForm(Form): error_css_class = 'error' @@ -58,4 +59,4 @@ class AssignmentConfigForm(Form): assignment_pdf_ballot_papers_selection = ChoiceField(widget=Select(), required=False, label=_("Number of ballot papers (selection)"), choices=[("1", _("Number of all delegates")),("2", _("Number of all participants")),("0", _("Use the following custum number"))]) assignment_pdf_ballot_papers_number = IntegerField(widget=TextInput(attrs={'class':'small-input'}), required=False, min_value=1, label=_("Custom number of ballot papers")) - \ No newline at end of file + diff --git a/openslides/system/views.py b/openslides/system/views.py index 9239b3dea..b470a2ace 100644 --- a/openslides/system/views.py +++ b/openslides/system/views.py @@ -13,6 +13,7 @@ from django.shortcuts import redirect from django.core.urlresolvers import reverse from django.contrib import messages +from django.contrib.auth.models import Group, Permission from django.utils.translation import ugettext as _ from utils.utils import template from utils.utils import template, permission_required @@ -27,6 +28,22 @@ def get_system_config(request): if form.is_valid(): config_set('system_url', form.cleaned_data['system_url']) config_set('system_welcometext', form.cleaned_data['system_welcometext']) + if form.cleaned_data['system_enable_anonymous']: + config_set('system_enable_anonymous', True) + # check for Anonymous group and (re)create it as needed + try: + anonymous = Group.objects.get(name='Anonymous') + except Group.DoesNotExist: + default_perms = [u'can_see_agenda', u'can_see_projector', u'can_see_application'] + anonymous = Group() + anonymous.name = 'Anonymous' + anonymous.save() + anonymous.permissions = Permission.objects.filter(codename__in=default_perms) + anonymous.save() + messages.success(request, _('Anonymous access enabled. Please modify the "Anonymous" group to fit your required permissions.')) + else: + # use '' - False will evaluate to uniced(False) => True.. + config_set('system_enable_anonymous', '') messages.success(request, _('System settings successfully saved.')) else: messages.error(request, _('Please check the form for errors.')) @@ -35,6 +52,7 @@ def get_system_config(request): form = SystemConfigForm(initial={ 'system_url': config_get('system_url'), 'system_welcometext': config_get('system_welcometext'), + 'system_enable_anonymous': config_get('system_enable_anonymous'), }) return { 'form': form,