diff --git a/authentik/sources/kerberos/models.py b/authentik/sources/kerberos/models.py index 61960f0b74af..c209bb653d18 100644 --- a/authentik/sources/kerberos/models.py +++ b/authentik/sources/kerberos/models.py @@ -18,7 +18,6 @@ from structlog.stdlib import get_logger from authentik.core.models import ( - USER_PATH_SERVICE_ACCOUNT, GroupSourceConnection, PropertyMapping, Source, @@ -164,33 +163,13 @@ def sync_lock(self) -> pglock.advisory: ) def get_base_user_properties(self, principal: str, **kwargs): - localpart, realm = principal.rsplit("@", 1) - is_service_account = "/" in localpart - username = localpart - - # By default, don't sync system principals - denied_prefixes = ["kadmin/", "krbtgt/", "K/M", "WELLKNOWN/"] - for prefix in denied_prefixes: - if username.lower().startswith(prefix.lower()): - username = None - break - # By default, don't sync principals from another realm - if realm.upper() != self.realm.upper(): - username = None - - properties = { - "username": username, + localpart, _ = principal.rsplit("@", 1) + + return { + "username": localpart, "type": UserTypes.INTERNAL, "path": self.get_user_path(), } - if is_service_account: - properties.update( - { - "type": UserTypes.SERVICE_ACCOUNT, - "path": USER_PATH_SERVICE_ACCOUNT, - } - ) - return properties def get_base_group_properties(self, group_id: str, **kwargs): return { @@ -346,7 +325,7 @@ class KerberosSourcePropertyMapping(PropertyMapping): @property def component(self) -> str: - return "ak-property-mapping-kerberos-form" + return "ak-property-mapping-source-kerberos-form" @property def serializer(self) -> type[Serializer]: diff --git a/authentik/sources/kerberos/signals.py b/authentik/sources/kerberos/signals.py index d240eb126902..42531ccec3db 100644 --- a/authentik/sources/kerberos/signals.py +++ b/authentik/sources/kerberos/signals.py @@ -33,7 +33,12 @@ def kerberos_sync_password(sender, user: User, password: str, **_): """Connect to kerberos and update password.""" user_source_connections = UserKerberosSourceConnection.objects.select_related( "source__kerberossource" - ).filter(user=user, source__enabled=True, source__kerberossource__sync_users=True, source__kerberossource__sync_users_password=True) + ).filter( + user=user, + source__enabled=True, + source__kerberossource__sync_users=True, + source__kerberossource__sync_users_password=True, + ) for user_source_connection in user_source_connections: source = user_source_connection.source.kerberossource with Krb5ConfContext(source): diff --git a/authentik/sources/kerberos/tests/test_auth.py b/authentik/sources/kerberos/tests/test_auth.py index e11555a7084d..72db23e7192a 100644 --- a/authentik/sources/kerberos/tests/test_auth.py +++ b/authentik/sources/kerberos/tests/test_auth.py @@ -1,6 +1,7 @@ """Kerberos Source Auth tests""" from django.contrib.auth.hashers import is_password_usable + from authentik.core.models import User from authentik.lib.generators import generate_id from authentik.sources.kerberos.auth import KerberosBackend diff --git a/authentik/sources/kerberos/tests/test_sync.py b/authentik/sources/kerberos/tests/test_sync.py index 2e8ebd37e8dd..7123c0d68fa2 100644 --- a/authentik/sources/kerberos/tests/test_sync.py +++ b/authentik/sources/kerberos/tests/test_sync.py @@ -1,8 +1,9 @@ """Kerberos Source sync tests""" from authentik.core.models import User +from authentik.blueprints.tests import apply_blueprint from authentik.lib.generators import generate_id -from authentik.sources.kerberos.models import KerberosSourcePropertyMapping, KerberosSource +from authentik.sources.kerberos.models import KerberosSource, KerberosSourcePropertyMapping from authentik.sources.kerberos.sync import KerberosSync from authentik.sources.kerberos.tasks import kerberos_sync_all from authentik.sources.kerberos.tests.utils import KerberosTestCase @@ -11,6 +12,7 @@ class TestKerberosSync(KerberosTestCase): """Kerberos Sync tests""" + @apply_blueprint("system/sources-kerberos.yaml") def setUp(self): self.source: KerberosSource = KerberosSource.objects.create( name="kerberos", @@ -21,10 +23,24 @@ def setUp(self): sync_principal=self.realm.admin_princ, sync_password=self.realm.password("admin"), ) + self.source.user_property_mappings.set(KerberosSourcePropertyMapping.objects.filter(managed__startswith="goauthentik.io/sources/kerberos/user/default/")) + + def test_default_mappings(self): + """Test default mappings""" + KerberosSync(self.source).sync() + + self.assertTrue( + User.objects.filter(username=self.realm.user_princ.rsplit("@", 1)[0]).exists() + ) + self.assertFalse( + User.objects.filter(username=self.realm.nfs_princ.rsplit("@", 1)[0]).exists() + ) def test_sync_mapping(self): """Test property mappings""" - noop = KerberosSourcePropertyMapping.objects.create(name=generate_id(), expression="return {}") + noop = KerberosSourcePropertyMapping.objects.create( + name=generate_id(), expression="return {}" + ) email = KerberosSourcePropertyMapping.objects.create( name=generate_id(), expression='return {"email": principal.lower()}' ) diff --git a/blueprints/schema.json b/blueprints/schema.json index 093ed97b56bd..15699da1efa0 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -6892,11 +6892,6 @@ "title": "Spnego ccache", "description": "Credential cache to use for SPNEGO in form type:residual" }, - "password_login_enabled": { - "type": "boolean", - "title": "Password login enabled", - "description": "Enable the passwword authentication backend" - }, "password_login_update_internal_password": { "type": "boolean", "title": "Password login update internal password", diff --git a/blueprints/system/sources-kerberos.yaml b/blueprints/system/sources-kerberos.yaml new file mode 100644 index 000000000000..22e50e0e5a0b --- /dev/null +++ b/blueprints/system/sources-kerberos.yaml @@ -0,0 +1,58 @@ +version: 1 +metadata: + labels: + blueprints.goauthentik.io/system: "true" + name: System - Kerberos Source - Mappings +entries: + - identifiers: + managed: goauthentik.io/sources/kerberos/user/default/multipart-principals-as-service-accounts + model: authentik_sources_kerberos.kerberossourcepropertymapping + attrs: + name: "authentik default Kerberos User Mapping: Multipart principals as service accounts" + expression: | + from authentik.core.models import USER_PATH_SERVICE_ACCOUNT, UserTypes + + localpart, _ = principal.rsplit("@", 1) + is_service_account = "/" in localpart + attrs = {} + if is_service_account: + attrs = { + "type": UserTypes.SERVICE_ACCOUNT, + "path": USER_PATH_SERVICE_ACCOUNT, + } + return attrs + - identifiers: + managed: goauthentik.io/sources/kerberos/user/default/realm-as-group + model: authentik_sources_kerberos.kerberossourcepropertymapping + attrs: + name: "authentik default Kerberos User Mapping: Add realm as group" + expression: | + localpart, realm = principal.rsplit("@", 1) + return { + "groups": [realm.upper()] + } + - identifiers: + managed: goauthentik.io/sources/kerberos/user/default/ignore-other-realms + model: authentik_sources_kerberos.kerberossourcepropertymapping + attrs: + name: "authentik default Kerberos User Mapping: Ignore other realms" + expression: | + localpart, realm = principal.rsplit("@", 1) + attrs = {} + if realm.upper() != source.realm.upper(): + attrs["username"] = None + return attrs + - identifiers: + managed: goauthentik.io/sources/kerberos/user/default/ignore-system-principals + model: authentik_sources_kerberos.kerberossourcepropertymapping + attrs: + name: "authentik default Kerberos User Mapping: Ignore system principals" + expression: | + localpart, realm = principal.rsplit("@", 1) + attrs = {} + denied_prefixes = ["kadmin/", "krbtgt/", "K/M", "WELLKNOWN/"] + for prefix in denied_prefixes: + if localpart.lower().startswith(prefix.lower()): + attrs["username"] = None + break + return attrs diff --git a/schema.yml b/schema.yml index 6044c29f7f0f..c13c0b6281b9 100644 --- a/schema.yml +++ b/schema.yml @@ -25440,10 +25440,6 @@ paths: description: Number of results to return per page. schema: type: integer - - in: query - name: password_login_enabled - schema: - type: boolean - in: query name: password_login_update_internal_password schema: @@ -42035,10 +42031,6 @@ components: type: string icon: type: string - nullable: true - description: |- - Get the URL to the Icon. If the name is /static or - starts with http it is returned as-is readOnly: true group_matching_mode: allOf: @@ -42077,9 +42069,6 @@ components: spnego_ccache: type: string description: Credential cache to use for SPNEGO in form type:residual - password_login_enabled: - type: boolean - description: Enable the passwword authentication backend password_login_update_internal_password: type: boolean description: If enabled, the authentik-stored password will be updated upon @@ -42254,9 +42243,6 @@ components: spnego_ccache: type: string description: Credential cache to use for SPNEGO in form type:residual - password_login_enabled: - type: boolean - description: Enable the passwword authentication backend password_login_update_internal_password: type: boolean description: If enabled, the authentik-stored password will be updated upon @@ -47459,9 +47445,6 @@ components: spnego_ccache: type: string description: Credential cache to use for SPNEGO in form type:residual - password_login_enabled: - type: boolean - description: Enable the passwword authentication backend password_login_update_internal_password: type: boolean description: If enabled, the authentik-stored password will be updated upon diff --git a/web/src/admin/sources/kerberos/KerberosSourceConnectivity.ts b/web/src/admin/sources/kerberos/KerberosSourceConnectivity.ts index d1aed81817e6..84eba9e4ca37 100644 --- a/web/src/admin/sources/kerberos/KerberosSourceConnectivity.ts +++ b/web/src/admin/sources/kerberos/KerberosSourceConnectivity.ts @@ -27,13 +27,7 @@ export class KerberosSourceConnectivity extends AKElement { } return html``; } diff --git a/web/src/admin/sources/kerberos/KerberosSourceForm.ts b/web/src/admin/sources/kerberos/KerberosSourceForm.ts index c6e3ca72c05a..158b945cac47 100644 --- a/web/src/admin/sources/kerberos/KerberosSourceForm.ts +++ b/web/src/admin/sources/kerberos/KerberosSourceForm.ts @@ -51,11 +51,13 @@ async function propertyMappingsProvider(page = 1, search = "") { }; } -function makePropertyMappingsSelector(instanceMappings?: string[]) { +function makePropertyMappingsSelector(object: string, instanceMappings?: string[]) { const localMappings = instanceMappings ? new Set(instanceMappings) : undefined; return localMappings ? ([pk, _]: DualSelectPair) => localMappings.has(pk) - : ([_0, _1, _2, _mapping]: DualSelectPair) => false; + : ([_0, _1, _2, mapping]: DualSelectPair) => + object == "user" && + mapping?.managed?.startsWith("goauthentik.io/sources/kerberos/user/default/"); } @customElement("ak-source-kerberos-form") @@ -122,12 +124,6 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm - @@ -167,79 +163,79 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm + + + + + + - - - - - - ${msg("Sync connection settings")} @@ -254,7 +250,7 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm @@ -327,6 +323,7 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm +
+

${msg("Connectivity")}

+
+
+ +
+ +
+ { + return new SourcesApi(DEFAULT_CONFIG).sourcesKerberosSyncStatusRetrieve({ + slug: this.source?.slug, + }); + }} + .triggerSync=${() => { + return new SourcesApi(DEFAULT_CONFIG).sourcesKerberosPartialUpdate({ + slug: this.source?.slug || "", + patchedKerberosSourceRequest: {}, + }); + }} + > +
+ `; + } + render(): TemplateResult { if (!this.source) { return html``; @@ -131,33 +164,7 @@ export class KerberosSourceViewPage extends AKElement { -
-
-

${msg("Connectivity")}

-
-
- -
-
-
- { - return new SourcesApi( - DEFAULT_CONFIG, - ).sourcesKerberosSyncStatusRetrieve({ - slug: this.source?.slug, - }); - }} - .triggerSync=${() => { - return new SourcesApi(DEFAULT_CONFIG).sourcesKerberosPartialUpdate({ - slug: this.source?.slug || "", - patchedKerberosSourceRequest: {}, - }); - }} - > -
+ ${this.renderSyncCards()}