diff --git a/functional_tests/features/openvpn.feature b/functional_tests/features/openvpn.feature new file mode 100644 index 000000000..140be4fd8 --- /dev/null +++ b/functional_tests/features/openvpn.feature @@ -0,0 +1,46 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# + +@apps @openvpn @backups +Feature: OpenVPN - Virtual Private Network + Setup and configure OpenVPN + +Background: + Given I'm a logged in user + Given the openvpn application is installed + Given the openvpn application is setup + +Scenario: Enable openvpn application + Given the openvpn application is disabled + When I enable the openvpn application + Then the openvpn service should be running + +Scenario: Download openvpn profile + Given the openvpn application is enabled + Then the openvpn profile should be downloadable + +Scenario: Backup and restore openvpn + Given the openvpn application is enabled + And I download openvpn profile + When I create a backup of the openvpn app data + And I restore the openvpn app data backup + Then the openvpn profile downloaded should be same as before + +Scenario: Disable openvpn application + Given the openvpn application is enabled + When I disable the openvpn application + Then the openvpn service should not be running diff --git a/functional_tests/step_definitions/application.py b/functional_tests/step_definitions/application.py index 7fdf7f68a..e1e517ff9 100644 --- a/functional_tests/step_definitions/application.py +++ b/functional_tests/step_definitions/application.py @@ -25,7 +25,7 @@ from support import application @given(parsers.parse('the {app_name:w} application is installed')) def application_is_installed(browser, app_name): application.install(browser, app_name) - assert(application.is_installed(browser, app_name)) + assert (application.is_installed(browser, app_name)) @given(parsers.parse('the {app_name:w} application is enabled')) @@ -331,14 +331,19 @@ def tor_assert_download_software_over_tor(browser, enabled): application.tor_assert_feature_enabled(browser, 'software', enabled) -@then(parsers.parse('{domain:S} should be a tahoe {introducer_type:w} introducer')) +@then( + parsers.parse( + '{domain:S} should be a tahoe {introducer_type:w} introducer')) def tahoe_assert_introducer(browser, domain, introducer_type): assert application.tahoe_get_introducer(browser, domain, introducer_type) -@then(parsers.parse('{domain:S} should not be a tahoe {introducer_type:w} introducer')) +@then( + parsers.parse( + '{domain:S} should not be a tahoe {introducer_type:w} introducer')) def tahoe_assert_not_introducer(browser, domain, introducer_type): - assert not application.tahoe_get_introducer(browser, domain, introducer_type) + assert not application.tahoe_get_introducer(browser, domain, + introducer_type) @given(parsers.parse('{domain:S} is not a tahoe introducer')) @@ -397,3 +402,24 @@ def radicale_check_owner_write(browser): @then('the access rights should be "any user can view or make changes"') def radicale_check_authenticated(browser): assert application.radicale_get_access_rights(browser) == 'authenticated' + + +@given(parsers.parse('the openvpn application is setup')) +def openvpn_setup(browser): + application.openvpn_setup(browser) + + +@given('I download openvpn profile') +def openvpn_download_profile(browser): + return application.openvpn_download_profile(browser) + + +@then('the openvpn profile should be downloadable') +def openvpn_profile_downloadable(browser): + application.openvpn_download_profile(browser) + + +@then('the openvpn profile downloaded should be same as before') +def openvpn_profile_download_compare(browser, openvpn_download_profile): + new_profile = application.openvpn_download_profile(browser) + assert openvpn_download_profile == new_profile diff --git a/functional_tests/support/application.py b/functional_tests/support/application.py index 03e556321..72bf7a34a 100644 --- a/functional_tests/support/application.py +++ b/functional_tests/support/application.py @@ -17,6 +17,7 @@ from time import sleep +import requests import splinter from support import config, interface, site @@ -32,6 +33,7 @@ app_module = { app_checkbox_id = { 'tor': 'id_tor-enabled', + 'openvpn': 'id_openvpn-enabled', } default_url = config['DEFAULT']['url'] @@ -115,6 +117,16 @@ def wait_for_config_update(browser, app_name): sleep(0.1) +def _download_file(browser, url): + """Return file contents after downloading a URL.""" + cookies = browser.cookies.all() + response = requests.get(url, cookies=cookies, verify=False) + if response.status_code != 200: + raise Exception('URL download failed') + + return response.content + + def select_domain_name(browser, app_name, domain_name): browser.visit('{}/plinth/apps/{}/setup/'.format(default_url, app_name)) drop_down = browser.find_by_id('id_domain_name') @@ -439,3 +451,21 @@ def radicale_set_access_rights(browser, access_rights_type): interface.nav_to_module(browser, 'radicale') browser.choose('access_rights', access_rights_type) interface.submit(browser, form_class='form-configuration') + + +def openvpn_setup(browser): + """Setup the OpenVPN application after installation.""" + interface.nav_to_module(browser, 'openvpn') + setup_form = browser.find_by_css('.form-setup') + if not setup_form: + return + + submit(browser, form_class='form-setup') + wait_for_config_update(browser, 'openvpn') + + +def openvpn_download_profile(browser): + """Download the current user's profile into a file and return path.""" + interface.nav_to_module(browser, 'openvpn') + url = browser.find_by_css('.form-profile')['action'] + return _download_file(browser, url) diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index dce31e9e3..ebfae2e92 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -26,6 +26,8 @@ from plinth import action_utils, actions, cfg, frontpage from plinth.menu import main_menu from plinth.utils import format_lazy +from .manifest import backup + version = 2 service = None diff --git a/plinth/modules/openvpn/manifest.py b/plinth/modules/openvpn/manifest.py new file mode 100644 index 000000000..05b241959 --- /dev/null +++ b/plinth/modules/openvpn/manifest.py @@ -0,0 +1,28 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# +""" +Application manifest for OpenVPN. +""" + +from plinth.modules.backups.api import validate as validate_backup + + +backup = validate_backup({ + 'secrets': { + 'directories': ['/etc/openvpn/'] + } +}) diff --git a/plinth/modules/openvpn/templates/openvpn.html b/plinth/modules/openvpn/templates/openvpn.html index 6ac6d0c90..6c24c9a4a 100644 --- a/plinth/modules/openvpn/templates/openvpn.html +++ b/plinth/modules/openvpn/templates/openvpn.html @@ -58,7 +58,8 @@ {% endblocktrans %}

-
+ {% csrf_token %} - + {% csrf_token %} {% trans "Configuration" %} - + {% csrf_token %} {{ form|bootstrap }}