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

Rpenido/jill/content search perms #650

Closed
Closed
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
110 changes: 107 additions & 3 deletions openedx/core/djangoapps/content/search/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
"""
import functools
from django.test import override_settings
from rest_framework.test import APITestCase, APIClient
from rest_framework.test import APIClient
from unittest import mock

from organizations.models import Organization
from common.djangoapps.student.auth import add_users, update_org_role
from common.djangoapps.student.roles import (
CourseInstructorRole,
OrgStaffRole
)
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from openedx.core.djangoapps.content.search.models import SearchAccess
from openedx.core.djangolib.testing.utils import skip_unless_cms
from organizations.models import Organization
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory


STUDIO_SEARCH_ENDPOINT_URL = "/api/content_search/v2/studio/"
MOCK_API_KEY_UID = "3203d764-370f-4e99-a917-d47ab7f29739"
Expand Down Expand Up @@ -38,7 +48,7 @@ def wrapper(*args, **kwargs):


@skip_unless_cms
class StudioSearchViewTest(APITestCase):
class StudioSearchViewTest(SharedModuleStoreTestCase):
"""
General tests for the Studio search REST API.
"""
Expand All @@ -52,11 +62,41 @@ def setUpClass(cls):
cls.student = UserFactory.create(
username='student', email='student@example.com', is_staff=False, password='student_pass'
)
cls.staff_org = UserFactory.create(
username='staff_org', email='staff_org@example.com', is_staff=False, password='staff_org_pass'
)
cls.instructor_course = UserFactory.create(
username='instructor_course',
email='instructor_course@example.com',
is_staff=False,
password='instructor_course_pass'
)
cls.org = Organization.objects.create(name="edX", short_name="edX")

def setUp(self):
super().setUp()
self.client = APIClient()

course_location = self.store.make_course_key('edX', 'CreatedCourse', 'Run')
self.course = self._create_course(course_location)
self.course_access_keys = SearchAccess.objects.get(context_key=self.course.id).id

update_org_role(self.staff, OrgStaffRole, self.staff_org, [self.course.id.org])

add_users(self.staff, CourseInstructorRole(self.course.id), self.instructor_course)

def _create_course(self, course_location):
"""
Create dummy course and overview.
"""
CourseFactory.create(
org=course_location.org,
number=course_location.course,
run=course_location.run
)
course = CourseOverviewFactory.create(id=course_location, org=course_location.org)
return course

@mock_meilisearch(enabled=False)
def test_studio_search_unathenticated_disabled(self):
"""
Expand Down Expand Up @@ -143,6 +183,70 @@ def test_studio_search_staff(self, mock_search_client):
expires_at=mock.ANY,
)

@mock_meilisearch(enabled=True)
@mock.patch('openedx.core.djangoapps.content.search.views.meilisearch.Client')
@mock.patch('openedx.core.djangoapps.content.search.views._get_meili_api_key_uid')
def test_studio_search_org_staff(self, mock_get_api_key_uid, mock_search_client):
"""
Org staff can access documents from its orgs
"""
self.client.login(username='staff_org', password='staff_org_pass')
mock_get_api_key_uid.return_value = MOCK_API_KEY_UID
mock_generate_tenant_token = mock.Mock(return_value='restricted_api_key')
mock_search_client.return_value = mock.Mock(
generate_tenant_token=mock_generate_tenant_token,
)
result = self.client.get(STUDIO_SEARCH_ENDPOINT_URL)
assert result.status_code == 200

### To help with debugging
assert mock_generate_tenant_token.call_args[1]["search_rules"]["studio_content"]["filter"] == (
"org IN ['edX'] OR access_id IN []"
)
###

mock_generate_tenant_token.assert_called_once_with(
api_key_uid=MOCK_API_KEY_UID,
search_rules={
"studio_content": {
"filter": "org IN ['edX'] OR access_id IN []",
}
},
expires_at=mock.ANY,
)

@mock_meilisearch(enabled=True)
@mock.patch('openedx.core.djangoapps.content.search.views.meilisearch.Client')
@mock.patch('openedx.core.djangoapps.content.search.views._get_meili_api_key_uid')
def test_studio_search_course_instructor(self, mock_get_api_key_uid, mock_search_client):
"""
Course instructor can access documents it has direct access to
"""
self.client.login(username='instructor_course', password='instructor_course_pass')
mock_get_api_key_uid.return_value = MOCK_API_KEY_UID
mock_generate_tenant_token = mock.Mock(return_value='restricted_api_key')
mock_search_client.return_value = mock.Mock(
generate_tenant_token=mock_generate_tenant_token,
)
result = self.client.get(STUDIO_SEARCH_ENDPOINT_URL)
assert result.status_code == 200

### To help with debugging
assert mock_generate_tenant_token.call_args[1]["search_rules"]["studio_content"]["filter"] == (
f"org IN [] OR access_id IN [{self.course_access_keys}]"
)
###

mock_generate_tenant_token.assert_called_once_with(
api_key_uid=MOCK_API_KEY_UID,
search_rules={
"studio_content": {
"filter": f"org IN [] OR access_id IN [{self.toy_course_access_id}]",
}
},
expires_at=mock.ANY,
)

@mock_meilisearch(enabled=True)
@mock.patch('openedx.core.djangoapps.content.search.views.get_access_ids_for_request')
@mock.patch('openedx.core.djangoapps.content.search.views.meilisearch.Client')
Expand Down
Loading