diff --git a/plinth/modules/first_boot/__init__.py b/plinth/modules/first_boot/__init__.py index 508feb26b..77e6948df 100644 --- a/plinth/modules/first_boot/__init__.py +++ b/plinth/modules/first_boot/__init__.py @@ -22,12 +22,16 @@ Plinth module for first boot wizard version = 1 is_essential = True -first_boot_steps = [{'id': 'firstboot_state0', - 'url': 'first_boot:state0', - 'order': 0 - }, - {'id': 'firstboot_state10', - 'url': 'first_boot:state10', - 'order': 10 - } - ] + +first_boot_steps = [ + { + 'id': 'firstboot_state0', + 'url': 'first_boot:state0', + 'order': 0 + }, + { + 'id': 'firstboot_state10', + 'url': 'first_boot:state10', + 'order': 10 + } +] diff --git a/plinth/modules/first_boot/forms.py b/plinth/modules/first_boot/forms.py deleted file mode 100644 index 457f866a9..000000000 --- a/plinth/modules/first_boot/forms.py +++ /dev/null @@ -1,196 +0,0 @@ -# -# This file is part of Plinth. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# - -""" -Forms for first boot module. -""" - -import json -import logging -import requests - -from django import forms -from django.contrib import auth -from django.contrib import messages -from django.core.exceptions import ValidationError -from django.utils.translation import ugettext as _, ugettext_lazy - -from plinth import actions -from plinth import cfg -from plinth.errors import ActionError, DomainRegistrationError -from plinth.modules.pagekite.utils import PREDEFINED_SERVICES, run -from plinth.modules.security import set_restricted_access -from plinth.modules.users.forms import GROUP_CHOICES, ValidNewUsernameCheckMixin -from plinth.utils import format_lazy - -logger = logging.getLogger(__name__) - - -class State1Form(ValidNewUsernameCheckMixin, auth.forms.UserCreationForm): - """Firstboot state 1: create a new user.""" - - def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request') - super().__init__(*args, **kwargs) - - def save(self, commit=True): - """Create and log the user in.""" - user = super().save(commit=commit) - if commit: - try: - actions.superuser_run( - 'ldap', - ['create-user', user.get_username()], - input=self.cleaned_data['password1'].encode()) - except ActionError: - messages.error(self.request, - _('Creating LDAP user failed.')) - - try: - actions.superuser_run( - 'ldap', - ['add-user-to-group', user.get_username(), 'admin']) - except ActionError: - messages.error(self.request, - _('Failed to add new user to admin group.')) - - # Create initial Django groups - for group_choice in GROUP_CHOICES: - auth.models.Group.objects.get_or_create(name=group_choice[0]) - - admin_group = auth.models.Group.objects.get(name='admin') - admin_group.user_set.add(user) - - self.login_user(self.cleaned_data['username'], - self.cleaned_data['password1']) - - # Restrict console login to users in admin or sudo group - try: - set_restricted_access(True) - message = _('Console login access restricted to users in ' - '"admin" group. This can be configured in ' - 'security settings.') - messages.success(self.request, message) - except Exception: - messages.error(self.request, - _('Failed to restrict console access.')) - - return user - - def login_user(self, username, password): - """Try to login the user with the credentials provided""" - try: - user = auth.authenticate(username=username, password=password) - auth.login(self.request, user) - except Exception: - pass - else: - message = _('User account created, you are now logged in') - messages.success(self.request, message) - - -class State5Form(forms.Form): - """Set up freedombox.me pagekite subdomain""" - DOMAIN_APPENDIX = '.freedombox.me' - # Webservice url for domain validation and registration - service_url = 'http://freedombox.me/cgi-bin/freedomkite.pl' - - code_help_text = format_lazy( - ugettext_lazy('The voucher you received with your {box_name} Danube ' - 'Edition'), box_name=ugettext_lazy(cfg.box_name)) - - code = forms.CharField(help_text=code_help_text) - - domain = forms.SlugField(label=_('Subdomain'), - widget=SubdomainWidget(domain=DOMAIN_APPENDIX), - help_text=_('The subdomain you want to register')) - - def clean_domain(self): - """Append the domain to the users' subdomain""" - return self.cleaned_data['domain'] + self.DOMAIN_APPENDIX - - def clean(self): - """Validate user input (subdomain and code)""" - cleaned_data = super().clean() - - # If the subdomain is wrong, don't look if the domain is - # available - if self.errors: - return cleaned_data - - self.domain_already_registered = False - code = cleaned_data.get('code') - domain = cleaned_data.get('domain') - - response = requests.get(self.service_url, params={'code': code}).json() - - # 1. Code is invalid: {} - if 'domain' not in response: - raise ValidationError(_('This code is not valid'), code='invalid') - # 2. Code is valid, domain registered: {'domain': 'xx.freedombox.me'} - elif response['domain']: - if response['domain'] == domain: - self.domain_already_registered = True - else: - message = _('This code is bound to the domain {domain}.') \ - .format(domain=response['domain']) - raise ValidationError(message, code='invalid') - # 3. Code is valid, no domain registered: {'domain': None} - elif response['domain'] is None: - # Make sure that the desired domain is available - data = {'domain': domain} - domain_response = requests.get(self.service_url, params=data) - registered_domain = domain_response.json()['domain'] - if registered_domain is not None: - message = _('The requested domain is already registered.') - raise ValidationError(message, code='invalid') - - return cleaned_data - - def register_domain(self): - """Register a domain (only if it's not already registered)""" - if self.domain_already_registered: - return - - data = {'domain': self.cleaned_data['domain'], - 'code': self.cleaned_data['code']} - response = requests.post(self.service_url, data) - if not response.ok: - message = _('Domain registration failed: {response}.').format( - response=response.text) - logger.error(message) - raise DomainRegistrationError(message) - - def setup_pagekite(self): - """Configure and enable PageKite service.""" - # Set kite name and secret - run(['set-kite', '--kite-name', self.cleaned_data['domain']], - input=self.cleaned_data['code'].encode()) - - # Set frontend - run(['set-frontend', '%s:80' % self.cleaned_data['domain']]) - - # Enable PageKite HTTP + HTTPS service - for service_name in ['http', 'https']: - service = PREDEFINED_SERVICES[service_name]['params'] - try: - run(['add-service', '--service', json.dumps(service)]) - except ActionError as err: - if 'already exists' not in str(err): - raise - - run(['start-and-enable']) diff --git a/plinth/modules/first_boot/middleware.py b/plinth/modules/first_boot/middleware.py index 99b25d9dd..d56a9579a 100644 --- a/plinth/modules/first_boot/middleware.py +++ b/plinth/modules/first_boot/middleware.py @@ -43,8 +43,10 @@ class FirstBootMiddleware(object): if state == 0 and old_state == 10: state = 1 kvstore.set('setup_state', 1) + user_requests_firstboot = is_firstboot(request.path) - user_requests_login = request.path.startswith(reverse(settings.LOGIN_URL)) + user_requests_login = request.path.startswith( + reverse(settings.LOGIN_URL)) help_index_url = reverse('help:index') user_requests_help = request.path.startswith(help_index_url) if not user_requests_login and not user_requests_help: @@ -65,6 +67,7 @@ def is_firstboot(path): for step in steps: if reverse(step.get('url')) == path: return True + return False @@ -76,6 +79,7 @@ def get_firstboot_steps(): if getattr(module_object, 'first_boot_steps', None): for step in module_object.first_boot_steps: steps.append(step) + steps = sorted(steps, key=itemgetter('order')) return steps @@ -85,6 +89,7 @@ def next_step(): global firstboot_steps if len(firstboot_steps) == 0: firstboot_steps = get_firstboot_steps() + for step in firstboot_steps: done = kvstore.get_default(step.get('id'), 0) if done == 0: @@ -100,11 +105,13 @@ def mark_step_done(id): global firstboot_steps if len(firstboot_steps) == 0: firstboot_steps = get_firstboot_steps() + setup_done = True for step in firstboot_steps: done = kvstore.get_default(step.get('id'), 0) if done == 0: setup_done = False break + if setup_done: kvstore.set('setup_state', 1) diff --git a/plinth/modules/first_boot/views.py b/plinth/modules/first_boot/views.py index 0d9299350..37546127a 100644 --- a/plinth/modules/first_boot/views.py +++ b/plinth/modules/first_boot/views.py @@ -18,7 +18,7 @@ from django.contrib.auth.models import User from django.shortcuts import render from django.utils.translation import ugettext as _ -from django.views.generic import CreateView, FormView, TemplateView +from django.views.generic import TemplateView from plinth import network from .middleware import mark_step_done, next_step @@ -29,7 +29,7 @@ class State0View(TemplateView): template_name = 'firstboot_state0.html' def get_context_data(self, **kwargs): - """Returns the context data""" + """Returns the context data for the template.""" context = super(State0View, self).get_context_data(**kwargs) mark_step_done('firstboot_state0') context['next_url'] = next_step() diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index 048b0e5d8..719c00c5e 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -31,11 +31,13 @@ depends = ['system', 'names'] managed_packages = ['pagekite'] -first_boot_steps = [{'id': 'pagekite_firstboot', - 'url': 'pagekite:firstboot', - 'order': 5, - }, - ] +first_boot_steps = [ + { + 'id': 'pagekite_firstboot', + 'url': 'pagekite:firstboot', + 'order': 5, + }, +] title = _('Public Visibility (PageKite)') diff --git a/plinth/modules/pagekite/forms.py b/plinth/modules/pagekite/forms.py index 81ef9ae47..b41266bac 100644 --- a/plinth/modules/pagekite/forms.py +++ b/plinth/modules/pagekite/forms.py @@ -25,13 +25,12 @@ from django.utils.translation import ugettext as _, ugettext_lazy import json import logging +from . import utils from plinth import cfg from plinth.errors import ActionError, DomainRegistrationError from plinth.modules.pagekite.utils import PREDEFINED_SERVICES, run from plinth.utils import format_lazy -from . import utils - LOGGER = logging.getLogger(__name__) @@ -71,9 +70,9 @@ class ConfigurationForm(forms.Form): server_domain = forms.CharField( label=ugettext_lazy('Server domain'), required=False, - help_text= \ - ugettext_lazy('Select your pagekite server. Set "pagekite.net" to use ' - 'the default pagekite.net server.'), + help_text=ugettext_lazy( + 'Select your pagekite server. Set "pagekite.net" to use ' + 'the default pagekite.net server.'), widget=forms.TextInput()) server_port = forms.IntegerField( label=ugettext_lazy('Server port'), required=False, @@ -87,9 +86,9 @@ class ConfigurationForm(forms.Form): kite_secret = TrimmedCharField( label=ugettext_lazy('Kite secret'), - help_text= \ - ugettext_lazy('A secret associated with the kite or the default secret ' - 'for your account if no secret is set on the kite.')) + help_text=ugettext_lazy( + 'A secret associated with the kite or the default secret ' + 'for your account if no secret is set on the kite.')) def save(self, request): """Save the form on submission after validation.""" @@ -101,14 +100,14 @@ class ConfigurationForm(forms.Form): config_changed = False if old['kite_name'] != new['kite_name'] or \ - old['kite_secret'] != new['kite_secret']: + old['kite_secret'] != new['kite_secret']: utils.run(['set-kite', '--kite-name', new['kite_name']], input=new['kite_secret'].encode()) messages.success(request, _('Kite details set')) config_changed = True if old['server_domain'] != new['server_domain'] or \ - old['server_port'] != new['server_port']: + old['server_port'] != new['server_port']: server = "%s:%s" % (new['server_domain'], new['server_port']) utils.run(['set-frontend', server]) messages.success(request, _('Pagekite server set')) @@ -208,6 +207,8 @@ class BaseCustomServiceForm(forms.Form): class DeleteCustomServiceForm(BaseCustomServiceForm): + """Form to remove custom service.""" + def delete(self, request): service = self.convert_formdata_to_service(self.cleaned_data) utils.run(['remove-service', '--service', json.dumps(service)]) diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py index 3746ead85..41621af26 100644 --- a/plinth/modules/pagekite/views.py +++ b/plinth/modules/pagekite/views.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # + from django.contrib import messages from django.http.response import HttpResponseRedirect from django.template.response import TemplateResponse @@ -21,11 +22,11 @@ from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.views.generic import View, TemplateView from django.views.generic.edit import FormView -from plinth.errors import DomainRegistrationError from . import utils from .forms import ConfigurationForm, StandardServiceForm, \ AddCustomServiceForm, DeleteCustomServiceForm, State5Form +from plinth.errors import DomainRegistrationError from plinth.modules import pagekite from plinth.modules.first_boot.middleware import mark_step_done diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index cad4201b5..bec3acd39 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -34,11 +34,15 @@ depends = ['system'] managed_packages = ['ldapscripts', 'ldap-utils', 'libnss-ldapd', 'libpam-ldapd', 'nslcd', 'slapd'] -first_boot_steps = [{'id': 'users_firstboot', - 'url': 'users:firstboot', - 'order': 1 - }, - ] + +first_boot_steps = [ + { + 'id': 'users_firstboot', + 'url': 'users:firstboot', + 'order': 1 + }, +] + title = _('Users and Groups') @@ -80,4 +84,4 @@ def _diagnose_ldap_entry(search_item): pass return [_('Check LDAP entry "{search_item}"') - .format(search_item=search_item), result] + .format(search_item=search_item), result] diff --git a/plinth/modules/users/forms.py b/plinth/modules/users/forms.py index 1ff3e72dd..b6b62bcf9 100644 --- a/plinth/modules/users/forms.py +++ b/plinth/modules/users/forms.py @@ -27,11 +27,9 @@ from django.utils.translation import ugettext as _, ugettext_lazy from plinth import actions from plinth.errors import ActionError - from plinth.modules.security import set_restricted_access # Usernames used by optional services (that might not be installed yet). - RESERVED_USERNAMES = [ 'debian-deluged', 'Debian-minetest', @@ -95,14 +93,14 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): label=ugettext_lazy('Groups'), required=False, widget=forms.CheckboxSelectMultiple, - help_text= \ - ugettext_lazy('Select which services should be available to the new ' - 'user. The user will be able to log in to services that ' - 'support single sign-on through LDAP, if they are in the ' - 'appropriate group.

