Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enterprise/providers: SSF #12327

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

enterprise/providers: SSF #12327

wants to merge 10 commits into from

Conversation

BeryJu
Copy link
Member

@BeryJu BeryJu commented Dec 11, 2024

Details

Add SSF Provider


Checklist

  • Local tests pass (ak test authentik/)
  • The code has been formatted (make lint-fix)

If an API change has been made

  • The API schema has been updated (make gen-build)

If changes to the frontend have been made

  • The code has been formatted (make web)

If applicable

  • The documentation has been updated
  • The documentation has been formatted (make website)

@BeryJu BeryJu requested review from a team as code owners December 11, 2024 16:33
Copy link

netlify bot commented Dec 11, 2024

Deploy Preview for authentik-storybook ready!

Name Link
🔨 Latest commit 28c78bc
🔍 Latest deploy log https://app.netlify.com/sites/authentik-storybook/deploys/6765c132bc95a40008027d92
😎 Deploy Preview https://deploy-preview-12327--authentik-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

netlify bot commented Dec 11, 2024

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit 28c78bc
🔍 Latest deploy log https://app.netlify.com/sites/authentik-docs/deploys/6765c132c1e72400085f4e81
😎 Deploy Preview https://deploy-preview-12327--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@BeryJu BeryJu force-pushed the enterprise/providers/ssf branch from 5a58fcf to 23485a9 Compare December 11, 2024 18:16
Copy link

codecov bot commented Dec 11, 2024

❌ 18 Tests Failed:

Tests completed Failed Passed Skipped
1657 18 1639 2
View the top 3 failed tests by shortest run time
authentik.crypto.tests.TestCrypto::test_private_key_download_denied
Stack Traces | 0.037s run time
self = <unittest.case._Outcome object at 0x7fa666faa8a0>
test_case = <authentik.crypto.tests.TestCrypto testMethod=test_private_key_download_denied>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_private_key_download_denied>
result = <TestCaseFunction test_private_key_download_denied>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_private_key_download_denied>
method = <bound method TestCrypto.test_private_key_download_denied of <authentik.crypto.tests.TestCrypto testMethod=test_private_key_download_denied>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_private_key_download_denied>

    def test_private_key_download_denied(self):
        """Test private_key export (download)"""
>       self.client.logout()

authentik/crypto/tests.py:250: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa66817bad0>

    def logout(self):
        self._credentials = {}
    
        # Also clear any `force_authenticate`
        self.handler._force_user = None
        self.handler._force_token = None
    
        if self.session:
>           super().logout()

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../site-packages/rest_framework/test.py:342: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa66817bad0>

    def logout(self):
        """Log out the user by removing the cookies and session object."""
        from django.contrib.auth import get_user, logout
    
        request = HttpRequest()
        if self.session:
            request.session = self.session
            request.user = get_user(request)
        else:
            engine = import_module(settings.SESSION_ENGINE)
            request.session = engine.SessionStore()
>       logout(request)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/test/client.py:861: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <HttpRequest>

    def logout(request):
        """
        Remove the authenticated user's ID from the request and flush their session
        data.
        """
        # Dispatch the signal before the user is logged out so the receivers have a
        # chance to find out *who* logged out.
        user = getattr(request, "user", None)
        if not getattr(user, "is_authenticated", True):
            user = None
