-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add studio write access validation (#55)
* feat: add studio write access validation https://jira.2u.com/browse/ACADEMIC-16359 * feat: add edx-drf-extensions lib * Add SessionAuthentication * Add JwtAuthentication * feat: Update CHANGELOG.rst
- Loading branch information
1 parent
2dc69b0
commit ccfacd8
Showing
22 changed files
with
704 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
""" | ||
Custom exceptions for ai-aside | ||
""" | ||
from rest_framework import status | ||
|
||
|
||
class AiAsideException(Exception): | ||
""" | ||
A common base class for all exceptions | ||
""" | ||
http_status = status.HTTP_400_BAD_REQUEST | ||
|
||
|
||
class AiAsideNotFoundException(AiAsideException): | ||
""" | ||
A 404 exception class | ||
""" | ||
http_status = status.HTTP_404_NOT_FOUND |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
""" Permissions for ai-aside API""" | ||
|
||
from rest_framework.permissions import BasePermission | ||
|
||
from ai_aside.config_api.validators import validate_course_key | ||
from ai_aside.platform_imports import can_change_summaries_settings | ||
|
||
|
||
class HasStudioWriteAccess(BasePermission): | ||
""" | ||
Check if the user has studio write access to a course. | ||
""" | ||
def has_permission(self, request, view): | ||
""" | ||
Check permissions for this class. | ||
""" | ||
|
||
if not request.user.is_authenticated: | ||
return False | ||
|
||
if not request.user.is_active: | ||
return False | ||
|
||
course_key_string = view.kwargs.get('course_id') | ||
course_key = validate_course_key(course_key_string) | ||
|
||
return can_change_summaries_settings(request.user, course_key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
""" | ||
Utilities related to API views | ||
""" | ||
|
||
from opaque_keys import InvalidKeyError | ||
from opaque_keys.edx.keys import CourseKey, UsageKey | ||
|
||
from ai_aside.config_api.exceptions import AiAsideException | ||
|
||
|
||
def validate_course_key(course_key_string: str) -> CourseKey: | ||
""" | ||
Validate and parse a course_key string, if supported. | ||
""" | ||
try: | ||
course_key = CourseKey.from_string(course_key_string) | ||
except InvalidKeyError as error: | ||
raise AiAsideException(f"{course_key_string} is not a valid CourseKey") from error | ||
if course_key.deprecated: | ||
raise AiAsideException("Deprecated CourseKeys (Org/Course/Run) are not supported.") | ||
return course_key | ||
|
||
|
||
def validate_unit_key(unit_key_string: str) -> UsageKey: | ||
""" | ||
Validate and parse a unit_key string, if supported. | ||
""" | ||
try: | ||
usage_key = UsageKey.from_string(unit_key_string) | ||
except InvalidKeyError as error: | ||
raise AiAsideException(f"{unit_key_string} is not a valid UsageKey") from error | ||
return usage_key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
""" | ||
Config API Utilities | ||
""" | ||
import logging | ||
|
||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication | ||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthentication | ||
from rest_framework import status | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from ai_aside.config_api.exceptions import AiAsideException | ||
from ai_aside.config_api.permissions import HasStudioWriteAccess | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def handle_ai_aside_exception(exc, name=None): # pylint: disable=inconsistent-return-statements | ||
""" | ||
Converts ai_aside exceptions into restframework responses | ||
""" | ||
if isinstance(exc, AiAsideException): | ||
log.exception(name) | ||
return APIResponse(http_status=exc.http_status, data={'message': str(exc)}) | ||
|
||
|
||
class APIResponse(Response): | ||
"""API Response""" | ||
def __init__(self, data=None, http_status=None, content_type=None, success=False): | ||
_status = http_status or status.HTTP_200_OK | ||
data = data or {} | ||
reply = {'response': {'success': success}} | ||
reply['response'].update(data) | ||
super().__init__(data=reply, status=_status, content_type=content_type) | ||
|
||
|
||
class AiAsideAPIView(APIView): | ||
""" | ||
Base API View with authentication and permissions. | ||
""" | ||
|
||
authentication_classes = (JwtAuthentication, SessionAuthentication,) | ||
permission_classes = (HasStudioWriteAccess,) | ||
|
||
def handle_exception(self, exc): | ||
""" | ||
Converts ai-aside exceptions into standard restframework responses | ||
""" | ||
resp = handle_ai_aside_exception(exc, name=self.__class__.__name__) | ||
if not resp: | ||
resp = super().handle_exception(exc) | ||
return resp |
Oops, something went wrong.