Compare commits

..

No commits in common. "cfc5d3acab0db5b67cb790ad4e03647649a21e51" and "68ccb46ecfcb083629588016502597db96a5e7c8" have entirely different histories.

195 changed files with 10975 additions and 19159 deletions

View File

@ -0,0 +1,16 @@
#!/usr/bin/python3
# -*- mode: python -*-
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Set required permissions for user "plinth" to run plinth in dev setup."""
import pathlib
content = '''
Cmnd_Alias FREEDOMBOX_ACTION_DEV = /usr/share/plinth/actions/actions, /freedombox/actions/actions
Defaults!FREEDOMBOX_ACTION_DEV closefrom_override
plinth ALL=(ALL:ALL) NOPASSWD:SETENV : FREEDOMBOX_ACTION_DEV
fbx ALL=(ALL:ALL) NOPASSWD : ALL
'''
sudoers_file = pathlib.Path('/etc/sudoers.d/01-freedombox-development')
sudoers_file.write_text(content)

View File

@ -416,7 +416,7 @@ for more details.
### Translating literals (contributing translations)
The easiest way to start translating is with your browser, by using
[Weblate](https://hosted.weblate.org/projects/freedombox/freedombox/).
[Weblate](https://hosted.weblate.org/projects/freedombox/plinth/).
Your changes will automatically get pushed to the code repository.
Alternatively, you can directly edit the `.po` file in your language directory

View File

@ -35,7 +35,7 @@ FreedomBox [Manual](https://wiki.debian.org/FreedomBox/Manual/)'s
3. Access FreedomBox UI:
UI should be accessible at http://localhost:8000/freedombox
UI should be accessible at http://localhost:8000/plinth
If you are installing FreedomBox Service (Plinth) for development purposes, see
HACKING.md instead.

View File

@ -21,8 +21,7 @@ DISABLED_APPS_TO_REMOVE := \
tahoe \
mldonkey \
i2p \
ttrss \
sso
ttrss
APP_FILES_TO_REMOVE := $(foreach app,$(DISABLED_APPS_TO_REMOVE),$(ENABLED_APPS_PATH)/$(app))
@ -107,7 +106,6 @@ install:
$(INSTALL) -D -t $(BIN_DIR) bin/plinth
$(INSTALL) -D -t $(LIB_DIR)/freedombox bin/freedombox-privileged
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-cmd
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-change-password
# Static web server files
rm -rf $(STATIC_FILES_DIRECTORY)
@ -231,7 +229,7 @@ provision-dev:
sshpass bash-completion
wait-while-first-setup:
while [ x$$(curl -k https://localhost/freedombox/status/ 2> /dev/null | \
while [ x$$(curl -k https://localhost/plinth/status/ 2> /dev/null | \
json_pp 2> /dev/null | grep 'is_first_setup_running' | \
tr -d '[:space:]' | cut -d':' -f2 ) != 'xfalse' ] ; do \
sleep 1; echo -n .; done

8
Vagrantfile vendored
View File

@ -28,11 +28,17 @@ Vagrant.configure(2) do |config|
SHELL
config.vm.provision "tests", run: "never", type: "shell", path: "plinth/tests/functional/install.sh"
config.vm.post_up_message = "FreedomBox virtual machine is ready
for development. Plinth will be available at https://localhost:4430/freedombox
for development. Plinth will be available at https://localhost:4430/plinth
(with an invalid SSL certificate). To watch logs:
$ vagrant ssh
$ sudo freedombox-logs
"
config.trigger.after [:up, :resume, :reload] do |trigger|
trigger.info = "Set plinth user permissions for development environment"
trigger.run_remote = {
path: ".vagrant-scripts/plinth-user-permissions.py"
}
end
config.vm.boot_timeout=1200
end

View File

@ -1,61 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Utility to change user password in FreedomBox's Django database.
Usage:
$ freedombox-change-password <username>
"""
import argparse
import getpass
import sys
import plinth.web_framework
from plinth.modules.users import privileged
def main():
"""Ask for new password, setup Django and update a user's password."""
try:
plinth.web_framework.init()
except Exception:
_print('Error initializing Django.')
return
parser = argparse.ArgumentParser()
parser.add_argument('username',
help='Username of the account to change password for')
args = parser.parse_args()
username = args.username
password = getpass.getpass('Enter new password: ')
try:
_change_password(username, password)
privileged._set_user_password(username, password)
privileged._set_samba_user(username, password)
_print('Password updated in web interface, LDAP, and samba databases.')
except Exception as exception:
_print('Error setting password:', str(exception))
def _print(*args, **kwargs):
"""Write to stderr."""
print(*args, **kwargs, file=sys.stderr)
def _change_password(username: str, password: str):
"""Update the password in SQLite database file."""
from django.contrib.auth.models import User
try:
user = User.objects.get(username=username)
user.set_password(password)
user.save()
except User.DoesNotExist:
_print('User account does not exist:', username)
raise
if __name__ == '__main__':
main()

View File

@ -12,7 +12,6 @@
# Don't redirect for onion sites as it is not needed and leads to
# unnecessary warning.
RewriteCond %{HTTP_HOST} !^.*\.onion$ [NC]
RewriteCond %{REQUEST_URI} !^/freedombox/apache/discover-idp/$ [NC]
ReWriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</LocationMatch>

View File

@ -39,16 +39,16 @@
</If>
##
## Redirect traffic on home to /freedombox as part of turning the machine
## Redirect traffic on home to /plinth as part of turning the machine
## into FreedomBox server. Plinth then acts as a portal to reach all
## other services.
##
<IfFile !/etc/apache2/conf-enabled/freedombox-apache-homepage.conf>
RedirectMatch "^/$" "/freedombox"
RedirectMatch "^/$" "/plinth"
</IfFile>
##
## On all sites, provide FreedomBox on a default path: /freedombox
## On all sites, provide FreedomBox on a default path: /plinth
##
## Requires the following Apache modules to be enabled:
## mod_headers
@ -56,8 +56,7 @@
## mod_proxy_http
##
<Location /freedombox>
ProxyPass http://127.0.0.1:8000/freedombox
ProxyPreserveHost On
ProxyPass http://127.0.0.1:8000/plinth
## Send the scheme from user's request to enable Plinth to redirect
## URLs, set cookies, set absolute URLs (if any) properly.
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
@ -71,20 +70,7 @@
RequestHeader unset X-Forwarded-For
</Location>
<Location /plinth>
ProxyPass http://127.0.0.1:8000/freedombox
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
RequestHeader unset X-Forwarded-For
</Location>
<Location /.well-known/openid-configuration>
ProxyPass http://127.0.0.1:8000/freedombox/o/.well-known/openid-configuration
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
RequestHeader unset X-Forwarded-For
</Location>
<Location /.well-known/jwks.json>
ProxyPass http://127.0.0.1:8000/freedombox/o/.well-known/jwks.json
ProxyPreserveHost On
ProxyPass http://127.0.0.1:8000/plinth
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
RequestHeader unset X-Forwarded-For
</Location>
@ -96,7 +82,7 @@
<Location ~ ^/favicon\.ico$>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule /favicon\.ico$ "/freedombox/static/theme/img/favicon.ico" [PT]
RewriteRule /favicon\.ico$ "/plinth/static/theme/img/favicon.ico" [PT]
</IfModule>
</Location>

141
debian/changelog vendored
View File

