Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions authentik/enterprise/providers/ssf/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from requests.exceptions import RequestException
from structlog.stdlib import get_logger

from authentik.core.models import User
from authentik.enterprise.providers.ssf.models import (
DeliveryMethods,
EventTypes,
Expand All @@ -17,6 +18,7 @@
from authentik.events.system_tasks import SystemTask
from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.root.celery import CELERY_APP

session = get_http_session()
Expand All @@ -43,10 +45,32 @@ def send_ssf_event(
return _send_ssf_event.delay(payload)


def _check_app_access(stream_uuid: str, event_data: dict) -> bool:
"""Check if event is related to user and if so, check
if the user has access to the application"""
stream = Stream.objects.filter(pk=stream_uuid).first()
if not stream:
return False
# `event_data` is a dict version of a StreamEvent
sub_id = event_data.get("payload", {}).get("sub_id", {})
email = sub_id.get("user", {}).get("email", None)
if not email:
return True
user = User.objects.filter(email=email).first()
if not user:
return True
engine = PolicyEngine(stream.provider.backchannel_application, user)
engine.use_cache = False
engine.build()
return engine.passing


@CELERY_APP.task()
def _send_ssf_event(event_data: list[tuple[str, dict]]):
tasks = []
for stream, data in event_data:
if not _check_app_access(stream, data):
continue
event = StreamEvent.objects.create(**data)
tasks.extend(send_single_ssf_event(stream, str(event.uuid)))
main_task = group(*tasks)
Expand Down
21 changes: 20 additions & 1 deletion authentik/enterprise/providers/ssf/tests/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
from django.urls import reverse
from rest_framework.test import APITestCase

from authentik.core.models import Application
from authentik.core.models import Application, Group
from authentik.core.tests.utils import (
create_test_cert,
create_test_user,
)
from authentik.enterprise.providers.ssf.models import (
EventTypes,
SSFEventStatus,
SSFProvider,
Stream,
StreamEvent,
)
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice


Expand Down Expand Up @@ -147,3 +149,20 @@ def test_signal_authenticator_deleted(self):
self.assertEqual(event.payload["sub_id"]["format"], "complex")
self.assertEqual(event.payload["sub_id"]["user"]["format"], "email")
self.assertEqual(event.payload["sub_id"]["user"]["email"], user.email)

def test_signal_policy_ignore(self):
"""Test event not being created for user that doesn't have access to the application"""
PolicyBinding.objects.create(
target=self.application, group=Group.objects.create(name=generate_id()), order=0
)
user = create_test_user()
self.client.force_login(user)
user.set_password(generate_id())
user.save()

stream = Stream.objects.filter(provider=self.provider).first()
self.assertIsNotNone(stream)
event = StreamEvent.objects.filter(
stream=stream, type=EventTypes.CAEP_CREDENTIAL_CHANGE
).first()
self.assertIsNone(event)
100 changes: 53 additions & 47 deletions web/src/admin/providers/ssf/SSFProviderViewPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,55 +110,61 @@ export class SSFProviderViewPage extends AKElement {
if (!this.provider) {
return html``;
}
return html`<div
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
>
<div class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-4-col-on-xl pf-m-4-col-on-2xl">
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("URL")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<input
class="pf-c-form-control"
readonly
type="text"
value=${this.provider.ssfUrl || ""}
/>
</div>
</dd>
</div>
</dl>
return html`<div slot="header" class="pf-c-banner pf-m-info">
${msg("SSF Provider is in preview.")}
<a href="mailto:hello+feature/ssf@goauthentik.io">${msg("Send us feedback!")}</a>
</div>
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
<div
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-4-col-on-xl pf-m-4-col-on-2xl"
>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("URL")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<input
class="pf-c-form-control"
readonly
type="text"
value=${this.provider.ssfUrl || ""}
/>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update SSF Provider")} </span>
<ak-provider-ssf-form slot="form" .instancePk=${this.provider.pk || 0}>
</ak-provider-ssf-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Edit")}
</button>
</ak-forms-modal>
</div>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update SSF Provider")} </span>
<ak-provider-ssf-form slot="form" .instancePk=${this.provider.pk || 0}>
</ak-provider-ssf-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Edit")}
</button>
</ak-forms-modal>
<div class="pf-c-card pf-l-grid__item pf-m-8-col-on-2xl">
<div class="pf-c-card__title">${msg("Streams")}</div>
<ak-provider-ssf-stream-list .providerId=${this.providerID}>
</ak-provider-ssf-stream-list>
</div>
</div>
<div class="pf-c-card pf-l-grid__item pf-m-8-col-on-2xl">
<div class="pf-c-card__title">${msg("Streams")}</div>
<ak-provider-ssf-stream-list .providerId=${this.providerID}>
</ak-provider-ssf-stream-list>
</div>
</div>`;
</div>`;
}
}

Expand Down