Users in the admin group ' - 'will be able to log in to all services. They can also ' - 'log in to the system through SSH and have ' - 'administrative privileges (sudo).')) + help_text=ugettext_lazy( + 'Select which services should be available to the new ' + 'user. The user will be able to log in to services that ' + 'support single sign-on through LDAP, if they are in the ' + 'appropriate group.

Users in the admin group ' + 'will be able to log in to all services. They can also ' + 'log in to the system through SSH and have ' + 'administrative privileges (sudo).')) def __init__(self, request, *args, **kwargs): """Initialize the form with extra request argument.""" @@ -132,7 +130,7 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): messages.error( self.request, _('Failed to add new user to {group} group.') - .format(group=group)) + .format(group=group)) group_object, created = Group.objects.get_or_create(name=group) group_object.user_set.add(user) @@ -146,12 +144,12 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm): label=ugettext_lazy('SSH Keys'), required=False, widget=forms.Textarea, - help_text= \ - ugettext_lazy('Setting an SSH public key will allow this user to ' - 'securely log in to the system without using a ' - 'password. You may enter multiple keys, one on each ' - 'line. Blank lines and lines starting with # will be ' - 'ignored.')) + help_text=ugettext_lazy( + 'Setting an SSH public key will allow this user to ' + 'securely log in to the system without using a ' + 'password. You may enter multiple keys, one on each ' + 'line. Blank lines and lines starting with # will be ' + 'ignored.')) class Meta: """Metadata to control automatic form building.""" diff --git a/plinth/modules/users/views.py b/plinth/modules/users/views.py index 5f89824a0..613c4c100 100644 --- a/plinth/modules/users/views.py +++ b/plinth/modules/users/views.py @@ -22,13 +22,13 @@ from django.contrib.messages.views import SuccessMessageMixin from django.urls import reverse, reverse_lazy from django.views.generic.edit import (CreateView, DeleteView, UpdateView, FormView) -from django.views.generic import ListView, CreateView as CV +import django.views.generic from django.utils.translation import ugettext as _, ugettext_lazy -from plinth import cfg - -from .forms import CreateUserForm, UserChangePasswordForm, UserUpdateForm, State1Form +from .forms import CreateUserForm, UserChangePasswordForm, UserUpdateForm, \ + State1Form from plinth import actions +from plinth import cfg from plinth.errors import ActionError from plinth.modules.first_boot.middleware import mark_step_done, next_step @@ -65,7 +65,7 @@ class UserCreate(ContextMixin, SuccessMessageMixin, CreateView): return kwargs -class UserList(ContextMixin, ListView): +class UserList(ContextMixin, django.views.generic.ListView): """View to list users.""" model = User template_name = 'users_list.html' @@ -168,7 +168,7 @@ class UserChangePassword(ContextMixin, SuccessMessageMixin, FormView): return super(UserChangePassword, self).form_valid(form) -class State1View(CV): +class State1View(django.views.generic.CreateView): """Create user account and log the user in.""" template_name = 'firstboot_state1.html' form_class = State1Form @@ -178,6 +178,7 @@ class State1View(CV): """Initialize the view object.""" if not cfg.danube_edition: mark_step_done('pagekite_firstboot') + mark_step_done('users_firstboot') self.success_url = next_step() return super(State1View, self).__init__(*args, **kwargs)