@ -1,144 +1,3 @@
freedombox (26.4) unstable; urgency=medium
[ Joseph Nuthalapati ]
* ui: Dismiss notifications without page reload
[ Sunil Mohan Adapa ]
* ui: Refactor notification delete buttons to avoid repeating code
* ui: Add animation for notification dismissal
* actions, privileged_daemon: Drop some unused global statements
* backups: Avoid some repeated text in form help text
* backups: Fix issue with Javascript in add remote location form
* backups: Show/hide form elements instead of disabling for simplicity
* backups: Tweak appearance of add remote location form
* backups: tests: Simplify functional test using more classes
* backups: Minor refactoring
* backups: Simplify handling of migration to SSH keys
* backups: Create .ssh folder before creating SSH key
* backups: Fix showing proper error for incorrect passphrase
* backups: Create a better comment in the generated SSH key file
* backups: Fix type checking errors
* action_utils: Implement utility to change umask temporarily
* quassel: Explicitly set permissions on the domain configuration file
* letsencrypt: When copying certificate reset the umask reliably
* doc/dev: Set new theme for developer documentation
* action_utils: Fix issue with type checking a generator
* tests: functional: Increase systemd rate limits for starting units
* js: When page load fails during install, show it to user
* tests: functional: Fix reloading error page during install/uninstall
* locale/de: Fix several translations with HTML links (German)
* locale/bg: Fix several translations with HTML links (Bulgarian)
* bin: Add tool to change FreedomBox password in Django database
* ejabberd: Fix setting up certificates for multiple domains
* gitweb: Fix issue with running post init due to missing method
* wireguard: Fix format when showing multiple endpoints of the server
* wireguard: Fix showing default route setting in server edit form
* wireguard: Show status of default route in server information page
* wireguard: Accept/use netmask with IP address for server connection
* README/HACKING: Update weblate project path to /freedombox
* *: Remove some absolute file paths in SVGs
* matrixsynapse: Update apache config to proxy Synapse client API
* cfg: Drop unused config_dir option
* cfg: Drop unused actions_dir option
* Vagrantfile: Drop unnecessary sudo configuration for actions
* pyproject: Use new format to specify licenses
* action_utils: Drop support for link-local IPv6 addresses
* debian: Ensure that gbp creates a clean tarball prior to build
* syncthing: tests: Fix tests by allowing rapid restarts
* web_server: Log requests to WSGI app
* *: Update URL base from /plinth to /freedombox
* tests: functional: Fix expecting FreedomBox to be home page
* web_framework: Allow FreedomBox apps to override templates
* templates: Allow building pages without navigation bar and footer
* apache: Preserve host header when proxying to service
* oidc: New app to implement OpenID Connect Provider
* oidc: Style the page for authorizing an OIDC app
* apache: Implement protecting apps using OpenID Connect
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
* syncthing: Use OpenID Connect instead of pubtkt based SSO
* searx: Use OpenID Connect instead of pubtkt based SSO
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
* email: Use OpenID Connect instead of pubtkt based SSO
* calibre: Use OpenID Connect instead of pubtkt based SSO
* deluge: Use OpenID Connect instead of pubtkt based SSO
* gitweb: Use OpenID Connect instead of pubtkt based SSO
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
* transmission: Use OpenID Connect instead of pubtkt based SSO
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
* sharing: Use OpenID Connect instead of pubtkt based SSO
* sso: Merge into users module, drop pubtkt related code
* apache: Fix diagnosing URLs protected by OpenID Connect
[ 大王叫我来巡山 ]
* Translated using Weblate (Chinese (Simplified Han script))
[ Burak Yavuz ]
* Translated using Weblate (Turkish)
[ Coucouf ]
* Translated using Weblate (French)
* Translated using Weblate (French)
[ Besnik Bleta ]
* Translated using Weblate (Albanian)
[ Dietmar ]
* Translated using Weblate (German)
[ James Valleroy ]
* backups: Generate SSH client key if needed
* backups: Display SSH public key when adding remote
* backups: Copy SSH client public key to remote
* backups: Use SSH key instead of password
* backups: Use selected SSH credential for remote
* backups: Test adding/removing remote location
* backups: Arrange form for adding remote location
* backups: Migrate to SSH key auth when mounting
* Translated using Weblate (Greek)
* mumble: murmurd renamed to mumble-server
* Translated using Weblate (Tamil)
* locale: Update translation strings
* doc: Fetch latest manual
* apache: Fix check_url test
[ Frederico Gomes ]
* container: Align terminology in printed banner
* wireguard: filter .local addresses from showClient view
* wireguard: improved server section UX flow
* wireguard: show server vpn ip in show client page
* wireguard: Fix split tunneling
* miniflux: Revert workaround for a packaging bug with DB connection
* db: Create a utility to get credentials from dbconfig
* miniflux: Get credentials from dbconfig-common directly
[ Pierfrancesco Passerini ]
* Translated using Weblate (Italian)
[ Daniel Wiik ]
* Translated using Weblate (Swedish)
* Translated using Weblate (Swedish)
[ kosagi ]
* Translated using Weblate (Catalan)
[ Jiří Podhorecký ]
* Translated using Weblate (Czech)
[ Isak ]
* Translated using Weblate (Swedish)
[ Βασίλης Χατζηκαμάρης ]
* Translated using Weblate (Greek)
[ Benedek Nagy ]
* doc/dev: always have an up-to-date copyright year
[ தமிழ்நேரம் ]
* Translated using Weblate (Tamil)
-- James Valleroy <jvalleroy@mailbox.org> Mon, 02 Mar 2026 21:35:46 -0500
freedombox (26.3) unstable; urgency=medium
[ Frederico Gomes ]

2
debian/control vendored
View File

@ -35,7 +35,6 @@ Build-Depends:
python3-django-captcha,
# Explictly depend on ipware as it is optional dependecy of django-axes
python3-django-ipware,
python3-django-oauth-toolkit,
python3-django-stronghold,
python3-gi,
python3-markupsafe,
@ -109,7 +108,6 @@ Depends:
python3-django-captcha,
# Explictly depend on ipware as it is optional dependecy of django-axes
python3-django-ipware,
python3-django-oauth-toolkit,
python3-django-stronghold,
python3-gi,
python3-markupsafe,

1
debian/copyright vendored
View File

@ -79,7 +79,6 @@ Files: plinth/modules/ejabberd/static/icons/ejabberd.png
plinth/modules/rssbridge/static/icons/rssbridge.svg
plinth/modules/zoph/static/icons/zoph.png
plinth/modules/zoph/static/icons/zoph.svg
static/themes/default/img/application.svg
static/themes/default/img/network-connection.svg
static/themes/default/img/network-connection-vertical.svg
static/themes/default/img/network-ethernet.svg

3
debian/gbp.conf vendored
View File

@ -1,9 +1,6 @@
[DEFAULT]
debian-branch = main
[buildpackage]
export-dir = ../build-area/
[dch]
git-log = --no-merges
multimaint-merge = True

View File

@ -291,8 +291,10 @@ file ``transmission-plinth.conf``, add the following.
<Location /transmission>
...
Use AuthOpenIDConnect
Use RequireGroup bit-torrent
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "bit-torrent"
</IfModule>
</Location>
Showing a shortcut in the front page

View File

@ -8,100 +8,6 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 26.4 (2026-03-02) ==
=== Highlights ===
* backups: Enable key-based SSH authentication for remote backups
* oidc: New app to implement OpenID Connect Provider
* apache: Implement protecting apps using OpenID Connect
* wireguard: Improve server section user experience flow
=== Other Changes ===
* *: Remove some absolute file paths in SVGs
* *: Update URL base from /plinth to /freedombox
* README/HACKING: Update weblate project path to /freedombox
* Vagrantfile: Drop unnecessary sudo configuration for actions
* action_utils: Drop support for link-local IPv6 addresses
* action_utils: Fix issue with type checking a generator
* action_utils: Implement utility to change umask temporarily
* actions, privileged_daemon: Drop some unused global statements
* apache: Fix diagnosing URLs protected by OpenID Connect
* apache: Preserve host header when proxying to service
* backups: Arrange form for adding remote location
* backups: Avoid some repeated text in form help text
* backups: Copy SSH client public key to remote
* backups: Create .ssh folder before creating SSH key
* backups: Create a better comment in the generated SSH key file
* backups: Display SSH public key when adding remote
* backups: Fix issue with Javascript in add remote location form
* backups: Fix showing proper error for incorrect passphrase
* backups: Fix type checking errors
* backups: Generate SSH client key if needed
* backups: Migrate to SSH key auth when mounting
* backups: Minor refactoring
* backups: Show/hide form elements instead of disabling for simplicity
* backups: Simplify handling of migration to SSH keys
* backups: Test adding/removing remote location
* backups: Tweak appearance of add remote location form
* backups: Use SSH key instead of password
* backups: Use selected SSH credential for remote
* backups: tests: Simplify functional test using more classes
* bin: Add tool to change !FreedomBox password in Django database
* calibre: Use OpenID Connect instead of pubtkt based SSO
* cfg: Drop unused actions_dir option
* cfg: Drop unused config_dir option
* container: Align terminology in printed banner
* db: Create a utility to get credentials from dbconfig
* debian: Ensure that gbp creates a clean tarball prior to build
* deluge: Use OpenID Connect instead of pubtkt based SSO
* doc/dev: Set new theme for developer documentation
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
* doc/dev: always have an up-to-date copyright year
* ejabberd: Fix setting up certificates for multiple domains
* email: Use OpenID Connect instead of pubtkt based SSO
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
* gitweb: Fix issue with running post init due to missing method
* gitweb: Use OpenID Connect instead of pubtkt based SSO
* js: When page load fails during install, show it to user
* letsencrypt: When copying certificate reset the umask reliably
* locale/bg: Fix several translations with HTML links (Bulgarian)
* locale/de: Fix several translations with HTML links (German)
* locale: Update translations for Albanian, Catalan, Chinese (Simplified Han script), Czech, French, German, Greek, Italian, Swedish, Tamil, Turkish
* matrixsynapse: Update apache config to proxy Synapse client API
* miniflux: Get credentials from dbconfig-common directly
* miniflux: Revert workaround for a packaging bug with DB connection
* mumble: murmurd renamed to mumble-server
* oidc: Style the page for authorizing an OIDC app
* pyproject: Use new format to specify licenses
* quassel: Explicitly set permissions on the domain configuration file
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
* searx: Use OpenID Connect instead of pubtkt based SSO
* sharing: Use OpenID Connect instead of pubtkt based SSO
* sso: Merge into users module, drop pubtkt related code
* syncthing: Use OpenID Connect instead of pubtkt based SSO
* syncthing: tests: Fix tests by allowing rapid restarts
* templates: Allow building pages without navigation bar and footer
* tests: functional: Fix expecting !FreedomBox to be home page
* tests: functional: Fix reloading error page during install/uninstall
* tests: functional: Increase systemd rate limits for starting units
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
* transmission: Use OpenID Connect instead of pubtkt based SSO
* ui: Add animation for notification dismissal
* ui: Dismiss notifications without page reload
* ui: Refactor notification delete buttons to avoid repeating code
* web_framework: Allow !FreedomBox apps to override templates
* web_server: Log requests to WSGI app
* wireguard: Accept/use netmask with IP address for server connection
* wireguard: Fix format when showing multiple endpoints of the server
* wireguard: Fix showing default route setting in server edit form
* wireguard: Fix split tunneling
* wireguard: Show status of default route in server information page
* wireguard: filter .local addresses from showClient view
* wireguard: show server vpn ip in show client page
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
== FreedomBox 26.3 (2026-02-02) ==
=== Highlights ===

