mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Upon access of an app URL, it may redirect to another URL that is configured in app settings. This new URL could only be accessed on IPv4 or IPv6 only. When curl is invoked with the IP address version of a different kind, the access fails. In such cases, tell the diagnostics methods not the restrict to a particular address type. Tests: - Unit tests pass. - All of transmission's diagnostics tests pass. The URL tests show that they have been performed on a particular IP address type. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""
|
|
App component for other apps to use Apache configuration functionality.
|
|
"""
|
|
|
|
import re
|
|
import subprocess
|
|
|
|
from django.utils.text import format_lazy
|
|
from django.utils.translation import gettext_lazy
|
|
|
|
from plinth import action_utils, actions, app
|
|
|
|
|
|
class Webserver(app.LeaderComponent):
|
|
"""Component to enable/disable Apache configuration."""
|
|
|
|
def __init__(self, component_id, web_name, kind='config', urls=None,
|
|
expect_redirects=False):
|
|
"""Initialize the web server component.
|
|
|
|
component_id should be a unique ID across all components of an app and
|
|
across all components.
|
|
|
|
web_name is the primary part of the configuration file path which must
|
|
be enabled/disabled by this component.
|
|
|
|
kind is the type of Apache configuration being enabled/disabled. This
|
|
must be 'config' for a configuration in /etc/apache/conf-available/,
|
|
'module' for configuration in /etc/apache2/mods-available/, 'site' for
|
|
configuration in /etc/apache2/sites-available/.
|
|
|
|
urls is a list of URLs over which a HTTP services will be available due
|
|
to this component. This list is only used for running diagnostics.
|
|
|
|
"""
|
|
super().__init__(component_id)
|
|
|
|
self.web_name = web_name
|
|
self.kind = kind
|
|
self.urls = urls or []
|
|
self.expect_redirects = expect_redirects
|
|
|
|
def is_enabled(self):
|
|
"""Return whether the Apache configuration is enabled."""
|
|
return action_utils.webserver_is_enabled(self.web_name, kind=self.kind)
|
|
|
|
def enable(self):
|
|
"""Enable the Apache configuration."""
|
|
actions.superuser_run(
|
|
'apache', ['enable', '--name', self.web_name, '--kind', self.kind])
|
|
|
|
def disable(self):
|
|
"""Disable the Apache configuration."""
|
|
actions.superuser_run(
|
|
'apache',
|
|
['disable', '--name', self.web_name, '--kind', self.kind])
|
|
|
|
def diagnose(self):
|
|
"""Check if the web path is accessible by clients.
|
|
|
|
See :py:meth:`plinth.app.Component.diagnose`.
|
|
|
|
"""
|
|
results = []
|
|
for url in self.urls:
|
|
if '{host}' in url:
|
|
results.extend(
|
|
diagnose_url_on_all(
|
|
url, check_certificate=False,
|
|
expect_redirects=self.expect_redirects))
|
|
else:
|
|
results.append(diagnose_url(url, check_certificate=False))
|
|
|
|
return results
|
|
|
|
|
|
class Uwsgi(app.LeaderComponent):
|
|
"""Component to enable/disable uWSGI configuration."""
|
|
|
|
def __init__(self, component_id, uwsgi_name):
|
|
"""Initialize the uWSGI component.
|
|
|
|
component_id should be a unique ID across all components of an app and
|
|
across all components.
|
|
|
|
uwsgi_name is the primary part of the configuration file path which
|
|
must be enabled/disabled by this component.
|
|
|
|
"""
|
|
super().__init__(component_id)
|
|
|
|
self.uwsgi_name = uwsgi_name
|
|
|
|
def is_enabled(self):
|
|
"""Return whether the uWSGI configuration is enabled."""
|
|
return action_utils.uwsgi_is_enabled(self.uwsgi_name) \
|
|
and action_utils.service_is_enabled('uwsgi')
|
|
|
|
def enable(self):
|
|
"""Enable the uWSGI configuration."""
|
|
actions.superuser_run('apache',
|
|
['uwsgi-enable', '--name', self.uwsgi_name])
|
|
|
|
def disable(self):
|
|
"""Disable the uWSGI configuration."""
|
|
actions.superuser_run('apache',
|
|
['uwsgi-disable', '--name', self.uwsgi_name])
|
|
|
|
def is_running(self):
|
|
"""Return whether the uWSGI daemon is running with configuration."""
|
|
return action_utils.uwsgi_is_enabled(self.uwsgi_name) \
|
|
and action_utils.service_is_running('uwsgi')
|
|
|
|
|
|
def diagnose_url(url, kind=None, env=None, check_certificate=True,
|
|
extra_options=None, wrapper=None, expected_output=None):
|
|
"""Run a diagnostic on whether a URL is accessible.
|
|
|
|
Kind can be '4' for IPv4 or '6' for IPv6.
|
|
"""
|
|
result = check_url(url, kind, env, check_certificate, extra_options,
|
|
wrapper, expected_output)
|
|
|
|
if kind:
|
|
template = gettext_lazy('Access URL {url} on tcp{kind}')
|
|
testname = format_lazy(template, url=url, kind=kind)
|
|
else:
|
|
template = gettext_lazy('Access URL {url}')
|
|
testname = format_lazy(template, url=url)
|
|
|
|
return [testname, result]
|
|
|
|
|
|
def diagnose_url_on_all(url, expect_redirects=False, **kwargs):
|
|
"""Run a diagnostic on whether a URL is accessible."""
|
|
results = []
|
|
for address in action_utils.get_addresses():
|
|
current_url = url.format(host=address['url_address'])
|
|
diagnose_kwargs = dict(kwargs)
|
|
if not expect_redirects:
|
|
diagnose_kwargs.setdefault('kind', address['kind'])
|
|
|
|
results.append(diagnose_url(current_url, **diagnose_kwargs))
|
|
|
|
return results
|
|
|
|
|
|
def check_url(url, kind=None, env=None, check_certificate=True,
|
|
extra_options=None, wrapper=None, expected_output=None):
|
|
"""Check whether a URL is accessible."""
|
|
command = ['curl', '--location', '-f', '-w', '%{response_code}']
|
|
|
|
if kind == '6':
|
|
# extract zone index
|
|
match = re.match(r'(.*://)\[(.*)%(?P<zone>.*)\](.*)', url)
|
|
if match:
|
|
command = command + ['--interface', match.group('zone')]
|
|
url = '{0}[{1}]{2}'.format(*match.group(1, 2, 4))
|
|
|
|
command.append(url)
|
|
|
|
if wrapper:
|
|
command.insert(0, wrapper)
|
|
|
|
if not check_certificate:
|
|
command.append('-k')
|
|
|
|
if extra_options:
|
|
command.extend(extra_options)
|
|
|
|
if kind:
|
|
command.append({'4': '-4', '6': '-6'}[kind])
|
|
|
|
try:
|
|
process = subprocess.run(command, env=env, check=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
result = 'passed'
|
|
if expected_output and expected_output not in process.stdout.decode():
|
|
result = 'failed'
|
|
except subprocess.CalledProcessError as exception:
|
|
result = 'failed'
|
|
# Authorization failed is a success
|
|
if exception.stdout.decode().strip() in ('401', '405'):
|
|
result = 'passed'
|
|
except FileNotFoundError:
|
|
result = 'error'
|
|
|
|
return result
|