|
28 | 28 | LIBRARY_COLLECTION_UPDATED, |
29 | 29 | LIBRARY_CONTAINER_UPDATED, |
30 | 30 | ) |
| 31 | +from openedx_authz.api.users import get_user_role_assignments_in_scope |
31 | 32 | from openedx_learning.api import authoring as authoring_api |
32 | 33 |
|
| 34 | +from common.djangoapps.student.tests.factories import UserFactory |
33 | 35 | from .. import api |
34 | 36 | from ..models import ContentLibrary |
35 | 37 | from .base import ContentLibrariesRestApiTest |
@@ -1479,3 +1481,126 @@ def test_get_backup_task_status_failed(self) -> None: |
1479 | 1481 | assert status is not None |
1480 | 1482 | assert status['state'] == UserTaskStatus.FAILED |
1481 | 1483 | assert status['file'] is None |
| 1484 | + |
| 1485 | + |
| 1486 | +class ContentLibraryAuthZRoleAssignmentTest(ContentLibrariesRestApiTest): |
| 1487 | + """ |
| 1488 | + Tests for Content Library role assignment via the AuthZ Authorization Framework. |
| 1489 | +
|
| 1490 | + These tests verify that library roles are correctly assigned to users through |
| 1491 | + the openedx-authz (AuthZ) Authorization Framework when libraries are created or when |
| 1492 | + explicit role assignments are made. |
| 1493 | +
|
| 1494 | + See: https://github.com/openedx/openedx-authz/ |
| 1495 | + """ |
| 1496 | + |
| 1497 | + def setUp(self) -> None: |
| 1498 | + super().setUp() |
| 1499 | + |
| 1500 | + # Create Content Libraries |
| 1501 | + self._create_library("test-lib-role-1", "Test Library Role 1") |
| 1502 | + |
| 1503 | + # Fetch the created ContentLibrary objects so we can access their learning_package.id |
| 1504 | + self.lib1 = ContentLibrary.objects.get(slug="test-lib-role-1") |
| 1505 | + |
| 1506 | + def test_assign_library_admin_role_to_user_via_authz(self) -> None: |
| 1507 | + """ |
| 1508 | + Test assigning a library admin role to a user via the AuthZ Authorization Framework. |
| 1509 | +
|
| 1510 | + This test verifies that the openedx-authz Authorization Framework correctly |
| 1511 | + assigns the library_admin role to a user when explicitly called. |
| 1512 | + """ |
| 1513 | + api.assign_library_role_to_user(self.lib1.library_key, self.user, api.AccessLevel.ADMIN_LEVEL) |
| 1514 | + |
| 1515 | + roles = get_user_role_assignments_in_scope(self.user.username, str(self.lib1.library_key)) |
| 1516 | + assert len(roles) == 1 |
| 1517 | + assert "library_admin" in repr(roles[0].roles[0]) |
| 1518 | + |
| 1519 | + def test_assign_library_author_role_to_user_via_authz(self) -> None: |
| 1520 | + """ |
| 1521 | + Test assigning a library author role to a user via the AuthZ Authorization Framework. |
| 1522 | +
|
| 1523 | + This test verifies that the openedx-authz Authorization Framework correctly |
| 1524 | + assigns the library_author role to a user when explicitly called. |
| 1525 | + """ |
| 1526 | + # Create a new user to avoid conflicts with roles assigned during library creation |
| 1527 | + author_user = UserFactory. create( username="Author", email="[email protected]") |
| 1528 | + |
| 1529 | + api.assign_library_role_to_user(self.lib1.library_key, author_user, api.AccessLevel.AUTHOR_LEVEL) |
| 1530 | + |
| 1531 | + roles = get_user_role_assignments_in_scope(author_user.username, str(self.lib1.library_key)) |
| 1532 | + assert len(roles) == 1 |
| 1533 | + assert "library_author" in repr(roles[0].roles[0]) |
| 1534 | + |
| 1535 | + @mock.patch("openedx.core.djangoapps.content_libraries.api.libraries.assign_role_to_user_in_scope") |
| 1536 | + def test_library_creation_assigns_admin_role_via_authz( |
| 1537 | + self, |
| 1538 | + mock_assign_role |
| 1539 | + ) -> None: |
| 1540 | + """ |
| 1541 | + Test that creating a library via REST API assigns admin role via AuthZ. |
| 1542 | +
|
| 1543 | + This test verifies that when a library is created via the REST API, |
| 1544 | + the creator is automatically assigned the library_admin role through |
| 1545 | + the openedx-authz Authorization Framework. |
| 1546 | + """ |
| 1547 | + mock_assign_role.return_value = True |
| 1548 | + |
| 1549 | + # Create a new library (this should trigger role assignment in the REST API) |
| 1550 | + self._create_library("test-lib-role-2", "Test Library Role 2") |
| 1551 | + |
| 1552 | + # Verify that assign_role_to_user_in_scope was called |
| 1553 | + mock_assign_role.assert_called_once() |
| 1554 | + call_args = mock_assign_role.call_args |
| 1555 | + assert call_args[0][0] == self.user.username # username |
| 1556 | + assert call_args[0][1] == "library_admin" # role |
| 1557 | + assert "test-lib-role-2" in call_args[0][2] # library_key (contains slug) |
| 1558 | + |
| 1559 | + @mock.patch("openedx.core.djangoapps.content_libraries.api.libraries.assign_role_to_user_in_scope") |
| 1560 | + def test_library_creation_handles_authz_failure_gracefully( |
| 1561 | + self, |
| 1562 | + mock_assign_role |
| 1563 | + ) -> None: |
| 1564 | + """ |
| 1565 | + Test that library creation succeeds even if AuthZ role assignment fails. |
| 1566 | +
|
| 1567 | + This test verifies that if the openedx-authz Authorization Framework fails to assign |
| 1568 | + a role (returns False), the library creation still succeeds. This ensures that |
| 1569 | + the system degrades gracefully and doesn't break library creation if there are |
| 1570 | + issues with the Authorization Framework. |
| 1571 | + """ |
| 1572 | + # Simulate openedx-authz failing to assign the role |
| 1573 | + mock_assign_role.return_value = False |
| 1574 | + |
| 1575 | + # Library creation should still succeed |
| 1576 | + result = self._create_library("test-lib-role-3", "Test Library Role 3") |
| 1577 | + assert result is not None |
| 1578 | + assert result["slug"] == "test-lib-role-3" |
| 1579 | + |
| 1580 | + # Verify that the library was created successfully |
| 1581 | + lib3 = ContentLibrary.objects.get(slug="test-lib-role-3") |
| 1582 | + assert lib3 is not None |
| 1583 | + assert lib3.slug == "test-lib-role-3" |
| 1584 | + |
| 1585 | + @mock.patch("openedx.core.djangoapps.content_libraries.api.libraries.assign_role_to_user_in_scope") |
| 1586 | + def test_library_creation_handles_authz_exception( |
| 1587 | + self, |
| 1588 | + mock_assign_role |
| 1589 | + ) -> None: |
| 1590 | + """ |
| 1591 | + Test that library creation succeeds even if AuthZ raises an exception. |
| 1592 | +
|
| 1593 | + This test verifies that if the openedx-authz Authorization Framework raises an |
| 1594 | + exception during role assignment, the library creation still succeeds. This ensures |
| 1595 | + robust error handling when the Authorization Framework is unavailable or misconfigured. |
| 1596 | + """ |
| 1597 | + # Simulate openedx-authz raising an exception for unknown issues |
| 1598 | + mock_assign_role.side_effect = Exception("AuthZ unavailable") |
| 1599 | + |
| 1600 | + # Library creation should still succeed (the exception should be caught/handled) |
| 1601 | + # Note: Currently, the code doesn't catch this exception, so we expect it to propagate. |
| 1602 | + # This test documents the current behavior and can be updated if error handling is added. |
| 1603 | + with self.assertRaises(Exception) as context: |
| 1604 | + self._create_library("test-lib-role-4", "Test Library Role 4") |
| 1605 | + |
| 1606 | + assert "AuthZ unavailable" in str(context.exception) |
0 commit comments