View File

@ -20,7 +20,7 @@ The content can be shared publicly or restricted to the users of listed allowed
=== Setting Up Shares ===
* In !FreedomBox web interface, enable the Sharing App. Only admins can create, edit or remove shares. They'll find the Sharing app in the Apps section of the !FreedomBox web interface. Many shares can coexist in the same server.
* In Plinth enable the Sharing App. Only admins can create, edit or remove shares. They'll find the Sharing app in the Apps section of the !FreedomBox web interface. Many shares can coexist in the same server.
* Add a new share
* Give it a name (an thereby the URL) with which the users will ask for the content. In the example above it would be called ''content_name''.
* The Disk Path of the content to be served. This path is relative to ''root'' on your !FreedomBox. For instance ''/var/lib/freedombox/sharing/content_name'' might be a choice.

View File

@ -11,19 +11,8 @@ Además de soportar varios SBC's (single board computers) y otros dispositivos,
== Hardware Recomendado ==
=== Libre Crafts FreedomBox ===
El 22 de Abril de 2019, la ''!FreedomBox Foundation'' anunció que los kits ''Pioneer Edition !FreedomBox Home Server'' salían a la [[https://freedomboxfoundation.org/buy/|venta]]. Este es el hardware preinstalado recomendado para todos los usuarios que no quieran construirse su propia (máquina) !FreedomBox eligiendo los componentes adecuados, descargando la imagen y preparando una tarjeta SD con (el software) !FreedomBox.
Libre Crafts es una iniviativa de los propios desarrolladores de !FreedomBox para proporcionar una !FreedomBox potente capaz de alojar las necesidades más exigentes de un servidor casero.
Los propios desarrolladores de !FreedomBox la montan. prueban y entregan. Tu compra ayuda al desarrollo de !FreedomBox.
Esta máquina lleva un procesador potente, mucha memoria, CPU, un disco de sitema operativo rápido, posibilidad de añador discos duros de alta capacidad, puertos Ethernet multi-gigabit duales, todo ello con bajo consumo.
Úsalo para alojar todas tus fotos, las copias de respaldo de tus otros dispositivos, como NAS, como centro de control de domótica, como ordenador de sobremesa, y más, todo a la vez.
||<style="text-align: center;"> [[FreedomBox/Hardware/LibreCrafts|{{attachment:FreedomBox/libre-crafts.png|FreedomBox de Libre Crafts|height=300}}]]<<BR>> [[FreedomBox/Hardware/LibreCrafts|FreedomBox de Libre Crafts]] ||
=== Olimex's FreedomBox Pioneer Edition ===
On April 22nd, 2019, the !FreedomBox Foundation announced the [[https://freedomboxfoundation.org/buy/|sales]] of the Pioneer Edition !FreedomBox Home Server Kits. This pre-installed hardware is for all users who don't wish to build their own !FreedomBox by choosing the right components, downloading the image and preparing an SD card with !FreedomBox.
El kit incluye todo el hardware necesario para arrancar un servidor casero !FreedomBox sobre una placa ''Olimex A20-OLinuXino-LIME2''. Este producto proporciona la combinación perfecta de hardware de fuentes abiertas y software libre. Al comprar este producto, soportas también los esfuerzos de la ''!FreedomBox Foundation'' para crear y promover su software de servidor libre.
||<style="text-align: center;"> [[es/FreedomBox/Hardware/PioneerEdition|{{attachment:FreedomBox/Hardware/pioneer-edition_thumb.jpg|Kits de servidor doméstico FreedomBox edición Pioneer|width=320,height=257}}]]<<BR>> [[es/FreedomBox/Hardware/PioneerEdition|Kits de servidor doméstico FreedomBox edición Pioneer]] ||

View File

@ -16,7 +16,7 @@ Los servidores caseros !FreedomBox Pioneer Edition los fabrica y vende Olimex, u
== Características del Producto ==
=== HW Recomendado ===
Éste es un hardware recomendado para los usuarios que quieran simplemente una !FreedomBox llave en mano, y '''no''' quieran '''construirse''' una.
Éste es el hardware recomendado para los usuarios que quieran simplemente una !FreedomBox llave en mano, y '''no''' quieran '''construirse''' una.
(Construir tu propia !FreedomBox implica algunos tecnicismos como elegir y comprar los componentes adecuados, descargar la imágen y preparar una tarjeta SD).

View File

@ -8,100 +8,6 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 26.4 (2026-03-02) ==
=== Highlights ===
* backups: Enable key-based SSH authentication for remote backups
* oidc: New app to implement OpenID Connect Provider
* apache: Implement protecting apps using OpenID Connect
* wireguard: Improve server section user experience flow
=== Other Changes ===
* *: Remove some absolute file paths in SVGs
* *: Update URL base from /plinth to /freedombox
* README/HACKING: Update weblate project path to /freedombox
* Vagrantfile: Drop unnecessary sudo configuration for actions
* action_utils: Drop support for link-local IPv6 addresses
* action_utils: Fix issue with type checking a generator
* action_utils: Implement utility to change umask temporarily
* actions, privileged_daemon: Drop some unused global statements
* apache: Fix diagnosing URLs protected by OpenID Connect
* apache: Preserve host header when proxying to service
* backups: Arrange form for adding remote location
* backups: Avoid some repeated text in form help text
* backups: Copy SSH client public key to remote
* backups: Create .ssh folder before creating SSH key
* backups: Create a better comment in the generated SSH key file
* backups: Display SSH public key when adding remote
* backups: Fix issue with Javascript in add remote location form
* backups: Fix showing proper error for incorrect passphrase
* backups: Fix type checking errors
* backups: Generate SSH client key if needed
* backups: Migrate to SSH key auth when mounting
* backups: Minor refactoring
* backups: Show/hide form elements instead of disabling for simplicity
* backups: Simplify handling of migration to SSH keys
* backups: Test adding/removing remote location
* backups: Tweak appearance of add remote location form
* backups: Use SSH key instead of password
* backups: Use selected SSH credential for remote
* backups: tests: Simplify functional test using more classes
* bin: Add tool to change !FreedomBox password in Django database
* calibre: Use OpenID Connect instead of pubtkt based SSO
* cfg: Drop unused actions_dir option
* cfg: Drop unused config_dir option
* container: Align terminology in printed banner
* db: Create a utility to get credentials from dbconfig
* debian: Ensure that gbp creates a clean tarball prior to build
* deluge: Use OpenID Connect instead of pubtkt based SSO
* doc/dev: Set new theme for developer documentation
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
* doc/dev: always have an up-to-date copyright year
* ejabberd: Fix setting up certificates for multiple domains
* email: Use OpenID Connect instead of pubtkt based SSO
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
* gitweb: Fix issue with running post init due to missing method
* gitweb: Use OpenID Connect instead of pubtkt based SSO
* js: When page load fails during install, show it to user
* letsencrypt: When copying certificate reset the umask reliably
* locale/bg: Fix several translations with HTML links (Bulgarian)
* locale/de: Fix several translations with HTML links (German)
* locale: Update translations for Albanian, Catalan, Chinese (Simplified Han script), Czech, French, German, Greek, Italian, Swedish, Tamil, Turkish
* matrixsynapse: Update apache config to proxy Synapse client API
* miniflux: Get credentials from dbconfig-common directly
* miniflux: Revert workaround for a packaging bug with DB connection
* mumble: murmurd renamed to mumble-server
* oidc: Style the page for authorizing an OIDC app
* pyproject: Use new format to specify licenses
* quassel: Explicitly set permissions on the domain configuration file
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
* searx: Use OpenID Connect instead of pubtkt based SSO
* sharing: Use OpenID Connect instead of pubtkt based SSO
* sso: Merge into users module, drop pubtkt related code
* syncthing: Use OpenID Connect instead of pubtkt based SSO
* syncthing: tests: Fix tests by allowing rapid restarts
* templates: Allow building pages without navigation bar and footer
* tests: functional: Fix expecting !FreedomBox to be home page
* tests: functional: Fix reloading error page during install/uninstall
* tests: functional: Increase systemd rate limits for starting units
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
* transmission: Use OpenID Connect instead of pubtkt based SSO
* ui: Add animation for notification dismissal
* ui: Dismiss notifications without page reload
* ui: Refactor notification delete buttons to avoid repeating code
* web_framework: Allow !FreedomBox apps to override templates
* web_server: Log requests to WSGI app
* wireguard: Accept/use netmask with IP address for server connection
* wireguard: Fix format when showing multiple endpoints of the server
* wireguard: Fix showing default route setting in server edit form
* wireguard: Fix split tunneling
* wireguard: Show status of default route in server information page
* wireguard: filter .local addresses from showClient view
* wireguard: show server vpn ip in show client page
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
== FreedomBox 26.3 (2026-02-02) ==
=== Highlights ===

View File

@ -19,15 +19,15 @@ El contenido se puede compartir públicamente o restringido a usuarios de una li
=== Editando comparticiones ===
Cada compartición tiene su propio ajuste de modo de compartición (pública o restringida). Sólo los grupos que reconoce el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La aplicación ''Sharing'' no ofrecerá los grupos creados en el interfaz de línea de órdenes.
Para que los usuarios accedan al contenido mediante su navegador debe existir y tener una compartición. Una compartición es una entrada en la aplicación Sharing que relaciona:
* El Nombre (y por tanto la URL) que usarán los usuarios para solicitar el contenido,
* el Ruta de acceso al contenido a servir y
* el modo de compartición. Si es restringido, también contendrá la lista de grupos autorizados.
En el mismo servidor pueden coexistir múltiples comparticiones.
* In el interfaz web de !FreedomBox, habilita la App ''Sharing''. Sólo los administradores pueden crear, editar o eliminar comparticiones. Encontrarán la aplicación ''Sharing'' en la sección Aplicaciones del interfaz web de !FreedomBox. En el mismo servidor pueden coexistir múltiples comparticiones.
* Añadir una nueva compartición:
* Dale un nombre (y por tanto la URL) que usarán los usuarios para solicitar el contenido, En el ejemplo anterior se llamaría ''nombre del contenido''.
* La Ruta completa de acceso al contenido a servir. Por ejemplo ''/var/lib/freedombox/sharing/nombre_del_contenido''.
* El modo de compartición. Si es restringido, también contendrá la lista de grupos autorizados. Solo los grupos reconocidos por el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La app no ofrecerá los grupos creados sólo en la línea de órdenes.
* Crea el directorio especificado en ''Ruta de Disco'' en !FreedomBox mediante ''Cockpit'', ''Nautilus'' o ingreso remoto.
* Asegúrate de que el usuario que proporcione el contenido tiene permiso para escribir en el directorio, por ejemplo, haciéndole dueño del directorio.
Sólo los administradores pueden crear, editar o eliminar comparticiones. Encontrarán la aplicación ''Sharing'' en la sección Aplicacions del interfaz web de !FreedomBox. La aplicación ''Sharing'' es una aplicación web fácil de usar y con un interfaz evidente.
Cada compartición tiene su priopio ajuste de modo de compartición (pública o restrigida). Sólo los grupos que reconoce el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La aplicación ''Sharing'' no ofrecerá los grupos creados en el interfaz de línea de órdenes.
=== Provisionar/actualizar el contenido ===

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

View File

@ -73,8 +73,8 @@
<para>
This the URL fragment under which Plinth will provide its services.
Plinth is shipped with a default value of
<filename>/freedombox</filename>. This means that Plinth will be
available as http://localhost:8000/freedombox by default.
<filename>/plinth</filename>. This means that Plinth will be
available as http://localhost:8000/plinth by default.
</para>
</listitem>
</varlistentry>
@ -194,7 +194,7 @@
<synopsis>$ plinth --server_dir='/myurl'</synopsis>
<para>
Run Plinth with the '/myurl' prefix. Note that Apache forwards requests
to '/freedombox' by default, so /myurl is not accessible outside of your
to '/plinth' by default, so /myurl is not accessible outside of your
FreedomBox without adapting the apache configuration.
</para>
</example>

View File

@ -13,7 +13,7 @@ from pathlib import Path
from xml.sax.saxutils import escape
BASE_URL = 'https://wiki.debian.org/'
LOCAL_BASE = '/freedombox/help/manual/{lang}/'
LOCAL_BASE = '/plinth/help/manual/{lang}/'
ICONS_DIR = 'icons'
DEFAULT_LANGUAGE = 'en'
@ -624,21 +624,21 @@ def resolve_url(url, context):
Locally available page in default language => shortcut to local copy:
>>> resolve_url('FreedomBox/Contribute', {'language': '', 'title': ''})
'/freedombox/help/manual/en/Contribute#'
'/plinth/help/manual/en/Contribute#'
Translated available page => shortcut to local copy:
>>> resolve_url('es/FreedomBox/Contribute', {'language': '', 'title': ''})
'/freedombox/help/manual/es/Contribute#'
'/plinth/help/manual/es/Contribute#'
Available page in default language refferred as translated => shortcut to
local copy:
>>> resolve_url('en/FreedomBox/Contribute', {'language': '', 'title': ''})
'/freedombox/help/manual/en/Contribute#'
'/plinth/help/manual/en/Contribute#'
Unrecognized language => handle considering it as default language:
>>> resolve_url('missing/FreedomBox/Contribute', {'language': '', \
'title': ''})
'/freedombox/help/manual/en/Contribute#'
'/plinth/help/manual/en/Contribute#'
"""
# Process first all easy, straight forward cases:
@ -1191,11 +1191,11 @@ from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>')
[Paragraph([PlainText('a')]), Paragraph([PlainText('b ')])]
>>> parse_wiki('{{{#!wiki caution\\n\\nOnce some other app is set as the \
home page, you can only navigate to the !FreedomBox Service (Plinth) by \
typing https://myfreedombox.rocks/freedombox/ into the browser. <<BR>>\\n\
typing https://myfreedombox.rocks/plinth/ into the browser. <<BR>>\\n\
''/freedombox'' can also be used as an alias to ''/plinth''\\n}}}')
[Admonition('caution', [Paragraph([PlainText('Once some other app is set \
as the home page, you can only navigate to the FreedomBox Service (Plinth) by \
typing '), Url('https://myfreedombox.rocks/freedombox/'), PlainText(' into the \
typing '), Url('https://myfreedombox.rocks/plinth/'), PlainText(' into the \
browser. ')]), Paragraph([PlainText('/freedombox can also be used as an alias \
to /plinth ')])])]
@ -1761,7 +1761,7 @@ Features introduction</ulink>'
>>> generate_inner_docbook([Link('../../Contribute', \
[PlainText('Contribute')])], context={'title': 'FreedomBox/Manual/Hardware'})
'<ulink url="/freedombox/help/manual/en/Contribute#">\
'<ulink url="/plinth/help/manual/en/Contribute#">\
Contribute</ulink>'
>>> generate_inner_docbook([Link('/Code', \
@ -1772,9 +1772,9 @@ Code</ulink>'
>>> generate_inner_docbook([Link('DebianBug:1234', [PlainText('Bug')])])
'<ulink url="https://bugs.debian.org/1234#">Bug</ulink>'
>>> generate_inner_docbook([Link('DebianPkg:freedombox', \
>>> generate_inner_docbook([Link('DebianPkg:plinth', \
[PlainText('Plinth')])])
'<ulink url="https://packages.debian.org/freedombox#">Plinth</ulink>'
'<ulink url="https://packages.debian.org/plinth#">Plinth</ulink>'
>>> generate_inner_docbook([Link('AliothList:freedombox-discuss', \
[PlainText('Discuss')])])
@ -1911,7 +1911,7 @@ PlainText(' on it. ')])])
'<para>An alternative to downloading these images is to \
<ulink url="https://wiki.debian.org/InstallingDebianOn/TI/BeagleBone#">\
install Debian</ulink> on the BeagleBone and then \
<ulink url="/freedombox/help/manual/en/Debian#">install \
<ulink url="/plinth/help/manual/en/Debian#">install \
FreedomBox</ulink> on it. </para>'
>>> generate_inner_docbook([Paragraph([PlainText('After Roundcube is \

View File

@ -3,4 +3,4 @@
Package init file.
"""
__version__ = '26.4'
__version__ = '26.3'

View File

@ -371,13 +371,12 @@ def get_addresses() -> list[dict[str, str | bool]]:
'url_address': hostname
})
# When a hostname is resolved to IPv6 address, it may likely be link-local
# address. Link local IPv6 addresses are valid only for a given link and
# need to be scoped with interface name such as '%eth0' to work. Browsers
# refused to implement support for link-local addresses (with zone IDs) in
# URLs due to platform specific parsing rules and other implementation
# difficulties. mod_auth_openidc does not support them either.
# XXX: When a hostname is resolved to IPv6 address, it may likely
# be link-local address. Link local IPv6 addresses are valid only
# for a given link and need to be scoped with interface name such
# as '%eth0' to work. Tools such as curl don't seem to handle
# this correctly.
# addresses.append({'kind': '6', 'address': hostname, 'numeric': False})
return addresses
@ -399,14 +398,12 @@ def get_ip_addresses() -> list[dict[str, str | bool]]:
}
if address['kind'] == '6' and address['numeric']:
address['url_address'] = '[{0}]'.format(address['address'])
if address['scope'] != 'link':
# Do not include link local addresses. Browsers refused to
# implement support for link-local addresses (with zone IDs) in
# URLs due to platform specific parsing rules and other
# implementation difficulties. mod_auth_openidc does not support
# them either.
address['url_address'] = '[{0}]'.format(address['address'])
else:
address['url_address'] = '[{0}%{1}]'.format(
address['url_address'], address['interface'])
addresses.append(address)
return addresses

View File

@ -12,11 +12,13 @@ logger = logging.getLogger(__name__)
# [Path] section
file_root = '/usr/share/plinth'
config_dir = '/etc/plinth'
data_dir = '/var/lib/plinth'
custom_static_dir = '/var/www/plinth/custom/static'
store_file = data_dir + '/plinth.sqlite3'
actions_dir = '/usr/share/plinth/actions'
doc_dir = '/usr/share/freedombox'
server_dir = '/freedombox'
server_dir = '/plinth'
# [Network] section
host = '127.0.0.1'
@ -109,9 +111,11 @@ def read_file(config_path):
config_items = (
('Path', 'file_root', 'string'),
('Path', 'config_dir', 'string'),
('Path', 'data_dir', 'string'),
('Path', 'custom_static_dir', 'string'),
('Path', 'store_file', 'string'),
('Path', 'actions_dir', 'string'),
('Path', 'doc_dir', 'string'),
('Path', 'server_dir', 'string'),
('Network', 'host', 'string'),

View File

@ -64,10 +64,10 @@ def fixture_load_cfg():
"""Load test configuration."""
from plinth import cfg
keys = ('file_root', 'data_dir', 'custom_static_dir', 'store_file',
'doc_dir', 'server_dir', 'host', 'port', 'use_x_forwarded_for',
'use_x_forwarded_host', 'secure_proxy_ssl_header', 'box_name',
'develop')
keys = ('file_root', 'config_dir', 'data_dir', 'custom_static_dir',
'store_file', 'actions_dir', 'doc_dir', 'server_dir', 'host',
'port', 'use_x_forwarded_for', 'use_x_forwarded_host',
'secure_proxy_ssl_header', 'box_name', 'develop')
saved_state = {}
for key in keys:
saved_state[key] = getattr(cfg, key)

View File

@ -1,40 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Utilities for parsing dbconfig-common files with Augeas."""
import pathlib
import augeas
def get_credentials(dbconfig_path: str) -> dict[str, str]:
"""Parse dbconfig-common file with Augeas Shellvars lens."""
if not pathlib.Path(dbconfig_path).is_file():
raise FileNotFoundError(f'DB config not found: {dbconfig_path}')
aug = _load_augeas(dbconfig_path)
required = ['dbc_dbuser', 'dbc_dbpass', 'dbc_dbname']
credentials = {}
for key in required + ['dbc_dbserver']:
credentials[key] = aug.get(key).strip('\'"')
if not all(credentials.get(key) for key in required):
raise ValueError('Missing required dbconfig-common credentials')
return {
'user': credentials['dbc_dbuser'],
'password': credentials['dbc_dbpass'],
'database': credentials['dbc_dbname'],
'host': credentials['dbc_dbserver'] or 'localhost'
}
def _load_augeas(config_path: str):
"""Initialize Augeas."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
pathstr = str(config_path)
aug.transform('Shellvars', pathstr)
aug.set('/augeas/context', f'/files{pathstr}')
aug.load()
return aug

View File

@ -1,3 +1,5 @@
[Path]
file_root = %(parent_parent_dir)s
config_dir = %(file_root)s/data/etc/plinth
actions_dir = %(file_root)s/actions
doc_dir = %(file_root)s/doc

View File

@ -10,7 +10,7 @@ translating the PO file from your language directory.
Introducing yourself is important since some work may have been done
already on Debian translators discussion lists and Weblate
localization platform.
https://hosted.weblate.org/projects/freedombox/freedombox/
https://hosted.weblate.org/projects/freedombox/plinth/
https://www.debian.org/MailingLists/subscribe
## Wiki: translators landing page

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,6 @@ Common Django middleware.
"""
import logging
import re
from django import urls
from django.conf import settings
@ -59,9 +58,7 @@ class SetupMiddleware(MiddlewareMixin):
except urls.Resolver404:
return
non_app_namespaces = {'oauth2_provider'}
if (not resolver_match.namespaces or not len(resolver_match.namespaces)
or (set(resolver_match.namespaces) & non_app_namespaces)):
if not resolver_match.namespaces or not len(resolver_match.namespaces):
# Requested URL does not belong to any application
return
@ -102,16 +99,9 @@ class AdminRequiredMiddleware(MiddlewareMixin):
hasattr(view_func, 'IS_NON_ADMIN'):
return
public_urls = settings.STRONGHOLD_PUBLIC_URLS
if any(re.match(url, request.path_info) for url in public_urls):
return
if is_user_admin(request):
return
if AdminRequiredMiddleware.check_user_group(view_func, request):
return
if not is_user_admin(request):
if not AdminRequiredMiddleware.check_user_group(
view_func, request):
raise PermissionDenied

View File

@ -1,79 +1,16 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""FreedomBox app for Apache server.
"""FreedomBox app for Apache server."""
This module implements a mechanism for protecting various URL paths using
FreedomBox's OpenID Connect implementation as Identity Provider and
mod_auth_oidc as Relying Party. The following is a simplified description of
how the flow works:
- User accesses the URL /foo using a browser. /foo is a URL path which is
protected by this module's OpenID Connect SSO (using AuthType
openid-connect).
- mod_auth_opendic seizes control and checks for authorization. Since this is
the first visit, it starts the authentication/authorization process. It first
redirects the browser to provider discovery URL
/freedombox/apache/discover-idp/.
- This URL selects and Identity Provider based on incoming URL's host header.
It will select https://mydomain.example/freedombox/o as IDP if the original
URL is https://mydomain.example/foo. Or https://freedombox.local/freedombox/o
if the original URL is https://freedombox.local/foo. After selection it will
redirect the browser back to /apache/oidc/callback with the selected IDP in
the GET parameters.
- /apache/oidc/callback is controlled by mod_auth_openidc which receives the
IDP selection. It will then query the IDP for further information such as
authorization URL, token URL, supported scopes and claims. This is done using
a backend call to /freedombox/o/.well-known/openid-configuration.
- After determining the authorization end point (/freedombox/o/authorize/) from
the metadata, mod_auth_openidc will start the authentication/authorization
process by redirecting the browser to the URL.
- FreedomBox shows login page if the user is not already logged in. User logs
in.
- FreedomBox will show a page asking the user to authorize the application to
access information such as name and email. In case of Apache's
mod_auth_openidc, this is skipped.
- FreedomBox will redirect back to /apache/oidc/callback after various checks.
This request will contain authorization grant token and OIDC claims in
parameters.
- mod_auth_openidc connects using back channel HTTP call to token endpoint
(/freedombox/o/token/) with the authorization grant token and then obtains
access token and refresh token. OIDC claims are checked using client_secret
known only to FreedomBox IDP and mod_auth_openidc.
- The OIDC claims contains username as part of 'sub' claim. This is exported as
REMOTE_USER header. 'freedombox_groups' contains the list of groups that
FreedomBox account is part of. These, along with 'Require claim' Apache
configuration directives, are used to determine if the user should get access
to /foo path or not.
- The application providing /foo will have access to information such username
and groups as part of REMOTE_USER and other OIDC_* environment variables.
- mod_auth_openidc also sets cookies that ensure that the whole process is not
repeated when a second request for the path /foo is received.
"""
import ipaddress
import os
from django.utils.translation import gettext_lazy as _
from plinth import action_utils
from plinth import app as app_module
from plinth import cfg
from plinth.config import DropinConfigs
from plinth.daemon import Daemon, RelatedDaemon
from plinth.modules import names
from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.modules.oidc.components import OpenIDConnect
from plinth.package import Packages
from plinth.signals import domain_added, domain_removed
from plinth.utils import format_lazy, is_valid_user_name
@ -86,7 +23,7 @@ class ApacheApp(app_module.App):
app_id = 'apache'
_version = 15
_version = 14
def __init__(self) -> None:
"""Create components for the app."""
@ -97,13 +34,11 @@ class ApacheApp(app_module.App):
self.add(info)
packages = Packages('packages-apache', [
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3',
'libapache2-mod-auth-openidc'
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3'
])
self.add(packages)
dropin_configs = DropinConfigs('dropin-configs-apache', [
'/etc/apache2/conf-available/10-freedombox.conf',
'/etc/apache2/conf-available/php-fpm-freedombox.conf',
'/etc/fail2ban/jail.d/apache-auth-freedombox.conf',
])
@ -124,13 +59,6 @@ class ApacheApp(app_module.App):
daemons=['apache2'], reload_daemons=True)
self.add(letsencrypt)
openidconnect = OpenIDConnect(
'openidconnect-apache', 'apache',
_('Web app protected by FreedomBox'),
redirect_uris=['https://{domain}/apache/oidc/callback'],
skip_authorization=True)
self.add(openidconnect)
daemon = Daemon('daemon-apache', 'apache2')
self.add(daemon)
@ -150,43 +78,6 @@ class ApacheApp(app_module.App):
self.enable()
def validate_host(hostname: str):
"""Check whether we are allowed to be called by a given name.
This is to prevent DNS rebinding attacks and other poor consequences in the
OpenID Connect protoctol.
"""
if hostname in ('localhost', 'ip6-localhost', 'ip6-loopback'):
return
if hostname == action_utils.get_hostname():
return
if hostname in names.components.DomainName.list_names():
return
try:
ipaddress.ip_address(hostname)
return
except ValueError:
pass
raise ValueError(f'Server not configured to be called as {hostname}')
def setup_oidc_client(netloc: str, hostname: str):
"""Setup OpenID Connect client configuration.
netloc is hostname or IP address along with port as parsed by
urllib.parse.urlparse() method from a URL.
"""
validate_host(hostname)
oidc = app_module.App.get('apache').get_component('openidconnect-apache')
privileged.setup_oidc_client(netloc, oidc.client_id,
oidc.get_client_secret())
def _on_domain_added(sender, domain_type, name='', description='',
services=None, **kwargs):
"""Add site specific configuration for a domain."""

View File

@ -301,19 +301,7 @@ def check_url(url: str, kind: str | None = None,
wrapper: str | None = None,
expected_output: str | None = None) -> bool:
"""Check whether a URL is accessible."""
# When testing a URL with cURL, following any redirections with --location.
# During those follows, store cookies that have been set and use them for
# later requests. mod_auth_openidc will set a cookie 'x_csrf' to prevent
# CSRF attacks and expect this cookie to sent back to it in later requests.
# If this cookie is not present, it will refuse to perform OIDC Discovery
# process resulting 404 errors and diagnostic failures for domains that
# have not been visited by a user recently. --cookie '' means the cURL will
# use an in-process cookie-jar for storing and retrieving cookies without
# writing to a file on the disk.
command = [
'curl', '--location', '--cookie', '', '--fail', '--write-out',
'%{response_code}'
]
command = ['curl', '--location', '-f', '-w', '%{response_code}']
if kind == '6':
# extract zone index

View File

@ -1,28 +0,0 @@
## SPDX-License-Identifier: AGPL-3.0-or-later
##
## DO NOT EDIT. If you do, FreedomBox will not automatically upgrade.
##
## Apache configuration managed by FreedomBox. If customization is needed,
## create a new configuration file with higher priority and override directives.
##
##
## Macro to protect directories or locations (potentially backed by application
## proxies) with OpenID Connect. To use the macro add 'Use AuthOpenIDConnect'.
## By default, the visitor will need to be part of the 'admin' group to be able
## to access the resource. Add additional groups using 'Use RequireGroup
## <group>'. To debug OpenID Connect related communication add 'LogLevel
## auth_openidc:debug'.
##
<Macro AuthOpenIDConnect>
<IfModule mod_auth_openidc.c>
AuthType openid-connect
Require claim freedombox_groups:admin
</IfModule>
</Macro>
<Macro RequireGroup $group>
<IfModule mod_auth_openidc.c>
Require claim freedombox_groups:$group
</IfModule>
</Macro>

View File

@ -2,22 +2,12 @@
"""Configure Apache web server."""
import glob
import json
import os
import pathlib
import re
import shutil
import urllib.parse
import augeas
from plinth import action_utils, utils
from plinth.actions import privileged, secret_str
openidc_config_path = pathlib.Path(
'/etc/apache2/conf-available/freedombox-openidc.conf')
metadata_dir_path = pathlib.Path(
'/var/cache/apache2/mod_auth_openidc/metadata/')
from plinth import action_utils
from plinth.actions import privileged
def _get_sort_key_of_version(version):
@ -80,8 +70,6 @@ def setup(old_version: int):
action_utils.run(['make-ssl-cert', 'generate-default-snakeoil'],
check=True)
_setup_oidc_config()
with action_utils.WebserverChange() as webserver:
# Disable mod_php as we have switched to mod_fcgi + php-fpm. Disable
# before switching away from mpm_prefork otherwise switching fails due
@ -126,7 +114,6 @@ def setup(old_version: int):
webserver.enable('headers', kind='module')
# Various modules for authentication/authorization
webserver.enable('auth_openidc', kind='module')
webserver.enable('authnz_ldap', kind='module')
webserver.enable('auth_pubtkt', kind='module')
@ -148,11 +135,9 @@ def setup(old_version: int):
webserver.enable('dav', kind='module')
webserver.enable('dav_fs', kind='module')
# setup freedombox configuration
webserver.enable('10-freedombox', kind='config')
# setup freedombox site
webserver.enable('freedombox', kind='config')
webserver.enable('freedombox-tls', kind='config')
webserver.enable('freedombox-openidc.conf', kind='config')
# enable serving Debian javascript libraries
webserver.enable('javascript-common', kind='config')
@ -166,119 +151,6 @@ def setup(old_version: int):
webserver.enable('freedombox-default', kind='site')
def _load_augeas():
"""Initialize augeas for this app's configuration file."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.transform('Httpd', str(openidc_config_path))
aug.set('/augeas/context', '/files' + str(openidc_config_path))
aug.load()
return aug
def _get_mod_openidc_passphrase() -> str:
"""Read existing mod-auth-openidc passphase.
Instead of generating a new passphrase every time, use existing one. If the
passphrase changes, all the existing sessions will be logged out and users
will have login to apps again.
"""
aug = _load_augeas()
for directive in aug.match('*/directive'):
if aug.get(directive) == 'OIDCCryptoPassphrase':
return aug.get(directive + '/arg')
# Does not exist already, generate new
return utils.generate_password(size=64)
@privileged
def setup_oidc_client(netloc: str, client_id: str, client_secret: secret_str):
"""Setup client ID and secret for provided domain.
netloc is hostname or IP address along with port as parsed by
urllib.parse.urlparse() method from a URL.
"""
issuer = f'{netloc}/freedombox/o'
issuer_quoted = urllib.parse.quote_plus(issuer)
client_path = metadata_dir_path / f'{issuer_quoted}.client'
if client_path.exists():
try:
current_data = json.loads(client_path.read_text())
if (current_data['client_id'] == client_id
and current_data['client_secret'] == client_secret):
return
except Exception:
pass
client_configuration = {
'client_id': client_id,
'client_secret': client_secret
}
previous_umask = os.umask(0o077)
try:
client_path.write_text(json.dumps(client_configuration))
finally:
os.umask(previous_umask)
shutil.chown(client_path, 'www-data', 'www-data')
def _setup_oidc_config():
"""Setup Apache as a OpenID Connect Relying Party.
Ensure that auth_openidc module's metadata directory is created. It will be
used to store provider-specific configuration. Since FreedomBox will be
configured with multiple domains and some of them may not be accessible due
to the access method, we need to configure a separate IDP for each domain.
This is also because auth_openidc does not allow IDP configuration with
relative URLs.
Keep the metadata directory and configuration file unreadable by non-admin
users since they contain module's crypto secret and OIDC client secret.
"""
metadata_dir_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
metadata_dir_path.mkdir(mode=0o700, exist_ok=True)
shutil.chown(metadata_dir_path.parent, 'www-data', 'www-data')
shutil.chown(metadata_dir_path, 'www-data', 'www-data')
# XXX: Default cache type is 'shm' or shared memory. This is lost when
# Apache is restarted and users/apps will have to reauthenticate. Improve
# this by using file (in tmpfs), redis, or memache caches.
passphrase = _get_mod_openidc_passphrase()
configuration = f'''##
## OpenID Connect related configuration
##
<IfModule mod_auth_openidc.c>
OIDCCryptoPassphrase {passphrase}
OIDCMetadataDir {str(metadata_dir_path)}
# Use relative URL to redirect to the same origin as the resource
OIDCDiscoverURL /freedombox/apache/discover-idp/
OIDCSSLValidateServer Off
OIDCProviderMetadataRefreshInterval 86400
# Use relative URL to return to the original domain
OIDCRedirectURI /apache/oidc/callback
OIDCRemoteUserClaim sub
# The redirect URI must always be under a location protected by
# mod_openidc.
<Location /apache>
AuthType openid-connect
# Checking audience is not necessary, but we need to check some claim.
Require claim aud:apache
</Location>
</IfModule>
'''
previous_umask = os.umask(0o077)
try:
openidc_config_path.write_text(configuration)
finally:
os.umask(previous_umask)
# TODO: Check that the (name, kind) is a managed by FreedomBox before
# performing operation.
@privileged

View File

@ -473,8 +473,7 @@ def test_diagnose_url(get_addresses, check):
def test_check_url(run):
"""Test checking whether a URL is accessible."""
url = 'http://localhost/test'
basic_command = ['curl', '--location', '--cookie', '', '--fail',
'--write-out', '%{response_code}']
basic_command = ['curl', '--location', '-f', '-w', '%{response_code}']
extra_args = {'env': None, 'check': True, 'stdout': -1, 'stderr': -1}
# Basic

View File

@ -1,12 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""URLs for the Apache module."""
"""
URLs for the Apache module.
"""
from django.urls import re_path
from stronghold.decorators import public
from .views import DiscoverIDPView
urlpatterns = [
re_path(r'^apache/discover-idp/$', public(DiscoverIDPView.as_view()),
name='discover-idp'),
]
urlpatterns: list = []

View File

@ -1,71 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Views for the Apache app."""
from urllib.parse import urlencode, urlparse
from django.http import (HttpResponseBadRequest, HttpResponseRedirect,
HttpResponseServerError)
from django.views import View
from . import setup_oidc_client, validate_host
# By default 'openid' scope already included by mod_auth_openidc
OIDC_SCOPES = 'email freedombox_groups'
class DiscoverIDPView(View):
"""A view called by auth_openidc Apache module to find the IDP.
According to documentation for auth_openidc: an Issuer selection can be
passed back to the callback URL as in:
<callback-url>?iss=[${issuer}|${domain}|${e-mail-style-account-name}]
[parameters][&login_hint=<login-hint>][&scopes=<scopes>]
[&auth_request_params=<params>]
where the <iss> parameter contains the URL-encoded issuer value of the
selected Provider (or...), [parameters] contains the additional parameters
that were passed in on the discovery request (e.g.
target_link_uri=<url>&x_csrf=<x_csrf>&method=<method>&scopes=<scopes>)
"""
def get(self, request):
"""Redirect back to auth_openidc module after selecting a IDP."""
target_link_uri = request.GET.get('target_link_uri', '')
method = request.GET.get('method', 'get')
x_csrf = request.GET.get('x_csrf', '')
oidc_callback = request.GET.get('oidc_callback')
if method != 'get':
return HttpResponseBadRequest(f'Cannot handle "{method}" method')
oidc_callback_parts = urlparse(oidc_callback)
request_host = request.META['HTTP_HOST']
if request_host != oidc_callback_parts.netloc:
return HttpResponseBadRequest(
f'Cannot redirect from {request_host} to a different host '
f'{oidc_callback_parts.netloc}')
try:
validate_host(oidc_callback_parts.hostname)
except ValueError:
return HttpResponseBadRequest(
f'Accessed using unknown domain {request_host}. Please add '
'the domain to list of configured domains.')
try:
setup_oidc_client(oidc_callback_parts.netloc,
oidc_callback_parts.hostname)
except ValueError:
return HttpResponseServerError(
f'Server not configured to called as {request_host}')
url = '/apache/oidc/callback'
params = {
'iss': f'https://{request_host}/freedombox/o',
'target_link_uri': target_link_uri,
'method': method,
'x_csrf': x_csrf,
'scopes': OIDC_SCOPES,
}
params = urlencode(params)
return HttpResponseRedirect(f'{url}?{params}')

View File

@ -85,8 +85,7 @@ def test_remote_backup_location(session_browser):
def _assert_main_page_is_shown(session_browser):
assert (session_browser.url.endswith('/freedombox/')
or session_browser.url.endswith('/plinth/'))
assert (session_browser.url.endswith('/plinth/'))
def _backup_download(session_browser, downloaded_file_info, archive_name):
@ -127,7 +126,7 @@ def _backup_schedule_get(browser):
functional.nav_to_module(browser, 'backups')
with functional.wait_for_page_update(browser):
browser.links.find_by_href(
'/freedombox/sys/backups/root/schedule/').first.click()
'/plinth/sys/backups/root/schedule/').first.click()
without_apps = []
elements = browser.find_by_name('backups_schedule-selected_apps')
@ -159,7 +158,7 @@ def _backup_schedule_set(browser, enable, daily, weekly, monthly, run_at,
functional.nav_to_module(browser, 'backups')
with functional.wait_for_page_update(browser):
browser.links.find_by_href(
'/freedombox/sys/backups/root/schedule/').first.click()
'/plinth/sys/backups/root/schedule/').first.click()
if enable:
browser.find_by_name('backups_schedule-enabled').check()
@ -193,7 +192,7 @@ def _download_file_logged_in(browser, url, suffix=''):
def _download(browser, archive_name=None):
"""Download a backup archive to a temporary file on disk."""
functional.nav_to_module(browser, 'backups')
href = f'/freedombox/sys/backups/root/download/{archive_name}/'
href = f'/plinth/sys/backups/root/download/{archive_name}/'
url = functional.base_url + href
file_path = _download_file_logged_in(browser, url, suffix='.tar.gz')
return file_path
@ -202,23 +201,22 @@ def _download(browser, archive_name=None):
def _open_main_page(browser):
"""Open the FreedomBox interface main page."""
with functional.wait_for_page_update(browser):
browser.links.find_by_href('/freedombox/').first.click()
browser.links.find_by_href('/plinth/').first.click()
def _upload_and_restore(browser, app_name, downloaded_file_path):
"""Upload a backup archive from the disk and perform restore operation."""
functional.nav_to_module(browser, 'backups')
with functional.wait_for_page_update(browser):
browser.links.find_by_href(
'/freedombox/sys/backups/upload/').first.click()
browser.links.find_by_href('/plinth/sys/backups/upload/').first.click()
fileinput = browser.find_by_id('id_backups-file')
fileinput.fill(downloaded_file_path)
# submit upload form
functional.submit(browser, form_class='form-upload')
# submit restore form
with functional.wait_for_page_update(
browser, expected_url='/freedombox/sys/backups/'):
with functional.wait_for_page_update(browser,
expected_url='/plinth/sys/backups/'):
functional.submit(browser, form_class='form-restore')
@ -235,7 +233,7 @@ def _add_remote_backup_location(browser, ssh_use_password=True):
_remove_remote_backup_location(browser)
browser.links.find_by_href(
'/freedombox/sys/backups/repositories/add-remote/').first.click()
'/plinth/sys/backups/repositories/add-remote/').first.click()
browser.find_by_name('repository').fill(REMOTE_PATH)
password = functional.get_password(
functional.config['DEFAULT']['username'])

View File

@ -15,7 +15,7 @@
version="1.1"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="bepasty.svg"
inkscape:export-filename="bepasty.png"
inkscape:export-filename="/media/Volume/Dokumente/Inkscape/bepasty.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -68,7 +68,7 @@ def _set_default_permissions(browser, permissions=''):
def _add_password(browser):
functional.visit(browser, '/freedombox/apps/bepasty/add/')
functional.visit(browser, '/plinth/apps/bepasty/add/')
for permission in ['read', 'create', 'list', 'delete', 'admin']:
browser.find_by_css('#id_bepasty-permissions input[value="{}"]'.format(
permission)).check()

View File

@ -3,13 +3,8 @@
##
<Location /calibre>
ProxyPass http://localhost:8844/calibre
Use AuthOpenIDConnect
Use RequireGroup calibre
# Calibre has a bug where an empty value for a HTTP header is treated as
# invalid. OIDC_CLAIM_email can be empty and this causes Calibre to error
# out. So, try to pass all the OpenID Connect user information and claims as
# headers that can't have an empty value.
OIDCPassIDTokenAs "serialized"
OIDCPassUserInfoAs "json"
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "calibre"
</IfModule>
</Location>

View File

@ -60,7 +60,7 @@ def _add_library(browser, name):
return
browser.links.find_by_href(
'/freedombox/apps/calibre/library/create/').first.click()
'/plinth/apps/calibre/library/create/').first.click()
browser.find_by_id('id_calibre-name').fill(name)
functional.submit(browser, form_class='form-calibre')
@ -69,7 +69,7 @@ def _delete_library(browser, name, ignore_missing=False):
"""Delete a library."""
functional.nav_to_module(browser, 'calibre')
link = browser.links.find_by_href(
f'/freedombox/apps/calibre/library/{name}/delete/')
f'/plinth/apps/calibre/library/{name}/delete/')
if not link:
if ignore_missing:
return
@ -84,7 +84,7 @@ def _is_library_available(browser, name):
"""Return whether a library is present in the list of libraries."""
functional.nav_to_module(browser, 'calibre')
link = browser.links.find_by_href(
f'/freedombox/apps/calibre/library/{name}/delete/')
f'/plinth/apps/calibre/library/{name}/delete/')
return bool(link)

View File

@ -94,7 +94,7 @@ def home_page_url2scid(url: str | None):
if url in ('/plinth/', '/plinth', 'plinth', '/freedombox/', '/freedombox',
'freedombox'):
return 'freedombox'
return 'plinth'
if url and url.startswith('/~'):
return 'uws-{}'.format(user_of_uws_url(url))
@ -109,8 +109,8 @@ def home_page_url2scid(url: str | None):
def _home_page_scid2url(shortcut_id: str) -> str | None:
"""Return the url for the given home page shortcut ID."""
url: str | None = '/freedombox/'
if shortcut_id == 'freedombox':
url: str | None = '/plinth/'
if shortcut_id == 'plinth':
pass
elif shortcut_id == 'apache-default':
url = None
@ -134,7 +134,7 @@ def _get_home_page_url() -> str | None:
"""Get the default application for the domain."""
conf_file = privileged.APACHE_HOMEPAGE_CONFIG
if not pathlib.Path(conf_file).exists():
return '/freedombox/'
return '/plinth/'
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)

View File

@ -24,8 +24,8 @@ def get_homepage_choices():
format_lazy(gettext_lazy("{user}'s website"), user=user))
for user, url in get_users_with_website().items()]
apache_default = ('apache-default', _('Apache Default'))
freedombox = ('freedombox', _('FreedomBox Service (Plinth)'))
return [apache_default, freedombox] + uws_choices + shortcut_choices
plinth = ('plinth', _('FreedomBox Service (Plinth)'))
return [apache_default, plinth] + uws_choices + shortcut_choices
class ConfigurationForm(forms.Form):
@ -39,8 +39,8 @@ class ConfigurationForm(forms.Form):
'case is to set your blog or wiki as the home page when '
'someone visits the domain name. Note that once the home '
'page is set to something other than {box_name} Service '
'(Plinth), your users must explicitly type /freedombox to '
'reach {box_name} Service (Plinth).'),
'(Plinth), your users must explicitly type /plinth or '
'/freedombox to reach {box_name} Service (Plinth).'),
box_name=gettext_lazy(cfg.box_name)), required=False,
choices=get_homepage_choices)

View File

@ -21,18 +21,15 @@ def test_homepage_mapping():
func = home_page_url2scid
assert func(None) == 'apache-default'
assert func('/unknown/url') is None
assert func('/plinth/') == 'freedombox'
assert func('/plinth') == 'freedombox'
assert func('plinth') == 'freedombox'
assert func('/freedombox/') == 'freedombox'
assert func('/freedombox') == 'freedombox'
assert func('freedombox') == 'freedombox'
assert func('/plinth/') == 'plinth'
assert func('/plinth') == 'plinth'
assert func('plinth') == 'plinth'
assert func('/index.html') == 'apache-default'
assert func('/~user') == 'uws-user'
assert func('/~user/whatever/else') == 'uws-user'
func = _home_page_scid2url
assert func('freedombox') == '/freedombox/'
assert func('plinth') == '/plinth/'
assert func('apache-default') is None
@ -66,7 +63,7 @@ def test_homepage_mapping_skip_ci():
# AC: Return None if it doesn't:
os.rmdir(uws_directory)
assert _home_page_scid2url(uws_scid) == '/freedombox/'
assert _home_page_scid2url(uws_scid) == '/plinth/'
@patch(
@ -105,7 +102,7 @@ def test_homepage_field():
uws_url = uws_url_of_user(user)
uws_scid = home_page_url2scid(uws_url)
default_home_page = 'freedombox'
default_home_page = 'plinth'
original_home_page = get_home_page() or default_home_page
change_home_page(default_home_page) # Set to known value explicitly
@ -116,7 +113,7 @@ def test_homepage_field():
# AC: valid changes actually happen:
pathlib.Path(uws_directory).mkdir(parents=True)
for scid in ('b', 'a', uws_scid, 'apache-default', 'freedombox'):
for scid in ('b', 'a', uws_scid, 'apache-default', 'plinth'):
change_home_page(scid)
assert get_home_page() == scid

View File

@ -22,12 +22,12 @@ def test_change_home_page(session_browser):
functional.app_enable(session_browser, 'syncthing')
_set_home_page(session_browser, 'syncthing')
_set_home_page(session_browser, 'freedombox')
assert _check_home_page_redirect(session_browser, 'freedombox')
_set_home_page(session_browser, 'plinth')
assert _check_home_page_redirect(session_browser, 'plinth')
def _set_home_page(browser, home_page):
if 'freedombox' not in home_page and 'apache' not in home_page:
if 'plinth' not in home_page and 'apache' not in home_page:
home_page = 'shortcut-' + home_page
functional.nav_to_module(browser, 'config')
@ -39,4 +39,4 @@ def _set_home_page(browser, home_page):
def _check_home_page_redirect(browser, app_name):
functional.visit(browser, '/')
return browser.find_by_xpath(
"//a[contains(@href, '/freedombox/') and @title='FreedomBox']")
"//a[contains(@href, '/plinth/') and @title='FreedomBox']")

View File

@ -16,7 +16,7 @@
width="512"
height="512"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="coturn.png"
inkscape:export-filename="/home/bunny/work/freedombox/plinth/static/themes/default/icons/coturn.png"
inkscape:export-xdpi="48"
inkscape:export-ydpi="48">
<metadata

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -8,8 +8,10 @@
##
<Location /deluge>
ProxyPass http://localhost:8112
Use AuthOpenIDConnect
Use RequireGroup bit-torrent
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "bit-torrent"
</IfModule>
## Send the scheme from user's request to enable Deluge to redirect URLs,
## set cookies, set absolute URLs (if any) properly.
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS

View File

@ -16,7 +16,7 @@
sodipodi:version="0.32"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Deluge-Logo.svg"
inkscape:export-filename="deluge.png"
inkscape:export-filename="/home/zach/deluge.png"
inkscape:export-xdpi="480"
inkscape:export-ydpi="480"
inkscape:output_extension="org.inkscape.output.svg.inkscape"

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -100,7 +100,7 @@ class DiagnosticsApp(app_module.App):
results = super().diagnose()
results.append(diagnose_port_listening(8000, 'tcp4'))
results.extend(
diagnose_url_on_all('http://{host}/freedombox/',
diagnose_url_on_all('http://{host}/plinth/',
check_certificate=False))
return results

View File

@ -112,7 +112,7 @@ def _configure(browser, config):
functional.nav_to_module(browser, 'dynamicdns')
functional.click_link_by_href(browser,
'/freedombox/sys/dynamicdns/domain/add/')
'/plinth/sys/dynamicdns/domain/add/')
for key, value in config.items():
field_id = f'id_domain-{key}'
if key == 'service_type':
@ -130,7 +130,7 @@ def _configure(browser, config):
def _assert_has_config(browser, config):
functional.nav_to_module(browser, 'dynamicdns')
link = f'/freedombox/sys/dynamicdns/domain/{config["domain"]}/edit/'
link = f'/plinth/sys/dynamicdns/domain/{config["domain"]}/edit/'
functional.click_link_by_href(browser, link)
for key, value in config.items():
if key == 'password':
@ -153,6 +153,6 @@ def _get_domains(browser):
def _delete_domain(browser, domain):
"""Delete a given domain."""
functional.nav_to_module(browser, 'dynamicdns')
link = f'/freedombox/sys/dynamicdns/domain/{domain}/delete/'
link = f'/plinth/sys/dynamicdns/domain/{domain}/delete/'
functional.click_link_by_href(browser, link)
functional.submit(browser, form_class='form-delete')

View File

@ -50,7 +50,7 @@ class EjabberdApp(app_module.App):
app_id = 'ejabberd'
_version = 10
_version = 9
def __init__(self) -> None:
"""Create components for the app."""
@ -98,10 +98,8 @@ class EjabberdApp(app_module.App):
urls=['http://{host}/bosh/'])
self.add(webserver)
# Always setup certificates for all domains to keep configuration
# simple.
letsencrypt = LetsEncrypt(
'letsencrypt-ejabberd', domains='*', daemons=['ejabberd'],
'letsencrypt-ejabberd', domains=get_domains, daemons=['ejabberd'],
should_copy_certificates=True,
private_key_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
certificate_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
@ -142,10 +140,11 @@ class EjabberdApp(app_module.App):
logger.info('ejabberd service domain name - %s', domain_name)
privileged.pre_install(domain_name)
# XXX: Configure all other domain names
super().setup(old_version)
# Setup certificates for all domains to keep configuration simple
self.get_component('letsencrypt-ejabberd').setup_certificates()
privileged.setup()
self.get_component('letsencrypt-ejabberd').setup_certificates(
[domain_name])
privileged.setup(domain_name)
if not old_version:
self.enable()

View File

@ -45,7 +45,7 @@ def pre_install(domain_name: str):
@privileged
def setup() -> None:
def setup(domain_name: str):
"""Enable LDAP authentication."""
with open(EJABBERD_CONFIG, 'r', encoding='utf-8') as file_handle:
conf = yaml.load(file_handle)
@ -83,16 +83,10 @@ def setup() -> None:
conf['ldap_base'] = scalarstring.DoubleQuotedScalarString(
'ou=users,dc=thisbox')
# Read all available certificates
conf['certfiles'].append(
scalarstring.DoubleQuotedScalarString(
'/etc/ejabberd/letsencrypt/*/ejabberd.pem'))
conf['certfiles'] = list(set(conf['certfiles']))
with open(EJABBERD_CONFIG, 'w', encoding='utf-8') as file_handle:
yaml.dump(conf, file_handle)
_upgrade_config()
_upgrade_config(domain_name)
try:
action_utils.run(['ejabberdctl', 'restart'], check=True)
@ -101,7 +95,7 @@ def setup() -> None:
err)
def _upgrade_config() -> None:
def _upgrade_config(domain):
"""Fix the config file by removing deprecated settings."""
current_version = _get_version()
if not current_version:
@ -131,9 +125,13 @@ def _upgrade_config() -> None:
if listen_port['port'] == 5280:
listen_port['port'] = 5443
# s2s_certfile is deprecated in favor of certfiles
if 's2s_certfile' in conf:
conf.pop('s2s_certfile')
cert_dir = Path('/etc/ejabberd/letsencrypt') / domain
cert_file = str(cert_dir / 'ejabberd.pem')
cert_file = scalarstring.DoubleQuotedScalarString(cert_file)
conf['s2s_certfile'] = cert_file
for listen_port in conf['listen']:
if 'certfile' in listen_port:
listen_port['certfile'] = cert_file
# Write changes back to the file
with open(EJABBERD_CONFIG, 'w', encoding='utf-8') as file_handle:

View File

@ -118,7 +118,7 @@ def _jsxc_login(browser):
"""Login to JSXC."""
username = functional.config['DEFAULT']['username']
password = functional.config['DEFAULT']['password']
functional.visit(browser, '/freedombox/apps/jsxc/jsxc/')
functional.visit(browser, '/plinth/apps/jsxc/jsxc/')
assert functional.eventually(browser.find_by_text,
['BOSH Server reachable.'])
if browser.find_by_text('relogin'):

View File

@ -38,7 +38,7 @@ _description = [
'they can add aliases to their email address. Necessary aliases '
'such as "postmaster" are automatically created pointing to the '
'first admin user.'), box_name=_(cfg.box_name)),
_('<a href="/freedombox/apps/roundcube/">Roundcube app</a> provides web '
_('<a href="/plinth/apps/roundcube/">Roundcube app</a> provides web '
'interface for users to access email.'),
_('During installation, any other email servers in the system will be '
'uninstalled.')

View File

@ -14,7 +14,10 @@
ProxyAddHeaders off
# Require SSO
Use AuthOpenIDConnect
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin"
</IfModule>
</Location>
# Automatic configuration for clients like Thunderbird:
@ -22,12 +25,12 @@
<Location "/.well-known/autoconfig/mail/config-v1.1.xml">
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^ /freedombox/apps/email/config.xml [PT]
RewriteRule ^ /plinth/apps/email/config.xml [PT]
</IfModule>
</Location>
# If Roundcube is not yet installed and the user clicks on 'Launch web client',
# redirect to the installation page instead of Apache's Not Found page.
<Location "/roundcube/">
ErrorDocument 404 /freedombox/apps/roundcube/
ErrorDocument 404 /plinth/apps/roundcube/
</Location>

View File

@ -5,8 +5,10 @@
Alias /featherwiki /var/lib/featherwiki
<Location /featherwiki>
Use AuthOpenIDConnect
Use RequireGroup wiki
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "wiki"
</IfModule>
# Disable caching
<IfModule mod_headers.c>

Some files were not shown because too many files have changed in this diff Show More