From f89479caf303298720c9bca8c535b1b230f20a60 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 26 Apr 2021 11:52:42 +0200 Subject: [PATCH] providers/ldap: add LDAP provider Signed-off-by: Jens Langhammer --- authentik/api/v2/urls.py | 3 + .../migrations/0016_alter_outpost_type.py | 20 + .../0013_alter_eventmatcherpolicy_app.py | 84 ++++ authentik/providers/ldap/__init__.py | 0 authentik/providers/ldap/api.py | 47 +++ authentik/providers/ldap/apps.py | 10 + .../providers/ldap/migrations/0001_initial.py | 55 +++ .../providers/ldap/migrations/__init__.py | 0 authentik/providers/ldap/models.py | 51 +++ authentik/providers/oauth2/apps.py | 4 +- authentik/root/settings.py | 1 + swagger.yaml | 380 ++++++++++++++++++ 12 files changed, 653 insertions(+), 2 deletions(-) create mode 100644 authentik/outposts/migrations/0016_alter_outpost_type.py create mode 100644 authentik/policies/event_matcher/migrations/0013_alter_eventmatcherpolicy_app.py create mode 100644 authentik/providers/ldap/__init__.py create mode 100644 authentik/providers/ldap/api.py create mode 100644 authentik/providers/ldap/apps.py create mode 100644 authentik/providers/ldap/migrations/0001_initial.py create mode 100644 authentik/providers/ldap/migrations/__init__.py create mode 100644 authentik/providers/ldap/models.py diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py index 0b71a2857..40657ca28 100644 --- a/authentik/api/v2/urls.py +++ b/authentik/api/v2/urls.py @@ -47,6 +47,7 @@ from authentik.policies.reputation.api import ( ReputationPolicyViewSet, UserReputationViewSet, ) +from authentik.providers.ldap.api import LDAPOutpostConfigViewSet, LDAPProviderViewSet from authentik.providers.oauth2.api.provider import OAuth2ProviderViewSet from authentik.providers.oauth2.api.scope import ScopeMappingViewSet from authentik.providers.oauth2.api.tokens import ( @@ -120,6 +121,7 @@ router.register( "outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet ) router.register("outposts/proxy", ProxyOutpostConfigViewSet) +router.register("outposts/ldap", LDAPOutpostConfigViewSet) router.register("flows/instances", FlowViewSet) router.register("flows/bindings", FlowStageBindingViewSet) @@ -149,6 +151,7 @@ router.register("policies/reputation/ips", IPReputationViewSet) router.register("policies/reputation", ReputationPolicyViewSet) router.register("providers/all", ProviderViewSet) +router.register("providers/ldap", LDAPProviderViewSet) router.register("providers/proxy", ProxyProviderViewSet) router.register("providers/oauth2", OAuth2ProviderViewSet) router.register("providers/saml", SAMLProviderViewSet) diff --git a/authentik/outposts/migrations/0016_alter_outpost_type.py b/authentik/outposts/migrations/0016_alter_outpost_type.py new file mode 100644 index 000000000..966b86997 --- /dev/null +++ b/authentik/outposts/migrations/0016_alter_outpost_type.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-04-26 09:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_outposts", "0015_auto_20201224_1206"), + ] + + operations = [ + migrations.AlterField( + model_name="outpost", + name="type", + field=models.TextField( + choices=[("proxy", "Proxy"), ("ldap", "Ldap")], default="proxy" + ), + ), + ] diff --git a/authentik/policies/event_matcher/migrations/0013_alter_eventmatcherpolicy_app.py b/authentik/policies/event_matcher/migrations/0013_alter_eventmatcherpolicy_app.py new file mode 100644 index 000000000..7739bd823 --- /dev/null +++ b/authentik/policies/event_matcher/migrations/0013_alter_eventmatcherpolicy_app.py @@ -0,0 +1,84 @@ +# Generated by Django 3.2 on 2021-04-26 09:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_policies_event_matcher", "0012_auto_20210323_1339"), + ] + + operations = [ + migrations.AlterField( + model_name="eventmatcherpolicy", + name="app", + field=models.TextField( + blank=True, + choices=[ + ("authentik.admin", "authentik Admin"), + ("authentik.api", "authentik API"), + ("authentik.events", "authentik Events"), + ("authentik.crypto", "authentik Crypto"), + ("authentik.flows", "authentik Flows"), + ("authentik.outposts", "authentik Outpost"), + ("authentik.lib", "authentik lib"), + ("authentik.policies", "authentik Policies"), + ("authentik.policies.dummy", "authentik Policies.Dummy"), + ( + "authentik.policies.event_matcher", + "authentik Policies.Event Matcher", + ), + ("authentik.policies.expiry", "authentik Policies.Expiry"), + ("authentik.policies.expression", "authentik Policies.Expression"), + ("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"), + ("authentik.policies.password", "authentik Policies.Password"), + ("authentik.policies.reputation", "authentik Policies.Reputation"), + ("authentik.providers.proxy", "authentik Providers.Proxy"), + ("authentik.providers.ldap", "authentik Providers.LDAP"), + ("authentik.providers.oauth2", "authentik Providers.OAuth2"), + ("authentik.providers.saml", "authentik Providers.SAML"), + ("authentik.recovery", "authentik Recovery"), + ("authentik.sources.ldap", "authentik Sources.LDAP"), + ("authentik.sources.oauth", "authentik Sources.OAuth"), + ("authentik.sources.saml", "authentik Sources.SAML"), + ( + "authentik.stages.authenticator_static", + "authentik Stages.Authenticator.Static", + ), + ( + "authentik.stages.authenticator_totp", + "authentik Stages.Authenticator.TOTP", + ), + ( + "authentik.stages.authenticator_validate", + "authentik Stages.Authenticator.Validate", + ), + ( + "authentik.stages.authenticator_webauthn", + "authentik Stages.Authenticator.WebAuthn", + ), + ("authentik.stages.captcha", "authentik Stages.Captcha"), + ("authentik.stages.consent", "authentik Stages.Consent"), + ("authentik.stages.deny", "authentik Stages.Deny"), + ("authentik.stages.dummy", "authentik Stages.Dummy"), + ("authentik.stages.email", "authentik Stages.Email"), + ( + "authentik.stages.identification", + "authentik Stages.Identification", + ), + ("authentik.stages.invitation", "authentik Stages.User Invitation"), + ("authentik.stages.password", "authentik Stages.Password"), + ("authentik.stages.prompt", "authentik Stages.Prompt"), + ("authentik.stages.user_delete", "authentik Stages.User Delete"), + ("authentik.stages.user_login", "authentik Stages.User Login"), + ("authentik.stages.user_logout", "authentik Stages.User Logout"), + ("authentik.stages.user_write", "authentik Stages.User Write"), + ("authentik.core", "authentik Core"), + ("authentik.managed", "authentik Managed"), + ], + default="", + help_text="Match events created by selected application. When left empty, all applications are matched.", + ), + ), + ] diff --git a/authentik/providers/ldap/__init__.py b/authentik/providers/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/providers/ldap/api.py b/authentik/providers/ldap/api.py new file mode 100644 index 000000000..5e02ce2ac --- /dev/null +++ b/authentik/providers/ldap/api.py @@ -0,0 +1,47 @@ +"""LDAPProvider API Views""" +from rest_framework.fields import CharField +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet + +from authentik.core.api.providers import ProviderSerializer +from authentik.providers.ldap.models import LDAPProvider + + +class LDAPProviderSerializer(ProviderSerializer): + """LDAPProvider Serializer""" + + class Meta: + + model = LDAPProvider + fields = ProviderSerializer.Meta.fields + [ + "bind_flow", + "base_dn", + ] + + +class LDAPProviderViewSet(ModelViewSet): + """LDAPProvider Viewset""" + + queryset = LDAPProvider.objects.all() + serializer_class = LDAPProviderSerializer + ordering = ["name"] + + +class LDAPOutpostConfigSerializer(ModelSerializer): + """LDAPProvider Serializer""" + + application_slug = CharField(source="application.slug") + bind_flow_slug = CharField(source="bind_flow.slug") + + class Meta: + + model = LDAPProvider + fields = ["pk", "name", "base_dn", "bind_flow_slug", "application_slug"] + + +class LDAPOutpostConfigViewSet(ReadOnlyModelViewSet): + """LDAPProvider Viewset""" + + queryset = LDAPProvider.objects.filter(application__isnull=False) + serializer_class = LDAPOutpostConfigSerializer + ordering = ["name"] diff --git a/authentik/providers/ldap/apps.py b/authentik/providers/ldap/apps.py new file mode 100644 index 000000000..7adc551ff --- /dev/null +++ b/authentik/providers/ldap/apps.py @@ -0,0 +1,10 @@ +"""authentik ldap provider app config""" +from django.apps import AppConfig + + +class AuthentikProviderLDAPConfig(AppConfig): + """authentik ldap provider app config""" + + name = "authentik.providers.ldap" + label = "authentik_providers_ldap" + verbose_name = "authentik Providers.LDAP" diff --git a/authentik/providers/ldap/migrations/0001_initial.py b/authentik/providers/ldap/migrations/0001_initial.py new file mode 100644 index 000000000..3e51ff2f4 --- /dev/null +++ b/authentik/providers/ldap/migrations/0001_initial.py @@ -0,0 +1,55 @@ +# Generated by Django 3.2 on 2021-04-26 09:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("authentik_core", "0019_source_managed"), + ("authentik_flows", "0018_oob_flows"), + ] + + operations = [ + migrations.CreateModel( + name="LDAPProvider", + fields=[ + ( + "provider_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="authentik_core.provider", + ), + ), + ( + "base_dn", + models.TextField( + default="DC=ldap,DC=goauthentik,DC=io", + help_text="DN under which objects are accessible.", + ), + ), + ( + "bind_flow", + models.ForeignKey( + default=None, + help_text="Flow which is used to bind users. When left empty, no users will be able to bind.", + null=True, + on_delete=django.db.models.deletion.SET_DEFAULT, + to="authentik_flows.flow", + ), + ), + ], + options={ + "verbose_name": "LDAP Provider", + "verbose_name_plural": "LDAP Providers", + }, + bases=("authentik_core.provider",), + ), + ] diff --git a/authentik/providers/ldap/migrations/__init__.py b/authentik/providers/ldap/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/providers/ldap/models.py b/authentik/providers/ldap/models.py new file mode 100644 index 000000000..594bd020e --- /dev/null +++ b/authentik/providers/ldap/models.py @@ -0,0 +1,51 @@ +"""LDAP Provider""" +from typing import Optional, Type + +from django.db import models +from django.utils.translation import gettext_lazy as _ +from rest_framework.serializers import Serializer + +from authentik.core.models import Provider +from authentik.flows.models import Flow + + +class LDAPProvider(Provider): + """LDAP Provider""" + + base_dn = models.TextField( + default="DC=ldap,DC=goauthentik,DC=io", + help_text=_("DN under which objects are accessible."), + ) + + bind_flow = models.ForeignKey( + Flow, + null=True, + default=None, + on_delete=models.SET_DEFAULT, + help_text=_( + "Flow which is used to bind users. When left empty, no users will be able to bind." + ), + ) + + @property + def launch_url(self) -> Optional[str]: + """LDAP never has a launch URL""" + return None + + @property + def component(self) -> str: + return "ak-provider-ldap-form" + + @property + def serializer(self) -> Type[Serializer]: + from authentik.providers.oauth2.api.provider import OAuth2ProviderSerializer + + return OAuth2ProviderSerializer + + def __str__(self): + return f"LDAP Provider {self.name}" + + class Meta: + + verbose_name = _("LDAP Provider") + verbose_name_plural = _("LDAP Providers") diff --git a/authentik/providers/oauth2/apps.py b/authentik/providers/oauth2/apps.py index a23e33339..59e9fd23a 100644 --- a/authentik/providers/oauth2/apps.py +++ b/authentik/providers/oauth2/apps.py @@ -1,11 +1,11 @@ -"""authentik auth oauth provider app config""" +"""authentik oauth provider app config""" from importlib import import_module from django.apps import AppConfig class AuthentikProviderOAuth2Config(AppConfig): - """authentik auth oauth provider app config""" + """authentik oauth provider app config""" name = "authentik.providers.oauth2" label = "authentik_providers_oauth2" diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 238f42b49..9a714ffc1 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -102,6 +102,7 @@ INSTALLED_APPS = [ "authentik.policies.password", "authentik.policies.reputation", "authentik.providers.proxy", + "authentik.providers.ldap", "authentik.providers.oauth2", "authentik.providers.saml", "authentik.recovery", diff --git a/swagger.yaml b/swagger.yaml index e74f8e2de..254c82cff 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -4671,6 +4671,103 @@ paths: required: true type: string format: uuid + /outposts/ldap/: + get: + operationId: outposts_ldap_list + description: LDAPProvider Viewset + parameters: + - name: ordering + in: query + description: Which field to use when ordering the results. + required: false + type: string + - name: search + in: query + description: A search term. + required: false + type: string + - name: page + in: query + description: Page Index + required: false + type: integer + - name: page_size + in: query + description: Page Size + required: false + type: integer + responses: + '200': + description: '' + schema: + required: + - results + - pagination + type: object + properties: + pagination: + required: + - next + - previous + - count + - current + - total_pages + - start_index + - end_index + type: object + properties: + next: + type: number + previous: + type: number + count: + type: number + current: + type: number + total_pages: + type: number + start_index: + type: number + end_index: + type: number + results: + type: array + items: + $ref: '#/definitions/LDAPOutpostConfig' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - outposts + parameters: [] + /outposts/ldap/{id}/: + get: + operationId: outposts_ldap_read + description: LDAPProvider Viewset + parameters: [] + responses: + '200': + description: '' + schema: + $ref: '#/definitions/LDAPOutpostConfig' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - outposts + parameters: + - name: id + in: path + description: A unique integer value identifying this LDAP Provider. + required: true + type: integer /outposts/outposts/: get: operationId: outposts_outposts_list @@ -8739,6 +8836,203 @@ paths: description: A unique integer value identifying this provider. required: true type: integer + /providers/ldap/: + get: + operationId: providers_ldap_list + description: LDAPProvider Viewset + parameters: + - name: ordering + in: query + description: Which field to use when ordering the results. + required: false + type: string + - name: search + in: query + description: A search term. + required: false + type: string + - name: page + in: query + description: Page Index + required: false + type: integer + - name: page_size + in: query + description: Page Size + required: false + type: integer + responses: + '200': + description: '' + schema: + required: + - results + - pagination + type: object + properties: + pagination: + required: + - next + - previous + - count + - current + - total_pages + - start_index + - end_index + type: object + properties: + next: + type: number + previous: + type: number + count: + type: number + current: + type: number + total_pages: + type: number + start_index: + type: number + end_index: + type: number + results: + type: array + items: + $ref: '#/definitions/LDAPProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - providers + post: + operationId: providers_ldap_create + description: LDAPProvider Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/LDAPProvider' + responses: + '201': + description: '' + schema: + $ref: '#/definitions/LDAPProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - providers + parameters: [] + /providers/ldap/{id}/: + get: + operationId: providers_ldap_read + description: LDAPProvider Viewset + parameters: [] + responses: + '200': + description: '' + schema: + $ref: '#/definitions/LDAPProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - providers + put: + operationId: providers_ldap_update + description: LDAPProvider Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/LDAPProvider' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/LDAPProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - providers + patch: + operationId: providers_ldap_partial_update + description: LDAPProvider Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/LDAPProvider' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/LDAPProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - providers + delete: + operationId: providers_ldap_delete + description: LDAPProvider Viewset + parameters: [] + responses: + '204': + description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - providers + parameters: + - name: id + in: path + description: A unique integer value identifying this LDAP Provider. + required: true + type: integer /providers/oauth2/: get: operationId: providers_oauth2_list @@ -15718,6 +16012,34 @@ definitions: title: Version outdated type: boolean readOnly: true + LDAPOutpostConfig: + required: + - name + - bind_flow_slug + - application_slug + type: object + properties: + pk: + title: ID + type: integer + readOnly: true + name: + title: Name + type: string + minLength: 1 + base_dn: + title: Base dn + description: DN under which objects are accessible. + type: string + minLength: 1 + bind_flow_slug: + title: Bind flow slug + type: string + minLength: 1 + application_slug: + title: Application slug + type: string + minLength: 1 OpenIDConnectConfiguration: description: Embed OpenID Connect provider information required: @@ -16223,6 +16545,7 @@ definitions: - authentik.policies.password - authentik.policies.reputation - authentik.providers.proxy + - authentik.providers.ldap - authentik.providers.oauth2 - authentik.providers.saml - authentik.recovery @@ -16727,6 +17050,63 @@ definitions: description: Description shown to the user when consenting. If left empty, the user won't be informed. type: string + LDAPProvider: + required: + - name + - authorization_flow + type: object + properties: + pk: + title: ID + type: integer + readOnly: true + name: + title: Name + type: string + minLength: 1 + authorization_flow: + title: Authorization flow + description: Flow used when authorizing this provider. + type: string + format: uuid + property_mappings: + type: array + items: + type: string + format: uuid + uniqueItems: true + component: + title: Component + type: string + readOnly: true + assigned_application_slug: + title: Assigned application slug + type: string + readOnly: true + assigned_application_name: + title: Assigned application name + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bind_flow: + title: Bind flow + description: Flow which is used to bind users. When left empty, no users will + be able to bind. + type: string + format: uuid + x-nullable: true + base_dn: + title: Base dn + description: DN under which objects are accessible. + type: string + minLength: 1 OAuth2ProviderSetupURLs: type: object properties: