mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
nextcloud: Populated and maintain a list of trusted domains
- Rename 'domain' to 'override domain'. See below. - If override domain is not set and trusted domains list is properly maintained, then Nextcloud can be accessed using a domain from list of trusted domains. This is ideal as accessing from .onion domain and a regular domain will simultaneously without forcing a single domain. However, non-localhost IP addresses will not work with this approach and 'override domain' will be needed. - When override domain is set to an IP address or a domain, then that domain will forced. Also hostname are accepted on a request but after the first page load, access will be forcefully redirected to the configured override domain. Multiple domains, even trusted domains, will thus not work. This option should be used as a last resort. - All un-setting the override domain to an empty value so that trusted domains can be used again. - Update diagnostic checks to ensure that above logic is used with checking domains. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
1272be0ad6
commit
fb0dd323ff
@ -1,17 +1,22 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""FreedomBox app to configure Nextcloud."""
|
||||
|
||||
import contextlib
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg, frontpage, menu
|
||||
from plinth.config import DropinConfigs
|
||||
from plinth.daemon import Daemon, SharedDaemon
|
||||
from plinth.modules.apache.components import Webserver, diagnose_url
|
||||
from plinth.modules.apache.components import (Webserver, diagnose_url,
|
||||
diagnose_url_on_all)
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import (Firewall,
|
||||
FirewallLocalProtection)
|
||||
from plinth.modules.names.components import DomainName
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import domain_added, domain_removed
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest, privileged
|
||||
@ -90,8 +95,7 @@ class NextcloudApp(app_module.App):
|
||||
'firewall-local-protection-nextcloud', ['9000'])
|
||||
self.add(firewall_local_protection)
|
||||
|
||||
webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox',
|
||||
urls=['https://{host}/nextcloud/login'])
|
||||
webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox')
|
||||
self.add(webserver)
|
||||
|
||||
daemon = SharedDaemon('shared-daemon-podman-auto-update',
|
||||
@ -116,6 +120,12 @@ class NextcloudApp(app_module.App):
|
||||
**manifest.backup)
|
||||
self.add(backup_restore)
|
||||
|
||||
@staticmethod
|
||||
def post_init():
|
||||
"""Perform post initialization operations."""
|
||||
domain_added.connect(_on_domain_added)
|
||||
domain_removed.connect(_on_domain_removed)
|
||||
|
||||
def setup(self, old_version):
|
||||
"""Install and configure the app."""
|
||||
super().setup(old_version)
|
||||
@ -133,6 +143,7 @@ class NextcloudApp(app_module.App):
|
||||
# Database needs to be running for successful initialization or
|
||||
# upgrade of Nextcloud database.
|
||||
privileged.setup()
|
||||
_set_trusted_domains()
|
||||
|
||||
if should_disable:
|
||||
self.disable()
|
||||
@ -146,9 +157,33 @@ class NextcloudApp(app_module.App):
|
||||
super().uninstall()
|
||||
|
||||
def diagnose(self):
|
||||
"""Run diagnostics and return the results."""
|
||||
"""Run diagnostics and return the results.
|
||||
|
||||
When an override domain is set, that domain and all other addresses are
|
||||
expected to work. This is because Nextcloud will accept any Host: HTTP
|
||||
header and then override it with the provided domain name. When
|
||||
override domain is not set, only the configured trusted domains along
|
||||
with local IP addresses are allowed. Others are rejected with an error.
|
||||
"""
|
||||
results = super().diagnose()
|
||||
|
||||
kwargs = {'check_certificate': False}
|
||||
url = 'https://{domain}/nextcloud/login'
|
||||
domain = privileged.get_override_domain()
|
||||
if domain:
|
||||
results.append(diagnose_url(url.format(domain=domain), **kwargs))
|
||||
results += diagnose_url_on_all(url.format(domain='{host}'),
|
||||
**kwargs)
|
||||
else:
|
||||
local_addresses = [('localhost', '4'), ('localhost', '6'),
|
||||
('127.0.0.1', '4'), ('[::1]', '6')]
|
||||
for address, kind in local_addresses:
|
||||
results.append(
|
||||
diagnose_url(url.format(domain=address), kind=kind,
|
||||
**kwargs))
|
||||
|
||||
results.append(diagnose_url('docker.com'))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@ -176,23 +211,51 @@ class NextcloudBackupRestore(BackupRestore):
|
||||
def backup_pre(self, packet):
|
||||
"""Save database contents."""
|
||||
super().backup_pre(packet)
|
||||
self.app.get_component('dropin-configs-nextcloud').enable()
|
||||
mysql = self.app.get_component('shared-daemon-nextcloud-mysql')
|
||||
redis = self.app.get_component('shared-daemon-nextcloud-redis')
|
||||
container = self.app.get_component('daemon-nextcloud')
|
||||
with mysql.ensure_running():
|
||||
with redis.ensure_running():
|
||||
with container.ensure_running():
|
||||
privileged.dump_database()
|
||||
with _ensure_nextcloud_running():
|
||||
privileged.dump_database()
|
||||
|
||||
def restore_post(self, packet):
|
||||
"""Restore database contents."""
|
||||
super().restore_post(packet)
|
||||
self.app.get_component('dropin-configs-nextcloud').enable()
|
||||
mysql = self.app.get_component('shared-daemon-nextcloud-mysql')
|
||||
redis = self.app.get_component('shared-daemon-nextcloud-redis')
|
||||
container = self.app.get_component('daemon-nextcloud')
|
||||
with mysql.ensure_running():
|
||||
with redis.ensure_running():
|
||||
with container.ensure_running():
|
||||
privileged.restore_database()
|
||||
with _ensure_nextcloud_running():
|
||||
privileged.restore_database()
|
||||
|
||||
|
||||
def _on_domain_added(sender, domain_type, name='', description='',
|
||||
services=None, **kwargs):
|
||||
"""Add domain to list of trusted domains."""
|
||||
app = app_module.App.get('nextcloud')
|
||||
if app.needs_setup():
|
||||
return
|
||||
|
||||
_set_trusted_domains()
|
||||
|
||||
|
||||
def _on_domain_removed(sender, domain_type, name='', **kwargs):
|
||||
"""Update the list of trusted domains."""
|
||||
app = app_module.App.get('nextcloud')
|
||||
if app.needs_setup():
|
||||
return
|
||||
|
||||
_set_trusted_domains()
|
||||
|
||||
|
||||
def _set_trusted_domains():
|
||||
"""Set the list of trusted domains."""
|
||||
all_domains = DomainName.list_names()
|
||||
with _ensure_nextcloud_running():
|
||||
privileged.set_trusted_domains(list(all_domains))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _ensure_nextcloud_running():
|
||||
"""Ensure the nextcloud is running and returns to original state."""
|
||||
app = app_module.App.get('nextcloud')
|
||||
app.get_component('dropin-configs-nextcloud').enable()
|
||||
mysql = app.get_component('shared-daemon-nextcloud-mysql')
|
||||
redis = app.get_component('shared-daemon-nextcloud-redis')
|
||||
container = app.get_component('daemon-nextcloud')
|
||||
with mysql.ensure_running():
|
||||
with redis.ensure_running():
|
||||
with container.ensure_running():
|
||||
yield
|
||||
|
||||
@ -22,9 +22,12 @@ def _get_phone_regions():
|
||||
class NextcloudForm(forms.Form):
|
||||
"""Nextcloud configuration form."""
|
||||
|
||||
domain = forms.CharField(
|
||||
label=_('Domain'), required=False, help_text=_(
|
||||
'Examples: "myfreedombox.example.org" or "example.onion".'))
|
||||
override_domain = forms.CharField(
|
||||
label=_('Override domain'), required=False, help_text=_(
|
||||
'Set to the domain or IP address that Nextcloud should be forced '
|
||||
'to generate URLs with. Should not be needed if a valid domain is '
|
||||
'used to access Nextcloud. Examples: "myfreedombox.example.org" '
|
||||
'or "example.onion".'))
|
||||
|
||||
admin_password = forms.CharField(
|
||||
label=_('Administrator password'), help_text=_(
|
||||
|
||||
@ -105,8 +105,8 @@ def disable():
|
||||
|
||||
|
||||
@privileged
|
||||
def get_domain():
|
||||
"""Return domain name set in Nextcloud."""
|
||||
def get_override_domain():
|
||||
"""Return the domain name that Nextcloud is configured to override with."""
|
||||
try:
|
||||
domain = _run_occ('config:system:get', 'overwritehost',
|
||||
capture_output=True)
|
||||
@ -116,22 +116,33 @@ def get_domain():
|
||||
|
||||
|
||||
@privileged
|
||||
def set_domain(domain_name: str):
|
||||
"""Set Nextcloud domain name."""
|
||||
def set_override_domain(domain_name: str):
|
||||
"""Set the domain name that Nextcloud will use to override all domains."""
|
||||
protocol = 'https'
|
||||
if domain_name.endswith('.onion'):
|
||||
protocol = 'http'
|
||||
|
||||
if domain_name:
|
||||
_run_occ('config:system:set', 'overwritehost', '--value', domain_name)
|
||||
|
||||
_run_occ('config:system:set', 'overwriteprotocol', '--value', protocol)
|
||||
_run_occ('config:system:set', 'overwrite.cli.url', '--value',
|
||||
f'{protocol}://{domain_name}/nextcloud')
|
||||
else:
|
||||
_run_occ('config:system:delete', 'overwritehost')
|
||||
_run_occ('config:system:delete', 'overwriteprotocol')
|
||||
_run_occ('config:system:delete', 'overwrite.cli.url')
|
||||
|
||||
_run_occ('config:system:set', 'overwriteprotocol', '--value', protocol)
|
||||
# Restart to apply changes immediately
|
||||
action_utils.service_restart('nextcloud-freedombox')
|
||||
|
||||
# Restart to apply changes immediately
|
||||
action_utils.service_restart('nextcloud-freedombox')
|
||||
|
||||
@privileged
|
||||
def set_trusted_domains(domains: list[str]):
|
||||
"""Set the list of trusted domains."""
|
||||
_run_occ('config:system:delete', 'trusted_domains')
|
||||
for index, domain in enumerate(domains):
|
||||
_run_occ('config:system:set', 'trusted_domains', str(index), '--value',
|
||||
domain)
|
||||
|
||||
|
||||
@privileged
|
||||
|
||||
@ -24,7 +24,7 @@ class NextcloudAppView(AppView):
|
||||
"""Return the values to fill in the form."""
|
||||
initial = super().get_initial()
|
||||
initial.update({
|
||||
'domain': privileged.get_domain(),
|
||||
'override_domain': privileged.get_override_domain(),
|
||||
'default_phone_region': privileged.get_default_phone_region() or ''
|
||||
})
|
||||
return initial
|
||||
@ -39,8 +39,8 @@ class NextcloudAppView(AppView):
|
||||
def _value_changed(key):
|
||||
return old_config.get(key) != new_config.get(key)
|
||||
|
||||
if _value_changed('domain'):
|
||||
privileged.set_domain(new_config['domain'])
|
||||
if _value_changed('override_domain'):
|
||||
privileged.set_override_domain(new_config['override_domain'])
|
||||
is_changed = True
|
||||
|
||||
if new_config['admin_password']:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user