Compare commits
58 Commits
68ccb46ecf
...
cfc5d3acab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfc5d3acab | ||
|
|
212d865b43 | ||
|
|
9a55902f37 | ||
|
|
07845bc960 | ||
|
|
abbc4e7557 | ||
|
|
af70c73f24 | ||
|
|
5ccb332ce6 | ||
|
|
e2047ec3a0 | ||
|
|
a7584b465d | ||
|
|
6fd85e3e46 | ||
|
|
31e7997d2b | ||
|
|
68126c3ec6 | ||
|
|
29ef56b51e | ||
|
|
efe2bccb11 | ||
|
|
64272a2bef | ||
|
|
af892adb5e | ||
|
|
4ab2007c99 | ||
|
|
3c1d801e15 | ||
|
|
e2da29cf25 | ||
|
|
ce62fdb142 | ||
|
|
cad6bc8ca0 | ||
|
|
483f28de83 | ||
|
|
64f1a1c918 | ||
|
|
cdfbff0b6b | ||
|
|
45076cc603 | ||
|
|
f0b1aa34ac | ||
|
|
bced133d90 | ||
|
|
a8e2d4cd69 | ||
|
|
bbbe2cf950 | ||
|
|
168f662a17 | ||
|
|
0d579012d7 | ||
|
|
854916c54c | ||
|
|
fde0a620f9 | ||
|
|
185559b43f | ||
|
|
82d7cd0e8f | ||
|
|
0eca1394c0 | ||
|
|
4371e2475d | ||
|
|
a7ec37dbce | ||
|
|
778c35f2bc | ||
|
|
bf83cb5a5b | ||
|
|
284a384d3a | ||
|
|
af6d1d9a4c | ||
|
|
9a524b331b | ||
|
|
72005d6205 | ||
|
|
4b24fda3f5 | ||
|
|
ad9ebe2301 | ||
|
|
7e7e7a6ccf | ||
|
|
643a06c7cd | ||
|
|
8a7e70aab2 | ||
|
|
11d58134e5 | ||
|
|
bc257af638 | ||
|
|
6ba35df665 | ||
|
|
e4ee756918 | ||
|
|
7f2b49f70c | ||
|
|
36fb92a953 | ||
|
|
57816029e5 | ||
|
|
3be73bad59 | ||
|
|
5e112bd8bf |
@ -1,16 +0,0 @@
|
||||
#!/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)
|
||||
@ -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/plinth/).
|
||||
[Weblate](https://hosted.weblate.org/projects/freedombox/freedombox/).
|
||||
Your changes will automatically get pushed to the code repository.
|
||||
|
||||
Alternatively, you can directly edit the `.po` file in your language directory
|
||||
|
||||
@ -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/plinth
|
||||
UI should be accessible at http://localhost:8000/freedombox
|
||||
|
||||
If you are installing FreedomBox Service (Plinth) for development purposes, see
|
||||
HACKING.md instead.
|
||||
|
||||
6
Makefile
@ -21,7 +21,8 @@ DISABLED_APPS_TO_REMOVE := \
|
||||
tahoe \
|
||||
mldonkey \
|
||||
i2p \
|
||||
ttrss
|
||||
ttrss \
|
||||
sso
|
||||
|
||||
APP_FILES_TO_REMOVE := $(foreach app,$(DISABLED_APPS_TO_REMOVE),$(ENABLED_APPS_PATH)/$(app))
|
||||
|
||||
@ -106,6 +107,7 @@ 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)
|
||||
@ -229,7 +231,7 @@ provision-dev:
|
||||
sshpass bash-completion
|
||||
|
||||
wait-while-first-setup:
|
||||
while [ x$$(curl -k https://localhost/plinth/status/ 2> /dev/null | \
|
||||
while [ x$$(curl -k https://localhost/freedombox/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
@ -28,17 +28,11 @@ 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/plinth
|
||||
for development. Plinth will be available at https://localhost:4430/freedombox
|
||||
(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
|
||||
|
||||
61
bin/freedombox-change-password
Executable file
@ -0,0 +1,61 @@
|
||||
#!/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()
|
||||
@ -12,6 +12,7 @@
|
||||
# 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>
|
||||
|
||||
@ -39,16 +39,16 @@
|
||||
</If>
|
||||
|
||||
##
|
||||
## Redirect traffic on home to /plinth as part of turning the machine
|
||||
## Redirect traffic on home to /freedombox 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 "^/$" "/plinth"
|
||||
RedirectMatch "^/$" "/freedombox"
|
||||
</IfFile>
|
||||
|
||||
##
|
||||
## On all sites, provide FreedomBox on a default path: /plinth
|
||||
## On all sites, provide FreedomBox on a default path: /freedombox
|
||||
##
|
||||
## Requires the following Apache modules to be enabled:
|
||||
## mod_headers
|
||||
@ -56,7 +56,8 @@
|
||||
## mod_proxy_http
|
||||
##
|
||||
<Location /freedombox>
|
||||
ProxyPass http://127.0.0.1:8000/plinth
|
||||
ProxyPass http://127.0.0.1:8000/freedombox
|
||||
ProxyPreserveHost On
|
||||
## 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
|
||||
@ -70,7 +71,20 @@
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
<Location /plinth>
|
||||
ProxyPass http://127.0.0.1:8000/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
|
||||
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
@ -82,7 +96,7 @@
|
||||
<Location ~ ^/favicon\.ico$>
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule /favicon\.ico$ "/plinth/static/theme/img/favicon.ico" [PT]
|
||||
RewriteRule /favicon\.ico$ "/freedombox/static/theme/img/favicon.ico" [PT]
|
||||
</IfModule>
|
||||
</Location>
|
||||
|
||||
|
||||
141
debian/changelog
vendored
@ -1,3 +1,144 @@
|
||||
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
@ -35,6 +35,7 @@ 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,
|
||||
@ -108,6 +109,7 @@ 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
@ -79,6 +79,7 @@ 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
@ -1,6 +1,9 @@
|
||||
[DEFAULT]
|
||||
debian-branch = main
|
||||
|
||||
[buildpackage]
|
||||
export-dir = ../build-area/
|
||||
|
||||
[dch]
|
||||
git-log = --no-merges
|
||||
multimaint-merge = True
|
||||
|
||||
@ -291,10 +291,8 @@ file ``transmission-plinth.conf``, add the following.
|
||||
|
||||
<Location /transmission>
|
||||
...
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "bit-torrent"
|
||||
</IfModule>
|
||||
Use AuthOpenIDConnect
|
||||
Use RequireGroup bit-torrent
|
||||
</Location>
|
||||
|
||||
Showing a shortcut in the front page
|
||||
|
||||
@ -8,6 +8,100 @@ 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 ===
|
||||
|
||||
@ -20,7 +20,7 @@ The content can be shared publicly or restricted to the users of listed allowed
|
||||
|
||||
=== Setting Up Shares ===
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
@ -11,8 +11,19 @@ Además de soportar varios SBC's (single board computers) y otros dispositivos,
|
||||
|
||||
== Hardware Recomendado ==
|
||||
|
||||
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 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]] ||
|
||||
|
||||
@ -16,7 +16,7 @@ Los servidores caseros !FreedomBox Pioneer Edition los fabrica y vende Olimex, u
|
||||
== Características del Producto ==
|
||||
|
||||
=== HW Recomendado ===
|
||||
Éste es el hardware recomendado para los usuarios que quieran simplemente una !FreedomBox llave en mano, y '''no''' quieran '''construirse''' una.
|
||||
Éste es un 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).
|
||||
|
||||
|
||||
@ -8,6 +8,100 @@ 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 ===
|
||||
|
||||
@ -19,15 +19,15 @@ El contenido se puede compartir públicamente o restringido a usuarios de una li
|
||||
|
||||
=== Editando comparticiones ===
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
* 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.
|
||||
|
||||
=== Provisionar/actualizar el contenido ===
|
||||
|
||||
|
||||
BIN
doc/manual/es/images/libre-crafts.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
@ -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>/plinth</filename>. This means that Plinth will be
|
||||
available as http://localhost:8000/plinth by default.
|
||||
<filename>/freedombox</filename>. This means that Plinth will be
|
||||
available as http://localhost:8000/freedombox 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 '/plinth' by default, so /myurl is not accessible outside of your
|
||||
to '/freedombox' by default, so /myurl is not accessible outside of your
|
||||
FreedomBox without adapting the apache configuration.
|
||||
</para>
|
||||
</example>
|
||||
|
||||
@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
BASE_URL = 'https://wiki.debian.org/'
|
||||
LOCAL_BASE = '/plinth/help/manual/{lang}/'
|
||||
LOCAL_BASE = '/freedombox/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': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/help/manual/en/Contribute#'
|
||||
|
||||
Translated available page => shortcut to local copy:
|
||||
>>> resolve_url('es/FreedomBox/Contribute', {'language': '', 'title': ''})
|
||||
'/plinth/help/manual/es/Contribute#'
|
||||
'/freedombox/help/manual/es/Contribute#'
|
||||
|
||||
Available page in default language refferred as translated => shortcut to
|
||||
local copy:
|
||||
>>> resolve_url('en/FreedomBox/Contribute', {'language': '', 'title': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/help/manual/en/Contribute#'
|
||||
|
||||
Unrecognized language => handle considering it as default language:
|
||||
>>> resolve_url('missing/FreedomBox/Contribute', {'language': '', \
|
||||
'title': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/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/plinth/ into the browser. <<BR>>\\n\
|
||||
typing https://myfreedombox.rocks/freedombox/ 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/plinth/'), PlainText(' into the \
|
||||
typing '), Url('https://myfreedombox.rocks/freedombox/'), 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="/plinth/help/manual/en/Contribute#">\
|
||||
'<ulink url="/freedombox/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:plinth', \
|
||||
>>> generate_inner_docbook([Link('DebianPkg:freedombox', \
|
||||
[PlainText('Plinth')])])
|
||||
'<ulink url="https://packages.debian.org/plinth#">Plinth</ulink>'
|
||||
'<ulink url="https://packages.debian.org/freedombox#">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="/plinth/help/manual/en/Debian#">install \
|
||||
<ulink url="/freedombox/help/manual/en/Debian#">install \
|
||||
FreedomBox</ulink> on it. </para>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('After Roundcube is \
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
Package init file.
|
||||
"""
|
||||
|
||||
__version__ = '26.3'
|
||||
__version__ = '26.4'
|
||||
|
||||
@ -371,12 +371,13 @@ def get_addresses() -> list[dict[str, str | bool]]:
|
||||
'url_address': hostname
|
||||
})
|
||||
|
||||
# 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
|
||||
# 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.
|
||||
# this correctly.
|
||||
# addresses.append({'kind': '6', 'address': hostname, 'numeric': False})
|
||||
|
||||
return addresses
|
||||
|
||||
@ -398,13 +399,15 @@ def get_ip_addresses() -> list[dict[str, str | bool]]:
|
||||
}
|
||||
|
||||
if address['kind'] == '6' and address['numeric']:
|
||||
if address['scope'] != 'link':
|
||||
address['url_address'] = '[{0}]'.format(address['address'])
|
||||
else:
|
||||
address['url_address'] = '[{0}%{1}]'.format(
|
||||
address['url_address'], address['interface'])
|
||||
address['url_address'] = '[{0}]'.format(address['address'])
|
||||
|
||||
addresses.append(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.
|
||||
addresses.append(address)
|
||||
|
||||
return addresses
|
||||
|
||||
|
||||
@ -12,13 +12,11 @@ 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 = '/plinth'
|
||||
server_dir = '/freedombox'
|
||||
|
||||
# [Network] section
|
||||
host = '127.0.0.1'
|
||||
@ -111,11 +109,9 @@ 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'),
|
||||
|
||||
@ -64,10 +64,10 @@ def fixture_load_cfg():
|
||||
"""Load test configuration."""
|
||||
from plinth import cfg
|
||||
|
||||
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')
|
||||
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')
|
||||
saved_state = {}
|
||||
for key in keys:
|
||||
saved_state[key] = getattr(cfg, key)
|
||||
|
||||
40
plinth/db/dbconfig.py
Normal file
@ -0,0 +1,40 @@
|
||||
# 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
|
||||
@ -1,5 +1,3 @@
|
||||
[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
|
||||
|
||||
@ -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/plinth/
|
||||
https://hosted.weblate.org/projects/freedombox/freedombox/
|
||||
https://www.debian.org/MailingLists/subscribe
|
||||
|
||||
## Wiki: translators landing page
|
||||
|
||||
@ -4,6 +4,7 @@ Common Django middleware.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django import urls
|
||||
from django.conf import settings
|
||||
@ -58,7 +59,9 @@ class SetupMiddleware(MiddlewareMixin):
|
||||
except urls.Resolver404:
|
||||
return
|
||||
|
||||
if not resolver_match.namespaces or not len(resolver_match.namespaces):
|
||||
non_app_namespaces = {'oauth2_provider'}
|
||||
if (not resolver_match.namespaces or not len(resolver_match.namespaces)
|
||||
or (set(resolver_match.namespaces) & non_app_namespaces)):
|
||||
# Requested URL does not belong to any application
|
||||
return
|
||||
|
||||
@ -99,10 +102,17 @@ class AdminRequiredMiddleware(MiddlewareMixin):
|
||||
hasattr(view_func, 'IS_NON_ADMIN'):
|
||||
return
|
||||
|
||||
if not is_user_admin(request):
|
||||
if not AdminRequiredMiddleware.check_user_group(
|
||||
view_func, request):
|
||||
raise PermissionDenied
|
||||
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
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
class FirstSetupMiddleware(MiddlewareMixin):
|
||||
|
||||
@ -1,16 +1,79 @@
|
||||
# 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
|
||||
@ -23,7 +86,7 @@ class ApacheApp(app_module.App):
|
||||
|
||||
app_id = 'apache'
|
||||
|
||||
_version = 14
|
||||
_version = 15
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Create components for the app."""
|
||||
@ -34,11 +97,13 @@ class ApacheApp(app_module.App):
|
||||
self.add(info)
|
||||
|
||||
packages = Packages('packages-apache', [
|
||||
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3'
|
||||
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3',
|
||||
'libapache2-mod-auth-openidc'
|
||||
])
|
||||
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',
|
||||
])
|
||||
@ -59,6 +124,13 @@ 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)
|
||||
|
||||
@ -78,6 +150,43 @@ 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."""
|
||||
|
||||
@ -301,7 +301,19 @@ 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."""
|
||||
command = ['curl', '--location', '-f', '-w', '%{response_code}']
|
||||
# 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}'
|
||||
]
|
||||
|
||||
if kind == '6':
|
||||
# extract zone index
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
## 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>
|
||||
@ -2,12 +2,22 @@
|
||||
"""Configure Apache web server."""
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import urllib.parse
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth.actions import privileged
|
||||
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/')
|
||||
|
||||
|
||||
def _get_sort_key_of_version(version):
|
||||
@ -70,6 +80,8 @@ 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
|
||||
@ -114,6 +126,7 @@ 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')
|
||||
|
||||
@ -135,9 +148,11 @@ def setup(old_version: int):
|
||||
webserver.enable('dav', kind='module')
|
||||
webserver.enable('dav_fs', kind='module')
|
||||
|
||||
# setup freedombox site
|
||||
# setup freedombox configuration
|
||||
webserver.enable('10-freedombox', kind='config')
|
||||
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')
|
||||
@ -151,6 +166,119 @@ 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
|
||||
|
||||
@ -473,7 +473,8 @@ 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', '-f', '-w', '%{response_code}']
|
||||
basic_command = ['curl', '--location', '--cookie', '', '--fail',
|
||||
'--write-out', '%{response_code}']
|
||||
extra_args = {'env': None, 'check': True, 'stdout': -1, 'stderr': -1}
|
||||
|
||||
# Basic
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
URLs for the Apache module.
|
||||
"""
|
||||
"""URLs for the Apache module."""
|
||||
|
||||
urlpatterns: list = []
|
||||
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'),
|
||||
]
|
||||
|
||||
71
plinth/modules/apache/views.py
Normal file
@ -0,0 +1,71 @@
|
||||
# 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}')
|
||||
@ -85,7 +85,8 @@ def test_remote_backup_location(session_browser):
|
||||
|
||||
|
||||
def _assert_main_page_is_shown(session_browser):
|
||||
assert (session_browser.url.endswith('/plinth/'))
|
||||
assert (session_browser.url.endswith('/freedombox/')
|
||||
or session_browser.url.endswith('/plinth/'))
|
||||
|
||||
|
||||
def _backup_download(session_browser, downloaded_file_info, archive_name):
|
||||
@ -126,7 +127,7 @@ def _backup_schedule_get(browser):
|
||||
functional.nav_to_module(browser, 'backups')
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.links.find_by_href(
|
||||
'/plinth/sys/backups/root/schedule/').first.click()
|
||||
'/freedombox/sys/backups/root/schedule/').first.click()
|
||||
|
||||
without_apps = []
|
||||
elements = browser.find_by_name('backups_schedule-selected_apps')
|
||||
@ -158,7 +159,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(
|
||||
'/plinth/sys/backups/root/schedule/').first.click()
|
||||
'/freedombox/sys/backups/root/schedule/').first.click()
|
||||
|
||||
if enable:
|
||||
browser.find_by_name('backups_schedule-enabled').check()
|
||||
@ -192,7 +193,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'/plinth/sys/backups/root/download/{archive_name}/'
|
||||
href = f'/freedombox/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
|
||||
@ -201,22 +202,23 @@ 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('/plinth/').first.click()
|
||||
browser.links.find_by_href('/freedombox/').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('/plinth/sys/backups/upload/').first.click()
|
||||
browser.links.find_by_href(
|
||||
'/freedombox/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='/plinth/sys/backups/'):
|
||||
with functional.wait_for_page_update(
|
||||
browser, expected_url='/freedombox/sys/backups/'):
|
||||
functional.submit(browser, form_class='form-restore')
|
||||
|
||||
|
||||
@ -233,7 +235,7 @@ def _add_remote_backup_location(browser, ssh_use_password=True):
|
||||
_remove_remote_backup_location(browser)
|
||||
|
||||
browser.links.find_by_href(
|
||||
'/plinth/sys/backups/repositories/add-remote/').first.click()
|
||||
'/freedombox/sys/backups/repositories/add-remote/').first.click()
|
||||
browser.find_by_name('repository').fill(REMOTE_PATH)
|
||||
password = functional.get_password(
|
||||
functional.config['DEFAULT']['username'])
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
version="1.1"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="bepasty.svg"
|
||||
inkscape:export-filename="/media/Volume/Dokumente/Inkscape/bepasty.png"
|
||||
inkscape:export-filename="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 |
@ -68,7 +68,7 @@ def _set_default_permissions(browser, permissions=''):
|
||||
|
||||
|
||||
def _add_password(browser):
|
||||
functional.visit(browser, '/plinth/apps/bepasty/add/')
|
||||
functional.visit(browser, '/freedombox/apps/bepasty/add/')
|
||||
for permission in ['read', 'create', 'list', 'delete', 'admin']:
|
||||
browser.find_by_css('#id_bepasty-permissions input[value="{}"]'.format(
|
||||
permission)).check()
|
||||
|
||||
@ -3,8 +3,13 @@
|
||||
##
|
||||
<Location /calibre>
|
||||
ProxyPass http://localhost:8844/calibre
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "calibre"
|
||||
</IfModule>
|
||||
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"
|
||||
</Location>
|
||||
|
||||
@ -60,7 +60,7 @@ def _add_library(browser, name):
|
||||
return
|
||||
|
||||
browser.links.find_by_href(
|
||||
'/plinth/apps/calibre/library/create/').first.click()
|
||||
'/freedombox/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'/plinth/apps/calibre/library/{name}/delete/')
|
||||
f'/freedombox/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'/plinth/apps/calibre/library/{name}/delete/')
|
||||
f'/freedombox/apps/calibre/library/{name}/delete/')
|
||||
return bool(link)
|
||||
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ def home_page_url2scid(url: str | None):
|
||||
|
||||
if url in ('/plinth/', '/plinth', 'plinth', '/freedombox/', '/freedombox',
|
||||
'freedombox'):
|
||||
return 'plinth'
|
||||
return 'freedombox'
|
||||
|
||||
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 = '/plinth/'
|
||||
if shortcut_id == 'plinth':
|
||||
url: str | None = '/freedombox/'
|
||||
if shortcut_id == 'freedombox':
|
||||
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 '/plinth/'
|
||||
return '/freedombox/'
|
||||
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
@ -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'))
|
||||
plinth = ('plinth', _('FreedomBox Service (Plinth)'))
|
||||
return [apache_default, plinth] + uws_choices + shortcut_choices
|
||||
freedombox = ('freedombox', _('FreedomBox Service (Plinth)'))
|
||||
return [apache_default, freedombox] + 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 /plinth or '
|
||||
'/freedombox to reach {box_name} Service (Plinth).'),
|
||||
'(Plinth), your users must explicitly type /freedombox to '
|
||||
'reach {box_name} Service (Plinth).'),
|
||||
box_name=gettext_lazy(cfg.box_name)), required=False,
|
||||
choices=get_homepage_choices)
|
||||
|
||||
|
||||
@ -21,15 +21,18 @@ def test_homepage_mapping():
|
||||
func = home_page_url2scid
|
||||
assert func(None) == 'apache-default'
|
||||
assert func('/unknown/url') is None
|
||||
assert func('/plinth/') == 'plinth'
|
||||
assert func('/plinth') == 'plinth'
|
||||
assert func('plinth') == 'plinth'
|
||||
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('/index.html') == 'apache-default'
|
||||
assert func('/~user') == 'uws-user'
|
||||
assert func('/~user/whatever/else') == 'uws-user'
|
||||
|
||||
func = _home_page_scid2url
|
||||
assert func('plinth') == '/plinth/'
|
||||
assert func('freedombox') == '/freedombox/'
|
||||
assert func('apache-default') is None
|
||||
|
||||
|
||||
@ -63,7 +66,7 @@ def test_homepage_mapping_skip_ci():
|
||||
|
||||
# AC: Return None if it doesn't:
|
||||
os.rmdir(uws_directory)
|
||||
assert _home_page_scid2url(uws_scid) == '/plinth/'
|
||||
assert _home_page_scid2url(uws_scid) == '/freedombox/'
|
||||
|
||||
|
||||
@patch(
|
||||
@ -102,7 +105,7 @@ def test_homepage_field():
|
||||
uws_url = uws_url_of_user(user)
|
||||
uws_scid = home_page_url2scid(uws_url)
|
||||
|
||||
default_home_page = 'plinth'
|
||||
default_home_page = 'freedombox'
|
||||
original_home_page = get_home_page() or default_home_page
|
||||
change_home_page(default_home_page) # Set to known value explicitly
|
||||
|
||||
@ -113,7 +116,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', 'plinth'):
|
||||
for scid in ('b', 'a', uws_scid, 'apache-default', 'freedombox'):
|
||||
change_home_page(scid)
|
||||
assert get_home_page() == scid
|
||||
|
||||
|
||||
@ -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, 'plinth')
|
||||
assert _check_home_page_redirect(session_browser, 'plinth')
|
||||
_set_home_page(session_browser, 'freedombox')
|
||||
assert _check_home_page_redirect(session_browser, 'freedombox')
|
||||
|
||||
|
||||
def _set_home_page(browser, home_page):
|
||||
if 'plinth' not in home_page and 'apache' not in home_page:
|
||||
if 'freedombox' 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, '/plinth/') and @title='FreedomBox']")
|
||||
"//a[contains(@href, '/freedombox/') and @title='FreedomBox']")
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
width="512"
|
||||
height="512"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
inkscape:export-filename="/home/bunny/work/freedombox/plinth/static/themes/default/icons/coturn.png"
|
||||
inkscape:export-filename="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 |
@ -8,10 +8,8 @@
|
||||
##
|
||||
<Location /deluge>
|
||||
ProxyPass http://localhost:8112
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "bit-torrent"
|
||||
</IfModule>
|
||||
Use AuthOpenIDConnect
|
||||
Use RequireGroup bit-torrent
|
||||
## 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
|
||||
|
||||
@ -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="/home/zach/deluge.png"
|
||||
inkscape:export-filename="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 |
@ -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}/plinth/',
|
||||
diagnose_url_on_all('http://{host}/freedombox/',
|
||||
check_certificate=False))
|
||||
|
||||
return results
|
||||
|
||||
@ -112,7 +112,7 @@ def _configure(browser, config):
|
||||
|
||||
functional.nav_to_module(browser, 'dynamicdns')
|
||||
functional.click_link_by_href(browser,
|
||||
'/plinth/sys/dynamicdns/domain/add/')
|
||||
'/freedombox/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'/plinth/sys/dynamicdns/domain/{config["domain"]}/edit/'
|
||||
link = f'/freedombox/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'/plinth/sys/dynamicdns/domain/{domain}/delete/'
|
||||
link = f'/freedombox/sys/dynamicdns/domain/{domain}/delete/'
|
||||
functional.click_link_by_href(browser, link)
|
||||
functional.submit(browser, form_class='form-delete')
|
||||
|
||||
@ -50,7 +50,7 @@ class EjabberdApp(app_module.App):
|
||||
|
||||
app_id = 'ejabberd'
|
||||
|
||||
_version = 9
|
||||
_version = 10
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Create components for the app."""
|
||||
@ -98,8 +98,10 @@ 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=get_domains, daemons=['ejabberd'],
|
||||
'letsencrypt-ejabberd', domains='*', daemons=['ejabberd'],
|
||||
should_copy_certificates=True,
|
||||
private_key_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
|
||||
certificate_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
|
||||
@ -140,11 +142,10 @@ 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)
|
||||
self.get_component('letsencrypt-ejabberd').setup_certificates(
|
||||
[domain_name])
|
||||
privileged.setup(domain_name)
|
||||
# Setup certificates for all domains to keep configuration simple
|
||||
self.get_component('letsencrypt-ejabberd').setup_certificates()
|
||||
privileged.setup()
|
||||
if not old_version:
|
||||
self.enable()
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ def pre_install(domain_name: str):
|
||||
|
||||
|
||||
@privileged
|
||||
def setup(domain_name: str):
|
||||
def setup() -> None:
|
||||
"""Enable LDAP authentication."""
|
||||
with open(EJABBERD_CONFIG, 'r', encoding='utf-8') as file_handle:
|
||||
conf = yaml.load(file_handle)
|
||||
@ -83,10 +83,16 @@ def setup(domain_name: str):
|
||||
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(domain_name)
|
||||
_upgrade_config()
|
||||
|
||||
try:
|
||||
action_utils.run(['ejabberdctl', 'restart'], check=True)
|
||||
@ -95,7 +101,7 @@ def setup(domain_name: str):
|
||||
err)
|
||||
|
||||
|
||||
def _upgrade_config(domain):
|
||||
def _upgrade_config() -> None:
|
||||
"""Fix the config file by removing deprecated settings."""
|
||||
current_version = _get_version()
|
||||
if not current_version:
|
||||
@ -125,13 +131,9 @@ def _upgrade_config(domain):
|
||||
if listen_port['port'] == 5280:
|
||||
listen_port['port'] = 5443
|
||||
|
||||
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
|
||||
# s2s_certfile is deprecated in favor of certfiles
|
||||
if 's2s_certfile' in conf:
|
||||
conf.pop('s2s_certfile')
|
||||
|
||||
# Write changes back to the file
|
||||
with open(EJABBERD_CONFIG, 'w', encoding='utf-8') as file_handle:
|
||||
|
||||
@ -118,7 +118,7 @@ def _jsxc_login(browser):
|
||||
"""Login to JSXC."""
|
||||
username = functional.config['DEFAULT']['username']
|
||||
password = functional.config['DEFAULT']['password']
|
||||
functional.visit(browser, '/plinth/apps/jsxc/jsxc/')
|
||||
functional.visit(browser, '/freedombox/apps/jsxc/jsxc/')
|
||||
assert functional.eventually(browser.find_by_text,
|
||||
['BOSH Server reachable.'])
|
||||
if browser.find_by_text('relogin'):
|
||||
|
||||
@ -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="/plinth/apps/roundcube/">Roundcube app</a> provides web '
|
||||
_('<a href="/freedombox/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.')
|
||||
|
||||
@ -14,10 +14,7 @@
|
||||
ProxyAddHeaders off
|
||||
|
||||
# Require SSO
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin"
|
||||
</IfModule>
|
||||
Use AuthOpenIDConnect
|
||||
</Location>
|
||||
|
||||
# Automatic configuration for clients like Thunderbird:
|
||||
@ -25,12 +22,12 @@
|
||||
<Location "/.well-known/autoconfig/mail/config-v1.1.xml">
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule ^ /plinth/apps/email/config.xml [PT]
|
||||
RewriteRule ^ /freedombox/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 /plinth/apps/roundcube/
|
||||
ErrorDocument 404 /freedombox/apps/roundcube/
|
||||
</Location>
|
||||
|
||||
@ -5,10 +5,8 @@
|
||||
Alias /featherwiki /var/lib/featherwiki
|
||||
|
||||
<Location /featherwiki>
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "wiki"
|
||||
</IfModule>
|
||||
Use AuthOpenIDConnect
|
||||
Use RequireGroup wiki
|
||||
|
||||
# Disable caching
|
||||
<IfModule mod_headers.c>
|
||||
|
||||