|
4 | 4 |
|
5 | 5 | from ...tests import mock |
6 | 6 | from ...utils.testing import create_test_profile |
7 | | -from ..decorators.auth import admin_authentication, tenant_authentication |
| 7 | +from ..decorators.auth import admin_authentication, require_scope, tenant_authentication |
8 | 8 | from ..request_context import AdminRequestContext |
9 | 9 |
|
10 | 10 |
|
@@ -174,3 +174,59 @@ async def test_base_wallet_additional_route_denied(self): |
174 | 174 | decor_func = tenant_authentication(self.decorated_handler) |
175 | 175 | with self.assertRaises(web.HTTPUnauthorized): |
176 | 176 | await decor_func(self.request) |
| 177 | + |
| 178 | + |
| 179 | +class TestRequireScope(IsolatedAsyncioTestCase): |
| 180 | + async def asyncSetUp(self) -> None: |
| 181 | + self.profile = await create_test_profile() |
| 182 | + self.decorated_handler = mock.CoroutineMock() |
| 183 | + |
| 184 | + def _make_request(self, scopes=None, method="POST"): |
| 185 | + metadata = {"scopes": set(scopes)} if scopes is not None else None |
| 186 | + context = AdminRequestContext(profile=self.profile, metadata=metadata) |
| 187 | + return mock.MagicMock( |
| 188 | + __getitem__=lambda _, k: {"context": context}[k], |
| 189 | + headers={}, |
| 190 | + method=method, |
| 191 | + ) |
| 192 | + |
| 193 | + async def test_options_always_passes(self): |
| 194 | + request = self._make_request(method="OPTIONS") |
| 195 | + decor = require_scope("acapy:tenant")(self.decorated_handler) |
| 196 | + await decor(request) |
| 197 | + self.decorated_handler.assert_called_once_with(request) |
| 198 | + |
| 199 | + async def test_non_oauth_mode_passes_without_scopes(self): |
| 200 | + """require_scope is a no-op when admin.oauth_enabled is not set.""" |
| 201 | + request = self._make_request() # no metadata / no scopes |
| 202 | + decor = require_scope("acapy:tenant")(self.decorated_handler) |
| 203 | + await decor(request) |
| 204 | + self.decorated_handler.assert_called_once_with(request) |
| 205 | + |
| 206 | + async def test_oauth_mode_passes_with_required_scope(self): |
| 207 | + self.profile.settings["admin.oauth_enabled"] = True |
| 208 | + request = self._make_request(scopes=["acapy:tenant"]) |
| 209 | + decor = require_scope("acapy:tenant", "acapy:admin")(self.decorated_handler) |
| 210 | + await decor(request) |
| 211 | + self.decorated_handler.assert_called_once_with(request) |
| 212 | + |
| 213 | + async def test_oauth_mode_admin_scope_satisfies_any_requirement(self): |
| 214 | + self.profile.settings["admin.oauth_enabled"] = True |
| 215 | + request = self._make_request(scopes=["acapy:admin"]) |
| 216 | + decor = require_scope("acapy:wallet:create", "acapy:admin")(self.decorated_handler) |
| 217 | + await decor(request) |
| 218 | + self.decorated_handler.assert_called_once_with(request) |
| 219 | + |
| 220 | + async def test_oauth_mode_raises_403_on_insufficient_scope(self): |
| 221 | + self.profile.settings["admin.oauth_enabled"] = True |
| 222 | + request = self._make_request(scopes=["acapy:tenant:read"]) |
| 223 | + decor = require_scope("acapy:wallet:create", "acapy:admin")(self.decorated_handler) |
| 224 | + with self.assertRaises(web.HTTPForbidden): |
| 225 | + await decor(request) |
| 226 | + |
| 227 | + async def test_oauth_mode_raises_403_when_no_scopes_in_token(self): |
| 228 | + self.profile.settings["admin.oauth_enabled"] = True |
| 229 | + request = self._make_request(scopes=[]) |
| 230 | + decor = require_scope("acapy:tenant")(self.decorated_handler) |
| 231 | + with self.assertRaises(web.HTTPForbidden): |
| 232 | + await decor(request) |
0 commit comments