diff --git a/authentik/blueprints/management/commands/apply_blueprint.py b/authentik/blueprints/management/commands/apply_blueprint.py index acc9ffbec..a35b092fe 100644 --- a/authentik/blueprints/management/commands/apply_blueprint.py +++ b/authentik/blueprints/management/commands/apply_blueprint.py @@ -3,6 +3,7 @@ from sys import exit as sys_exit from django.core.management.base import BaseCommand, no_translations from structlog.stdlib import get_logger +from tenant_schemas_celery.scheduler import Tenant from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.v1.importer import Importer @@ -16,14 +17,16 @@ class Command(BaseCommand): @no_translations def handle(self, *args, **options): """Apply all blueprints in order, abort when one fails to import""" - for blueprint_path in options.get("blueprints", []): - content = BlueprintInstance(path=blueprint_path).retrieve() - importer = Importer.from_string(content) - valid, _ = importer.validate() - if not valid: - self.stderr.write("blueprint invalid") - sys_exit(1) - importer.apply() + for tenant in Tenant.objects.all(): + with tenant: + for blueprint_path in options.get("blueprints", []): + content = BlueprintInstance(path=blueprint_path).retrieve() + importer = Importer.from_string(content) + valid, _ = importer.validate() + if not valid: + self.stderr.write("blueprint invalid") + sys_exit(1) + importer.apply() def add_arguments(self, parser): parser.add_argument("blueprints", nargs="+", type=str) diff --git a/authentik/blueprints/management/commands/export_blueprint.py b/authentik/blueprints/management/commands/export_blueprint.py index d4b29304a..b8194d0c5 100644 --- a/authentik/blueprints/management/commands/export_blueprint.py +++ b/authentik/blueprints/management/commands/export_blueprint.py @@ -1,5 +1,6 @@ """Export blueprint of current authentik install""" from django.core.management.base import BaseCommand, no_translations +from django_tenants.management.commands import TenantWrappedCommand from structlog.stdlib import get_logger from authentik.blueprints.v1.exporter import Exporter @@ -7,7 +8,7 @@ from authentik.blueprints.v1.exporter import Exporter LOGGER = get_logger() -class Command(BaseCommand): +class TCommand(BaseCommand): """Export blueprint of current authentik install""" @no_translations @@ -15,3 +16,7 @@ class Command(BaseCommand): """Export blueprint of current authentik install""" exporter = Exporter() self.stdout.write(exporter.export_to_string()) + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/core/management/commands/repair_permissions.py b/authentik/core/management/commands/repair_permissions.py index 242aef45a..c58167799 100644 --- a/authentik/core/management/commands/repair_permissions.py +++ b/authentik/core/management/commands/repair_permissions.py @@ -4,6 +4,8 @@ from django.contrib.auth.management import create_permissions from django.core.management.base import BaseCommand, no_translations from guardian.management import create_anonymous_user +from authentik.tenants.models import Tenant + class Command(BaseCommand): """Repair missing permissions""" @@ -11,7 +13,9 @@ class Command(BaseCommand): @no_translations def handle(self, *args, **options): """Check permissions for all apps""" - for app in apps.get_app_configs(): - self.stdout.write(f"Checking app {app.name} ({app.label})\n") - create_permissions(app, verbosity=0) - create_anonymous_user(None, using="default") + for tenant in Tenant.objects.all(): + with tenant: + for app in apps.get_app_configs(): + self.stdout.write(f"Checking app {app.name} ({app.label})\n") + create_permissions(app, verbosity=0) + create_anonymous_user(None, using="default") diff --git a/authentik/crypto/management/commands/import_certificate.py b/authentik/crypto/management/commands/import_certificate.py index 66761d903..003acfac3 100644 --- a/authentik/crypto/management/commands/import_certificate.py +++ b/authentik/crypto/management/commands/import_certificate.py @@ -2,6 +2,7 @@ from sys import exit as sys_exit from django.core.management.base import BaseCommand, no_translations +from django_tenants.management.commands import TenantWrappedCommand from rest_framework.exceptions import ValidationError from structlog.stdlib import get_logger @@ -11,7 +12,7 @@ from authentik.crypto.models import CertificateKeyPair LOGGER = get_logger() -class Command(BaseCommand): +class TCommand(BaseCommand): """Import certificate""" @no_translations @@ -49,3 +50,7 @@ class Command(BaseCommand): parser.add_argument("--certificate", type=str, required=True) parser.add_argument("--private-key", type=str, required=False) parser.add_argument("--name", type=str, required=True) + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/providers/scim/management/commands/scim_sync.py b/authentik/providers/scim/management/commands/scim_sync.py index 0fbca3dce..103a02887 100644 --- a/authentik/providers/scim/management/commands/scim_sync.py +++ b/authentik/providers/scim/management/commands/scim_sync.py @@ -1,5 +1,6 @@ """SCIM Sync""" from django.core.management.base import BaseCommand +from django_tenants.management.commands import TenantWrappedCommand from structlog.stdlib import get_logger from authentik.providers.scim.models import SCIMProvider @@ -8,7 +9,7 @@ from authentik.providers.scim.tasks import scim_sync LOGGER = get_logger() -class Command(BaseCommand): +class TCommand(BaseCommand): """Run sync for an SCIM Provider""" def add_arguments(self, parser): @@ -21,3 +22,7 @@ class Command(BaseCommand): LOGGER.warning("Provider does not exist", name=provider_name) continue scim_sync.delay(provider.pk).get() + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/recovery/management/commands/create_admin_group.py b/authentik/recovery/management/commands/create_admin_group.py index e5e9e2f5b..c0e9017b5 100644 --- a/authentik/recovery/management/commands/create_admin_group.py +++ b/authentik/recovery/management/commands/create_admin_group.py @@ -1,11 +1,12 @@ """authentik recovery create_admin_group""" from django.core.management.base import BaseCommand from django.utils.translation import gettext as _ +from django_tenants.management.commands import TenantWrappedCommand from authentik.core.models import Group, User -class Command(BaseCommand): +class TCommand(BaseCommand): """Create admin group if the default group gets deleted""" help = _("Create admin group if the default group gets deleted.") @@ -28,3 +29,7 @@ class Command(BaseCommand): ) group.users.add(user) self.stdout.write(f"User '{username}' successfully added to the group 'authentik Admins'.") + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/recovery/management/commands/create_recovery_key.py b/authentik/recovery/management/commands/create_recovery_key.py index 1b768fd73..9bd866bd4 100644 --- a/authentik/recovery/management/commands/create_recovery_key.py +++ b/authentik/recovery/management/commands/create_recovery_key.py @@ -7,11 +7,12 @@ from django.urls import reverse from django.utils.text import slugify from django.utils.timezone import now from django.utils.translation import gettext as _ +from django_tenants.management.commands import TenantWrappedCommand from authentik.core.models import Token, TokenIntents, User -class Command(BaseCommand): +class TCommand(BaseCommand): """Create Token used to recover access""" help = _("Create a Key which can be used to restore access to authentik.") @@ -50,3 +51,7 @@ class Command(BaseCommand): f"Store this link safely, as it will allow anyone to access authentik as {user}." ) self.stdout.write(self.get_url(token)) + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/recovery/tests.py b/authentik/recovery/tests.py index ad53c6c5a..0d886a5d7 100644 --- a/authentik/recovery/tests.py +++ b/authentik/recovery/tests.py @@ -4,6 +4,7 @@ from io import StringIO from django.core.management import call_command from django.test import TestCase from django.urls import reverse +from django_tenants.utils import get_public_schema_name from authentik.core.models import Token, TokenIntents, User @@ -18,7 +19,13 @@ class TestRecovery(TestCase): """Test creation of a new key""" out = StringIO() self.assertEqual(len(Token.objects.all()), 0) - call_command("create_recovery_key", "1", self.user.username, stdout=out) + call_command( + "create_recovery_key", + "1", + self.user.username, + schema=get_public_schema_name(), + stdout=out, + ) token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user) self.assertIn(token.key, out.getvalue()) self.assertEqual(len(Token.objects.all()), 1) @@ -27,13 +34,19 @@ class TestRecovery(TestCase): """Test creation of a new key (invalid)""" out = StringIO() self.assertEqual(len(Token.objects.all()), 0) - call_command("create_recovery_key", "1", "foo", stderr=out) + call_command("create_recovery_key", "1", "foo", schema=get_public_schema_name(), stderr=out) self.assertIn("not found", out.getvalue()) def test_recovery_view(self): """Test recovery view""" out = StringIO() - call_command("create_recovery_key", "1", self.user.username, stdout=out) + call_command( + "create_recovery_key", + "1", + self.user.username, + schema=get_public_schema_name(), + stdout=out, + ) token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user) self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": token.key})) self.assertEqual(int(self.client.session["_auth_user_id"]), token.user.pk) @@ -46,12 +59,14 @@ class TestRecovery(TestCase): def test_recovery_admin_group_invalid(self): """Test creation of admin group""" out = StringIO() - call_command("create_admin_group", "1", stderr=out) + call_command("create_admin_group", "1", schema=get_public_schema_name(), stderr=out) self.assertIn("not found", out.getvalue()) def test_recovery_admin_group(self): """Test creation of admin group""" out = StringIO() - call_command("create_admin_group", self.user.username, stdout=out) + call_command( + "create_admin_group", self.user.username, schema=get_public_schema_name(), stdout=out + ) self.assertIn("successfully added to", out.getvalue()) self.assertTrue(self.user.is_superuser) diff --git a/authentik/sources/ldap/management/commands/ldap_check_connection.py b/authentik/sources/ldap/management/commands/ldap_check_connection.py index 6da316aa4..3b13aa300 100644 --- a/authentik/sources/ldap/management/commands/ldap_check_connection.py +++ b/authentik/sources/ldap/management/commands/ldap_check_connection.py @@ -2,6 +2,7 @@ from json import dumps from django.core.management.base import BaseCommand +from django_tenants.management.commands import TenantWrappedCommand from structlog.stdlib import get_logger from authentik.sources.ldap.models import LDAPSource @@ -9,7 +10,7 @@ from authentik.sources.ldap.models import LDAPSource LOGGER = get_logger() -class Command(BaseCommand): +class TCommand(BaseCommand): """Check connectivity to LDAP servers for a source""" def add_arguments(self, parser): @@ -22,3 +23,7 @@ class Command(BaseCommand): for source in sources.order_by("slug"): status = source.check_connection() self.stdout.write(dumps(status, indent=4)) + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/sources/ldap/management/commands/ldap_sync.py b/authentik/sources/ldap/management/commands/ldap_sync.py index eac5a32ef..05fc89798 100644 --- a/authentik/sources/ldap/management/commands/ldap_sync.py +++ b/authentik/sources/ldap/management/commands/ldap_sync.py @@ -1,5 +1,6 @@ """LDAP Sync""" from django.core.management.base import BaseCommand +from django_tenants.management.commands import TenantWrappedCommand from structlog.stdlib import get_logger from authentik.sources.ldap.models import LDAPSource @@ -11,7 +12,7 @@ from authentik.sources.ldap.tasks import ldap_sync_paginator LOGGER = get_logger() -class Command(BaseCommand): +class TCommand(BaseCommand): """Run sync for an LDAP Source""" def add_arguments(self, parser): @@ -30,3 +31,7 @@ class Command(BaseCommand): ) for task in tasks: task() + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/authentik/stages/email/management/commands/test_email.py b/authentik/stages/email/management/commands/test_email.py index 3e1a17fa7..e4934bc1d 100644 --- a/authentik/stages/email/management/commands/test_email.py +++ b/authentik/stages/email/management/commands/test_email.py @@ -2,13 +2,14 @@ from uuid import uuid4 from django.core.management.base import BaseCommand, no_translations +from django_tenants.management.commands import TenantWrappedCommand from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mail from authentik.stages.email.utils import TemplateEmailMessage -class Command(BaseCommand): +class TCommand(BaseCommand): """Send a test-email with global settings""" @no_translations @@ -40,4 +41,8 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument("to", type=str) - parser.add_argument("-s", "--stage", type=str) + parser.add_argument("-S", "--stage", type=str) + + +class Command(TenantWrappedCommand): + COMMAND = TCommand diff --git a/website/developer-docs/blueprints/export.md b/website/developer-docs/blueprints/export.md index fa0203197..94daed668 100644 --- a/website/developer-docs/blueprints/export.md +++ b/website/developer-docs/blueprints/export.md @@ -8,7 +8,7 @@ title: Export Requires authentik 2022.8.2 ::: -To migrate existing configurations to blueprints, run `ak export_blueprint` within any authentik Worker container. This will output a blueprint for most currently created objects. Some objects will not be exported as they might have dependencies on other things. +To migrate existing configurations to blueprints, run `ak export_blueprint --schema public` within any authentik Worker container. This will output a blueprint for most currently created objects. Some objects will not be exported as they might have dependencies on other things. Exported blueprints don't use any of the YAML Tags, they just contain a list of entries as they are in the database. diff --git a/website/docs/core/certificates.md b/website/docs/core/certificates.md index e41763626..a1550e8a3 100644 --- a/website/docs/core/certificates.md +++ b/website/docs/core/certificates.md @@ -62,9 +62,9 @@ Files are checked every 5 minutes, and will trigger an Outpost refresh if the fi Starting with authentik 2022.9, you can also import certificates with any folder structure directly. To do this, run the following command within the worker container: ```shell -ak import_certificate --certificate /certs/mycert.pem --private-key /certs/something.pem --name test +ak import_certificate --schema public --certificate /certs/mycert.pem --private-key /certs/something.pem --name test # --private-key can be omitted to only import a certificate, i.e. to trust other connections -# ak import_certificate --certificate /certs/othercert.pem --name test2 +# ak import_certificate --schema public --certificate /certs/othercert.pem --name test2 ``` This will import the certificate into authentik under the given name. This command is idempotent, meaning you can run it via a cron-job and authentik will only update the certificate when it changes. diff --git a/website/docs/troubleshooting/emails.md b/website/docs/troubleshooting/emails.md index cc23b1437..ce5aaa475 100644 --- a/website/docs/troubleshooting/emails.md +++ b/website/docs/troubleshooting/emails.md @@ -9,19 +9,19 @@ Some hosting providers block outgoing SMTP ports, in which case you'll have to h To test if an email stage, or the global email settings are configured correctly, you can run the following command: ``` -ak test_email [-s ] +ak test_email --schema public [-S ] ``` -If you omit the `-s` parameter, the email will be sent using the global settings. Otherwise, the settings of the specified stage will be used. +If you omit the `-S` parameter, the email will be sent using the global settings. Otherwise, the settings of the specified stage will be used. To run this command with docker-compose, use ``` -docker-compose exec worker ak test_email [...] +docker-compose exec worker ak test_email --schema public [...] ``` To run this command with Kubernetes, use ``` -kubectl exec -it deployment/authentik-worker -c authentik -- ak test_email [...] +kubectl exec -it deployment/authentik-worker -c authentik -- ak test_email --schema public [...] ``` diff --git a/website/docs/troubleshooting/ldap_source.md b/website/docs/troubleshooting/ldap_source.md index ee3f843ef..b00d5adae 100644 --- a/website/docs/troubleshooting/ldap_source.md +++ b/website/docs/troubleshooting/ldap_source.md @@ -5,23 +5,23 @@ title: Troubleshooting LDAP Synchronization To troubleshoot LDAP sources, you can run the command below to run a synchronization in the foreground and see any errors or warnings that might happen directly ``` -docker-compose run --rm worker ldap_sync *slug of the source* +docker-compose run --rm worker ldap_sync --schema public *slug of the source* ``` or, for Kubernetes, run ``` -kubectl exec -it deployment/authentik-worker -c authentik -- ak ldap_sync *slug of the source* +kubectl exec -it deployment/authentik-worker -c authentik -- ak ldap_sync --schema public *slug of the source* ``` Starting with authentik 2023.10, you can also run command below to explicitly check the connectivity to the configured LDAP Servers: ``` -docker-compose run --rm worker ldap_check_connection *slug of the source* +docker-compose run --rm worker ldap_check_connection --schema public *slug of the source* ``` or, for Kubernetes, run ``` -kubectl exec -it deployment/authentik-worker -c authentik -- ak ldap_check_connection *slug of the source* +kubectl exec -it deployment/authentik-worker -c authentik -- ak ldap_check_connection --schema public *slug of the source* ``` diff --git a/website/docs/troubleshooting/login.md b/website/docs/troubleshooting/login.md index 58930706d..ffb82137f 100644 --- a/website/docs/troubleshooting/login.md +++ b/website/docs/troubleshooting/login.md @@ -11,19 +11,19 @@ This recovery key will give whoever has the link direct access to your instances To create the key, run the following command: ``` -docker-compose run --rm server create_recovery_key 10 akadmin +docker-compose run --rm server create_recovery_key --schema public 10 akadmin ``` For Kubernetes, run ``` -kubectl exec -it deployment/authentik-worker -c authentik -- ak create_recovery_key 10 akadmin +kubectl exec -it deployment/authentik-worker -c authentik -- ak create_recovery_key --schema public 10 akadmin ``` or, for CLI, run ``` -ak create_recovery_key 10 akadmin +ak create_recovery_key 10 --schema public akadmin ``` This will output a link, that can be used to instantly gain access to authentik as the user specified above. The link is valid for amount of years specified above, in this case, 10 years. diff --git a/website/docs/troubleshooting/missing_admin_group.md b/website/docs/troubleshooting/missing_admin_group.md index a1e612dfd..2019b463a 100644 --- a/website/docs/troubleshooting/missing_admin_group.md +++ b/website/docs/troubleshooting/missing_admin_group.md @@ -7,11 +7,11 @@ If all of the Admin groups have been deleted, or misconfigured during sync, you Run the following command, where _username_ is the user you want to add to the newly created group: ``` -docker-compose run --rm server create_admin_group username +docker-compose run --rm server create_admin_group --schema public username ``` or, for Kubernetes, run ``` -kubectl exec -it deployment/authentik-worker -c authentik -- ak create_admin_group username +kubectl exec -it deployment/authentik-worker -c authentik -- ak create_admin_group --schema public username ```