>       user_logged_out.send(sender=user.__class__, request=request, user=user)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../contrib/auth/__init__.py:170: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>
sender = <class 'NoneType'>, named = {'request': <HttpRequest>, 'user': None}
responses = [(<function user_logged_out_session at 0x7fa669b9b920>, None), (<function on_user_logged_out at 0x7fa669b9b600>, None)... (<function on_user_logged_out at 0x7fa669b9bb00>, None), (<function user_logged_out_session at 0x7fa669b742c0>, None)]
sync_receivers = [<function user_logged_out_session at 0x7fa669b9b920>, <function on_user_logged_out at 0x7fa669b9b600>, <function user...669b9b240>, <function on_user_logged_out at 0x7fa669b9bb00>, <function user_logged_out_session at 0x7fa669b742c0>, ...]
receiver = <function user_logged_out_session at 0x7fa669b75260>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'request': <HttpRequest>, 'sender': <class 'NoneType'>, 'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>, 'user': None}
signal_name = 'authentik.enterprise.providers.ssf.signals.user_logged_out_session'
span = <Span(op='event.django', description:'authentik.enterprise.providers.ssf.signals.user_logged_out_session', trace_id='0...15ea554b45a0729cb26925f5c6', span_id='968e3bc49c025dfb', parent_span_id=None, sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'NoneType'>, request = <HttpRequest>, user = None
_ = {'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>}

    @receiver(user_logged_out)
    def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
        send_ssf_event.delay(
            EventTypes.SET_VERIFICATION,
>           subject=get_user(user),
        )

.../providers/ssf/signals.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

user = None, original_user = None

    def get_user(user: User | AnonymousUser, original_user: User | None = None) -> dict[str, Any]:
        """Convert user object to dictionary, optionally including the original user"""
        if isinstance(user, AnonymousUser):
            try:
                user = get_anonymous_user()
            except User.DoesNotExist:
                return {}
        user_data = {
>           "username": user.username,
            "pk": user.pk,
            "email": user.email,
        }
E       AttributeError: 'NoneType' object has no attribute 'username'

authentik/events/utils.py:85: AttributeError
authentik.crypto.tests.TestCrypto::test_used_by_denied
Stack Traces | 0.037s run time
self = <unittest.case._Outcome object at 0x7fa6677660f0>
test_case = <authentik.crypto.tests.TestCrypto testMethod=test_used_by_denied>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_used_by_denied>
result = <TestCaseFunction test_used_by_denied>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_used_by_denied>
method = <bound method TestCrypto.test_used_by_denied of <authentik.crypto.tests.TestCrypto testMethod=test_used_by_denied>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_used_by_denied>

    def test_used_by_denied(self):
        """Test used_by endpoint"""
>       self.client.logout()

authentik/crypto/tests.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa6661868d0>

    def logout(self):
        self._credentials = {}
    
        # Also clear any `force_authenticate`
        self.handler._force_user = None
        self.handler._force_token = None
    
        if self.session:
>           super().logout()

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../site-packages/rest_framework/test.py:342: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa6661868d0>

    def logout(self):
        """Log out the user by removing the cookies and session object."""
        from django.contrib.auth import get_user, logout
    
        request = HttpRequest()
        if self.session:
            request.session = self.session
            request.user = get_user(request)
        else:
            engine = import_module(settings.SESSION_ENGINE)
            request.session = engine.SessionStore()
>       logout(request)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/test/client.py:861: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <HttpRequest>

    def logout(request):
        """
        Remove the authenticated user's ID from the request and flush their session
        data.
        """
        # Dispatch the signal before the user is logged out so the receivers have a
        # chance to find out *who* logged out.
        user = getattr(request, "user", None)
        if not getattr(user, "is_authenticated", True):
            user = None
>       user_logged_out.send(sender=user.__class__, request=request, user=user)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../contrib/auth/__init__.py:170: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>
sender = <class 'NoneType'>, named = {'request': <HttpRequest>, 'user': None}
responses = [(<function user_logged_out_session at 0x7fa669c47b00>, None), (<function on_user_logged_out at 0x7fa669c47880>, None)... (<function on_user_logged_out at 0x7fa669c47560>, None), (<function user_logged_out_session at 0x7fa669c477e0>, None)]
sync_receivers = [<function user_logged_out_session at 0x7fa669c47b00>, <function on_user_logged_out at 0x7fa669c47880>, <function user...669c47f60>, <function on_user_logged_out at 0x7fa669c47560>, <function user_logged_out_session at 0x7fa669c477e0>, ...]
receiver = <function user_logged_out_session at 0x7fa669c47e20>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'request': <HttpRequest>, 'sender': <class 'NoneType'>, 'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>, 'user': None}
signal_name = 'authentik.enterprise.providers.ssf.signals.user_logged_out_session'
span = <Span(op='event.django', description:'authentik.enterprise.providers.ssf.signals.user_logged_out_session', trace_id='0...15ea554b45a0729cb26925f5c6', span_id='a69d33ca0796bcee', parent_span_id=None, sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'NoneType'>, request = <HttpRequest>, user = None
_ = {'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>}

    @receiver(user_logged_out)
    def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
        send_ssf_event.delay(
            EventTypes.SET_VERIFICATION,
>           subject=get_user(user),
        )

.../providers/ssf/signals.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

user = None, original_user = None

    def get_user(user: User | AnonymousUser, original_user: User | None = None) -> dict[str, Any]:
        """Convert user object to dictionary, optionally including the original user"""
        if isinstance(user, AnonymousUser):
            try:
                user = get_anonymous_user()
            except User.DoesNotExist:
                return {}
        user_data = {
>           "username": user.username,
            "pk": user.pk,
            "email": user.email,
        }
E       AttributeError: 'NoneType' object has no attribute 'username'

authentik/events/utils.py:85: AttributeError
authentik.crypto.tests.TestCrypto::test_certificate_download_denied
Stack Traces | 0.038s run time
self = <unittest.case._Outcome object at 0x7fa66538e0c0>
test_case = <authentik.crypto.tests.TestCrypto testMethod=test_certificate_download_denied>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_certificate_download_denied>
result = <TestCaseFunction test_certificate_download_denied>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:634: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_certificate_download_denied>
method = <bound method TestCrypto.test_certificate_download_denied of <authentik.crypto.tests.TestCrypto testMethod=test_certificate_download_denied>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.12.8........./x64/lib/python3.12/unittest/case.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.crypto.tests.TestCrypto testMethod=test_certificate_download_denied>

    def test_certificate_download_denied(self):
        """Test certificate export (download)"""
>       self.client.logout()

authentik/crypto/tests.py:230: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa68200e6c0>

    def logout(self):
        self._credentials = {}
    
        # Also clear any `force_authenticate`
        self.handler._force_user = None
        self.handler._force_token = None
    
        if self.session:
>           super().logout()

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../site-packages/rest_framework/test.py:342: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <rest_framework.test.APIClient object at 0x7fa68200e6c0>

    def logout(self):
        """Log out the user by removing the cookies and session object."""
        from django.contrib.auth import get_user, logout
    
        request = HttpRequest()
        if self.session:
            request.session = self.session
            request.user = get_user(request)
        else:
            engine = import_module(settings.SESSION_ENGINE)
            request.session = engine.SessionStore()
>       logout(request)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/test/client.py:861: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <HttpRequest>

    def logout(request):
        """
        Remove the authenticated user's ID from the request and flush their session
        data.
        """
        # Dispatch the signal before the user is logged out so the receivers have a
        # chance to find out *who* logged out.
        user = getattr(request, "user", None)
        if not getattr(user, "is_authenticated", True):
            user = None
>       user_logged_out.send(sender=user.__class__, request=request, user=user)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../contrib/auth/__init__.py:170: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>
sender = <class 'NoneType'>, named = {'request': <HttpRequest>, 'user': None}
responses = [(<function user_logged_out_session at 0x7fa669c56d40>, None), (<function on_user_logged_out at 0x7fa669c547c0>, None)... (<function on_user_logged_out at 0x7fa669c57920>, None), (<function user_logged_out_session at 0x7fa669c55580>, None)]
sync_receivers = [<function user_logged_out_session at 0x7fa669c56d40>, <function on_user_logged_out at 0x7fa669c547c0>, <function user...669c542c0>, <function on_user_logged_out at 0x7fa669c57920>, <function user_logged_out_session at 0x7fa669c55580>, ...]
receiver = <function user_logged_out_session at 0x7fa669c568e0>, response = None

    def send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.
    
        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop. So it's possible that all receivers
        won't be called if an error is raised.
    
        If any receivers are asynchronous, they are called after all the
        synchronous receivers via a single call to async_to_sync(). They are
        also executed concurrently with asyncio.gather().
    
        Arguments:
    
            sender
                The sender of the signal. Either a specific object or None.
    
            named
                Named arguments which will be passed to receivers.
    
        Return a list of tuple pairs [(receiver, response), ... ].
        """
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
>           response = receiver(signal=self, sender=sender, **named)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../django/dispatch/dispatcher.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = ()
kwargs = {'request': <HttpRequest>, 'sender': <class 'NoneType'>, 'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>, 'user': None}
signal_name = 'authentik.enterprise.providers.ssf.signals.user_logged_out_session'
span = <Span(op='event.django', description:'authentik.enterprise.providers.ssf.signals.user_logged_out_session', trace_id='0...15ea554b45a0729cb26925f5c6', span_id='ac72e3f61b8bf510', parent_span_id=None, sampled=None, origin='auto.http.django')>

    @wraps(receiver)
    def wrapper(*args, **kwargs):
        # type: (Any, Any) -> Any
        signal_name = _get_receiver_name(receiver)
        with sentry_sdk.start_span(
            op=OP.EVENT_DJANGO,
            name=signal_name,
            origin=DjangoIntegration.origin,
        ) as span:
            span.set_data("signal", signal_name)
>           return receiver(*args, **kwargs)

../../../..../pypoetry/virtualenvs/authentik-xvtLQ9eE-py3.12/lib/python3.12.../integrations/django/signals_handlers.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

sender = <class 'NoneType'>, request = <HttpRequest>, user = None
_ = {'signal': <django.dispatch.dispatcher.Signal object at 0x7fa68b58a480>}

    @receiver(user_logged_out)
    def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
        send_ssf_event.delay(
            EventTypes.SET_VERIFICATION,
>           subject=get_user(user),
        )

.../providers/ssf/signals.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

user = None, original_user = None

    def get_user(user: User | AnonymousUser, original_user: User | None = None) -> dict[str, Any]:
        """Convert user object to dictionary, optionally including the original user"""
        if isinstance(user, AnonymousUser):
            try:
                user = get_anonymous_user()
            except User.DoesNotExist:
                return {}
        user_data = {
>           "username": user.username,
            "pk": user.pk,
            "email": user.email,
        }
E       AttributeError: 'NoneType' object has no attribute 'username'

authentik/events/utils.py:85: AttributeError

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

@BeryJu BeryJu force-pushed the enterprise/providers/ssf branch 2 times, most recently from 973497c to f96eae1 Compare December 11, 2024 18:41
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
@BeryJu BeryJu force-pushed the enterprise/providers/ssf branch from 3f8147f to 28c78bc Compare December 20, 2024 19:10
@BeryJu BeryJu requested a review from a team as a code owner December 20, 2024 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant