authentik.sources.oauth.tests.test_type_slack

Slack Type tests

  1"""Slack Type tests"""
  2
  3from unittest.mock import patch
  4
  5from django.test import TestCase
  6
  7from authentik.sources.oauth.models import OAuthSource
  8from authentik.sources.oauth.types.slack import (
  9    SlackOAuth2Callback,
 10    SlackOAuthClient,
 11    SlackType,
 12)
 13
 14# https://api.slack.com/methods/openid.connect.userInfo
 15SLACK_USER = {
 16    "ok": True,
 17    "sub": "U09VAHA70UU",
 18    "https://slack.com/user_id": "U09VAHA70UU",
 19    "https://slack.com/team_id": "T08G285D7DX",
 20    "email": "user@example.com",
 21    "email_verified": True,
 22    "name": "Test User",
 23    "picture": "https://secure.gravatar.com/avatar/test.jpg",
 24    "given_name": "Test",
 25    "family_name": "User",
 26    "locale": "en-US",
 27    "https://slack.com/team_name": "Test Workspace",
 28    "https://slack.com/team_domain": "test-workspace",
 29}
 30
 31# https://api.slack.com/methods/oauth.v2.access
 32# Slack oauth.v2.access response with user token (Sign in with Slack, no token rotation)
 33SLACK_TOKEN_RESPONSE = {
 34    "ok": True,
 35    "app_id": "A0118NQPZZC",
 36    "authed_user": {
 37        "id": "U065VRX1T0",
 38        "scope": "openid,email,profile",
 39        "access_token": "xoxp-user-token-12345",
 40        "token_type": "user",
 41    },
 42    "team": {"id": "T024BE7LD"},
 43    "enterprise": None,
 44    "is_enterprise_install": False,
 45}
 46
 47# https://api.slack.com/methods/oauth.v2.access
 48# https://api.slack.com/authentication/rotation
 49# Slack oauth.v2.access response with token rotation enabled
 50SLACK_TOKEN_RESPONSE_WITH_REFRESH = {
 51    "ok": True,
 52    "app_id": "A0KRD7HC3",
 53    "authed_user": {
 54        "id": "U1234",
 55        "scope": "openid,email,profile",
 56        "access_token": "xoxe.xoxp-1234",
 57        "refresh_token": "xoxe-1-refresh-token",
 58        "token_type": "user",
 59        "expires_in": 43200,
 60    },
 61    "team": {"id": "T9TK3CUKW", "name": "Slack Softball Team"},
 62    "enterprise": None,
 63    "is_enterprise_install": False,
 64}
 65
 66
 67class TestTypeSlack(TestCase):
 68    """Slack OAuth Source tests"""
 69
 70    def setUp(self):
 71        self.source = OAuthSource.objects.create(
 72            name="test",
 73            slug="test",
 74            provider_type="slack",
 75        )
 76
 77    def test_enroll_context(self):
 78        """Test Slack enrollment context"""
 79        ak_context = SlackType().get_base_user_properties(
 80            source=self.source, info=SLACK_USER, token={}
 81        )
 82        self.assertEqual(ak_context["username"], SLACK_USER["name"])
 83        self.assertEqual(ak_context["email"], SLACK_USER["email"])
 84        self.assertEqual(ak_context["name"], SLACK_USER["name"])
 85
 86    def test_get_user_id(self):
 87        """Test Slack user ID extraction from profile info"""
 88        callback = SlackOAuth2Callback()
 89        # Test with 'sub' field (OIDC userinfo response)
 90        self.assertEqual(callback.get_user_id({"sub": "U12345"}), "U12345")
 91        # Test with no sub
 92        self.assertIsNone(callback.get_user_id({}))
 93
 94    def test_token_extraction_user_token(self):
 95        """Test that user token is extracted from nested authed_user"""
 96        client = SlackOAuthClient(self.source, None)
 97
 98        # Mock the parent class method to return Slack's nested response
 99        with patch(
100            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
101        ) as mock_parent:
102            mock_parent.return_value = SLACK_TOKEN_RESPONSE.copy()
103
104            token = client.get_access_token()
105
106            # Verify user token was extracted to top level
107            self.assertEqual(token["access_token"], "xoxp-user-token-12345")
108            # Verify token_type was normalized to Bearer
109            self.assertEqual(token["token_type"], "Bearer")
110            # Verify user ID was extracted
111            self.assertEqual(token["id"], "U065VRX1T0")
112
113    def test_token_extraction_with_refresh(self):
114        """Test that refresh_token and expires_in are extracted when present"""
115        client = SlackOAuthClient(self.source, None)
116
117        with patch(
118            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
119        ) as mock_parent:
120            mock_parent.return_value = SLACK_TOKEN_RESPONSE_WITH_REFRESH.copy()
121
122            token = client.get_access_token()
123
124            # Verify tokens were extracted from authed_user
125            self.assertEqual(token["access_token"], "xoxe.xoxp-1234")
126            self.assertEqual(token["refresh_token"], "xoxe-1-refresh-token")
127            self.assertEqual(token["expires_in"], 43200)
128            self.assertEqual(token["token_type"], "Bearer")
129            self.assertEqual(token["id"], "U1234")
130
131    def test_token_type_always_bearer(self):
132        """Test that token_type is always set to Bearer"""
133        client = SlackOAuthClient(self.source, None)
134
135        # Test with authed_user response
136        with patch(
137            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
138        ) as mock_parent:
139            mock_parent.return_value = {
140                "ok": True,
141                "authed_user": {
142                    "id": "U12345",
143                    "access_token": "xoxp-test",
144                    "token_type": "user",  # Slack returns "user"
145                },
146            }
147            token = client.get_access_token()
148            self.assertEqual(token["token_type"], "Bearer")
149
150        # Test with bot token response (no authed_user)
151        with patch(
152            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
153        ) as mock_parent:
154            mock_parent.return_value = {
155                "ok": True,
156                "access_token": "xoxb-bot-token",
157                # No token_type in response
158            }
159            token = client.get_access_token()
160            self.assertEqual(token["token_type"], "Bearer")
161
162    def test_token_error_passthrough(self):
163        """Test that error responses are passed through unchanged"""
164        client = SlackOAuthClient(self.source, None)
165
166        with patch(
167            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
168        ) as mock_parent:
169            mock_parent.return_value = {"error": "invalid_grant"}
170            token = client.get_access_token()
171            self.assertEqual(token, {"error": "invalid_grant"})
172
173    def test_token_none_passthrough(self):
174        """Test that None is passed through"""
175        client = SlackOAuthClient(self.source, None)
176
177        with patch(
178            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
179        ) as mock_parent:
180            mock_parent.return_value = None
181            token = client.get_access_token()
182            self.assertIsNone(token)
SLACK_USER = {'ok': True, 'sub': 'U09VAHA70UU', 'https://slack.com/user_id': 'U09VAHA70UU', 'https://slack.com/team_id': 'T08G285D7DX', 'email': 'user@example.com', 'email_verified': True, 'name': 'Test User', 'picture': 'https://secure.gravatar.com/avatar/test.jpg', 'given_name': 'Test', 'family_name': 'User', 'locale': 'en-US', 'https://slack.com/team_name': 'Test Workspace', 'https://slack.com/team_domain': 'test-workspace'}
SLACK_TOKEN_RESPONSE = {'ok': True, 'app_id': 'A0118NQPZZC', 'authed_user': {'id': 'U065VRX1T0', 'scope': 'openid,email,profile', 'access_token': 'xoxp-user-token-12345', 'token_type': 'user'}, 'team': {'id': 'T024BE7LD'}, 'enterprise': None, 'is_enterprise_install': False}
SLACK_TOKEN_RESPONSE_WITH_REFRESH = {'ok': True, 'app_id': 'A0KRD7HC3', 'authed_user': {'id': 'U1234', 'scope': 'openid,email,profile', 'access_token': 'xoxe.xoxp-1234', 'refresh_token': 'xoxe-1-refresh-token', 'token_type': 'user', 'expires_in': 43200}, 'team': {'id': 'T9TK3CUKW', 'name': 'Slack Softball Team'}, 'enterprise': None, 'is_enterprise_install': False}
class TestTypeSlack(django.test.testcases.TestCase):
 68class TestTypeSlack(TestCase):
 69    """Slack OAuth Source tests"""
 70
 71    def setUp(self):
 72        self.source = OAuthSource.objects.create(
 73            name="test",
 74            slug="test",
 75            provider_type="slack",
 76        )
 77
 78    def test_enroll_context(self):
 79        """Test Slack enrollment context"""
 80        ak_context = SlackType().get_base_user_properties(
 81            source=self.source, info=SLACK_USER, token={}
 82        )
 83        self.assertEqual(ak_context["username"], SLACK_USER["name"])
 84        self.assertEqual(ak_context["email"], SLACK_USER["email"])
 85        self.assertEqual(ak_context["name"], SLACK_USER["name"])
 86
 87    def test_get_user_id(self):
 88        """Test Slack user ID extraction from profile info"""
 89        callback = SlackOAuth2Callback()
 90        # Test with 'sub' field (OIDC userinfo response)
 91        self.assertEqual(callback.get_user_id({"sub": "U12345"}), "U12345")
 92        # Test with no sub
 93        self.assertIsNone(callback.get_user_id({}))
 94
 95    def test_token_extraction_user_token(self):
 96        """Test that user token is extracted from nested authed_user"""
 97        client = SlackOAuthClient(self.source, None)
 98
 99        # Mock the parent class method to return Slack's nested response
100        with patch(
101            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
102        ) as mock_parent:
103            mock_parent.return_value = SLACK_TOKEN_RESPONSE.copy()
104
105            token = client.get_access_token()
106
107            # Verify user token was extracted to top level
108            self.assertEqual(token["access_token"], "xoxp-user-token-12345")
109            # Verify token_type was normalized to Bearer
110            self.assertEqual(token["token_type"], "Bearer")
111            # Verify user ID was extracted
112            self.assertEqual(token["id"], "U065VRX1T0")
113
114    def test_token_extraction_with_refresh(self):
115        """Test that refresh_token and expires_in are extracted when present"""
116        client = SlackOAuthClient(self.source, None)
117
118        with patch(
119            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
120        ) as mock_parent:
121            mock_parent.return_value = SLACK_TOKEN_RESPONSE_WITH_REFRESH.copy()
122
123            token = client.get_access_token()
124
125            # Verify tokens were extracted from authed_user
126            self.assertEqual(token["access_token"], "xoxe.xoxp-1234")
127            self.assertEqual(token["refresh_token"], "xoxe-1-refresh-token")
128            self.assertEqual(token["expires_in"], 43200)
129            self.assertEqual(token["token_type"], "Bearer")
130            self.assertEqual(token["id"], "U1234")
131
132    def test_token_type_always_bearer(self):
133        """Test that token_type is always set to Bearer"""
134        client = SlackOAuthClient(self.source, None)
135
136        # Test with authed_user response
137        with patch(
138            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
139        ) as mock_parent:
140            mock_parent.return_value = {
141                "ok": True,
142                "authed_user": {
143                    "id": "U12345",
144                    "access_token": "xoxp-test",
145                    "token_type": "user",  # Slack returns "user"
146                },
147            }
148            token = client.get_access_token()
149            self.assertEqual(token["token_type"], "Bearer")
150
151        # Test with bot token response (no authed_user)
152        with patch(
153            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
154        ) as mock_parent:
155            mock_parent.return_value = {
156                "ok": True,
157                "access_token": "xoxb-bot-token",
158                # No token_type in response
159            }
160            token = client.get_access_token()
161            self.assertEqual(token["token_type"], "Bearer")
162
163    def test_token_error_passthrough(self):
164        """Test that error responses are passed through unchanged"""
165        client = SlackOAuthClient(self.source, None)
166
167        with patch(
168            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
169        ) as mock_parent:
170            mock_parent.return_value = {"error": "invalid_grant"}
171            token = client.get_access_token()
172            self.assertEqual(token, {"error": "invalid_grant"})
173
174    def test_token_none_passthrough(self):
175        """Test that None is passed through"""
176        client = SlackOAuthClient(self.source, None)
177
178        with patch(
179            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
180        ) as mock_parent:
181            mock_parent.return_value = None
182            token = client.get_access_token()
183            self.assertIsNone(token)

Slack OAuth Source tests

def setUp(self):
71    def setUp(self):
72        self.source = OAuthSource.objects.create(
73            name="test",
74            slug="test",
75            provider_type="slack",
76        )

Hook method for setting up the test fixture before exercising it.

def test_enroll_context(self):
78    def test_enroll_context(self):
79        """Test Slack enrollment context"""
80        ak_context = SlackType().get_base_user_properties(
81            source=self.source, info=SLACK_USER, token={}
82        )
83        self.assertEqual(ak_context["username"], SLACK_USER["name"])
84        self.assertEqual(ak_context["email"], SLACK_USER["email"])
85        self.assertEqual(ak_context["name"], SLACK_USER["name"])

Test Slack enrollment context

def test_get_user_id(self):
87    def test_get_user_id(self):
88        """Test Slack user ID extraction from profile info"""
89        callback = SlackOAuth2Callback()
90        # Test with 'sub' field (OIDC userinfo response)
91        self.assertEqual(callback.get_user_id({"sub": "U12345"}), "U12345")
92        # Test with no sub
93        self.assertIsNone(callback.get_user_id({}))

Test Slack user ID extraction from profile info

def test_token_extraction_user_token(self):
 95    def test_token_extraction_user_token(self):
 96        """Test that user token is extracted from nested authed_user"""
 97        client = SlackOAuthClient(self.source, None)
 98
 99        # Mock the parent class method to return Slack's nested response
