|
16 | 16 | from rest_framework.request import Request |
17 | 17 | from rest_framework.test import APIRequestFactory |
18 | 18 |
|
| 19 | +from common.djangoapps.student.roles import CourseLimitedStaffRole |
| 20 | +from common.djangoapps.student.tests.factories import StaffFactory, UserFactory |
19 | 21 | from lms.djangoapps.courseware.exceptions import CourseAccessRedirect |
20 | 22 | from openedx.core.djangoapps.authz.tests.mixins import CourseAuthoringAuthzTestMixin |
21 | 23 | from openedx.core.djangoapps.content.course_overviews.models import CourseOverview |
22 | | -from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order |
23 | | -from xmodule.modulestore.tests.django_utils import ( # lint-amnesty, pylint: disable=wrong-import-order |
| 24 | +from xmodule.course_block import CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_NONE |
| 25 | +from xmodule.modulestore.exceptions import ItemNotFoundError # pylint: disable=wrong-import-order |
| 26 | +from xmodule.modulestore.tests.django_utils import ( # pylint: disable=wrong-import-order |
24 | 27 | ModuleStoreTestCase, |
25 | 28 | SharedModuleStoreTestCase, |
26 | 29 | ) |
@@ -146,98 +149,92 @@ def setUpClass(cls): |
146 | 149 |
|
147 | 150 | cls.course = cls.create_course() |
148 | 151 | cls.hidden_course = cls.create_course( |
149 | | - course='hidden', |
150 | | - visible_to_staff_only=True |
| 152 | + course="hidden", |
| 153 | + visible_to_staff_only=True, |
| 154 | + catalog_visibility=CATALOG_VISIBILITY_NONE, |
| 155 | + ) |
| 156 | + cls.about_only_course = cls.create_course( |
| 157 | + course="aboutonly", |
| 158 | + catalog_visibility=CATALOG_VISIBILITY_ABOUT, |
151 | 159 | ) |
152 | 160 |
|
153 | 161 | def test_get_existing_course_as_authorized_user(self): |
154 | 162 | """User with COURSE_EDITOR role can access course.""" |
155 | | - self.add_user_to_role_in_course( |
156 | | - self.authorized_user, |
157 | | - COURSE_EDITOR.external_key, |
158 | | - self.course.id |
159 | | - ) |
| 163 | + self.add_user_to_role_in_course(self.authorized_user, COURSE_EDITOR.external_key, self.course.id) |
160 | 164 |
|
161 | | - course = self._make_api_call( |
162 | | - self.authorized_user, |
163 | | - self.authorized_user, |
164 | | - self.course.id |
165 | | - ) |
| 165 | + course = self._make_api_call(self.authorized_user, self.authorized_user, self.course.id) |
| 166 | + |
| 167 | + self.verify_course(course) |
| 168 | + |
| 169 | + def test_get_existing_course_without_authz_role_when_catalog_visible(self): |
| 170 | + """User without AuthZ role can still access when catalog visibility allows.""" |
| 171 | + course = self._make_api_call(self.unauthorized_user, self.unauthorized_user, self.course.id) |
166 | 172 |
|
167 | 173 | self.verify_course(course) |
168 | 174 |
|
169 | | - def test_get_existing_course_as_unauthorized_user(self): |
170 | | - """User without role should be denied.""" |
| 175 | + def test_about_only_catalog_visibility_without_authz_role(self): |
| 176 | + """User without AuthZ role can access when catalog visibility is about-only.""" |
| 177 | + course = self._make_api_call(self.unauthorized_user, self.unauthorized_user, self.about_only_course.id) |
| 178 | + |
| 179 | + self.verify_course(course, course_id=str(self.about_only_course.id)) |
| 180 | + |
| 181 | + def test_hidden_course_denied_without_authz_role(self): |
| 182 | + """User without AuthZ role is denied when catalog visibility is none.""" |
171 | 183 | with pytest.raises(CourseAccessRedirect): |
172 | | - self._make_api_call( |
173 | | - self.unauthorized_user, |
174 | | - self.unauthorized_user, |
175 | | - self.course.id |
176 | | - ) |
| 184 | + self._make_api_call(self.unauthorized_user, self.unauthorized_user, self.hidden_course.id) |
177 | 185 |
|
178 | 186 | def test_get_nonexistent_course(self): |
179 | 187 | """Nonexistent course should raise 404.""" |
180 | | - course_key = CourseKey.from_string('edX/toy/nope') |
| 188 | + course_key = CourseKey.from_string("edX/toy/nope") |
181 | 189 |
|
182 | 190 | with pytest.raises(Http404): |
183 | | - self._make_api_call( |
184 | | - self.authorized_user, |
185 | | - self.authorized_user, |
186 | | - course_key |
187 | | - ) |
| 191 | + self._make_api_call(self.authorized_user, self.authorized_user, course_key) |
| 192 | + |
| 193 | + def test_course_staff_bypasses_authz_on_hidden_course(self): |
| 194 | + """Course staff can access a hidden course without an AuthZ role.""" |
| 195 | + course_staff = StaffFactory.create(course_key=self.hidden_course.id) |
| 196 | + |
| 197 | + course = self._make_api_call(course_staff, course_staff, self.hidden_course.id) |
| 198 | + |
| 199 | + self.verify_course(course, course_id=str(self.hidden_course.id)) |
| 200 | + |
| 201 | + def test_limited_staff_bypasses_authz_on_hidden_course(self): |
| 202 | + """Limited course staff can access a hidden course without an AuthZ role.""" |
| 203 | + limited_staff = UserFactory(password=self.password) |
| 204 | + CourseLimitedStaffRole(self.hidden_course.id).add_users(limited_staff) |
| 205 | + |
| 206 | + course = self._make_api_call(limited_staff, limited_staff, self.hidden_course.id) |
| 207 | + |
| 208 | + self.verify_course(course, course_id=str(self.hidden_course.id)) |
188 | 209 |
|
189 | 210 | def test_hidden_course_for_staff(self): |
190 | 211 | """Staff can access hidden course.""" |
191 | | - course = self._make_api_call( |
192 | | - self.staff_user, |
193 | | - self.staff_user, |
194 | | - self.hidden_course.id |
195 | | - ) |
| 212 | + course = self._make_api_call(self.staff_user, self.staff_user, self.hidden_course.id) |
196 | 213 |
|
197 | | - self.verify_course( |
198 | | - course, |
199 | | - course_id='course-v1:edX+hidden+2012_Fall' |
200 | | - ) |
| 214 | + self.verify_course(course, course_id=str(self.hidden_course.id)) |
201 | 215 |
|
202 | 216 | def test_hidden_course_for_staff_as_unauthorized_user(self): |
203 | 217 | """ |
204 | 218 | Staff requesting data for another user without permissions |
205 | 219 | should not bypass visibility rules. |
206 | 220 | """ |
207 | 221 | with pytest.raises(CourseAccessRedirect): |
208 | | - self._make_api_call( |
209 | | - self.staff_user, |
210 | | - self.unauthorized_user, |
211 | | - self.hidden_course.id |
212 | | - ) |
| 222 | + self._make_api_call(self.staff_user, self.unauthorized_user, self.hidden_course.id) |
213 | 223 |
|
214 | 224 | def test_user_gains_access_after_role_assignment(self): |
215 | | - """User initially denied, then allowed after role assignment.""" |
| 225 | + """User denied when catalog is hidden, then allowed after role assignment.""" |
216 | 226 | with pytest.raises(CourseAccessRedirect): |
217 | | - self._make_api_call( |
218 | | - self.unauthorized_user, |
219 | | - self.unauthorized_user, |
220 | | - self.course.id |
221 | | - ) |
222 | | - self.add_user_to_role_in_course( |
223 | | - self.unauthorized_user, |
224 | | - COURSE_EDITOR.external_key, |
225 | | - self.course.id |
226 | | - ) |
227 | | - course = self._make_api_call( |
228 | | - self.unauthorized_user, |
229 | | - self.unauthorized_user, |
230 | | - self.course.id |
231 | | - ) |
232 | | - self.verify_course(course) |
| 227 | + self._make_api_call(self.unauthorized_user, self.unauthorized_user, self.hidden_course.id) |
| 228 | + |
| 229 | + self.add_user_to_role_in_course(self.unauthorized_user, COURSE_EDITOR.external_key, self.hidden_course.id) |
| 230 | + |
| 231 | + course = self._make_api_call(self.unauthorized_user, self.unauthorized_user, self.hidden_course.id) |
| 232 | + |
| 233 | + self.verify_course(course, course_id=str(self.hidden_course.id)) |
233 | 234 |
|
234 | 235 | def test_staff_access_without_authz_role(self): |
235 | 236 | """Staff bypasses AuthZ roles.""" |
236 | | - course = self._make_api_call( |
237 | | - self.staff_user, |
238 | | - self.staff_user, |
239 | | - self.course.id |
240 | | - ) |
| 237 | + course = self._make_api_call(self.staff_user, self.staff_user, self.course.id) |
241 | 238 |
|
242 | 239 | self.verify_course(course) |
243 | 240 |
|
|
0 commit comments