100        with patch(
101            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
102        ) as mock_parent:
103            mock_parent.return_value = SLACK_TOKEN_RESPONSE.copy()
104
105            token = client.get_access_token()
106
107            # Verify user token was extracted to top level
108            self.assertEqual(token["access_token"], "xoxp-user-token-12345")
109            # Verify token_type was normalized to Bearer
110            self.assertEqual(token["token_type"], "Bearer")
111            # Verify user ID was extracted
112            self.assertEqual(token["id"], "U065VRX1T0")

Test that user token is extracted from nested authed_user

def test_token_extraction_with_refresh(self):
114    def test_token_extraction_with_refresh(self):
115        """Test that refresh_token and expires_in are extracted when present"""
116        client = SlackOAuthClient(self.source, None)
117
118        with patch(
119            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
120        ) as mock_parent:
121            mock_parent.return_value = SLACK_TOKEN_RESPONSE_WITH_REFRESH.copy()
122
123            token = client.get_access_token()
124
125            # Verify tokens were extracted from authed_user
126            self.assertEqual(token["access_token"], "xoxe.xoxp-1234")
127            self.assertEqual(token["refresh_token"], "xoxe-1-refresh-token")
128            self.assertEqual(token["expires_in"], 43200)
129            self.assertEqual(token["token_type"], "Bearer")
130            self.assertEqual(token["id"], "U1234")

Test that refresh_token and expires_in are extracted when present

def test_token_type_always_bearer(self):
132    def test_token_type_always_bearer(self):
133        """Test that token_type is always set to Bearer"""
134        client = SlackOAuthClient(self.source, None)
135
136        # Test with authed_user response
137        with patch(
138            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
139        ) as mock_parent:
140            mock_parent.return_value = {
141                "ok": True,
142                "authed_user": {
143                    "id": "U12345",
144                    "access_token": "xoxp-test",
145                    "token_type": "user",  # Slack returns "user"
146                },
147            }
148            token = client.get_access_token()
149            self.assertEqual(token["token_type"], "Bearer")
150
151        # Test with bot token response (no authed_user)
152        with patch(
153            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
154        ) as mock_parent:
155            mock_parent.return_value = {
156                "ok": True,
157                "access_token": "xoxb-bot-token",
158                # No token_type in response
159            }
160            token = client.get_access_token()
161            self.assertEqual(token["token_type"], "Bearer")

Test that token_type is always set to Bearer

def test_token_error_passthrough(self):
163    def test_token_error_passthrough(self):
164        """Test that error responses are passed through unchanged"""
165        client = SlackOAuthClient(self.source, None)
166
167        with patch(
168            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
169        ) as mock_parent:
170            mock_parent.return_value = {"error": "invalid_grant"}
171            token = client.get_access_token()
172            self.assertEqual(token, {"error": "invalid_grant"})

Test that error responses are passed through unchanged

def test_token_none_passthrough(self):
174    def test_token_none_passthrough(self):
175        """Test that None is passed through"""
176        client = SlackOAuthClient(self.source, None)
177
178        with patch(
179            "authentik.sources.oauth.clients.oauth2.OAuth2Client.get_access_token"
180        ) as mock_parent:
181            mock_parent.return_value = None
182            token = client.get_access_token()
183            self.assertIsNone(token)

Test that None is passed through