Commit ead419f0 by Asad Iqbal Committed by Jonathan Piacenti

asadiqbal08/api-address-based-restriction:API Security: IP address based…

asadiqbal08/api-address-based-restriction:API Security: IP address based restriction added MCKIN-1108

uncomment the code
parent 57cd0f14
...@@ -8,13 +8,11 @@ from StringIO import StringIO ...@@ -8,13 +8,11 @@ from StringIO import StringIO
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from rest_framework import status, generics from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from api_manager.permissions import ApiKeyHeaderPermission
from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile
from api_manager.users.serializers import UserSerializer from api_manager.users.serializers import UserSerializer
from courseware import module_render from courseware import module_render
...@@ -24,6 +22,7 @@ from courseware.views import get_static_tab_contents ...@@ -24,6 +22,7 @@ from courseware.views import get_static_tab_contents
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location, InvalidLocationError from xmodule.modulestore import Location, InvalidLocationError
from api_manager.permissions import SecureAPIView
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -246,8 +245,8 @@ def _parse_updates_html(html): ...@@ -246,8 +245,8 @@ def _parse_updates_html(html):
return result return result
class CourseContentList(APIView): class CourseContentList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id, content_id=None): def get(self, request, course_id, content_id=None):
""" """
...@@ -279,8 +278,8 @@ class CourseContentList(APIView): ...@@ -279,8 +278,8 @@ class CourseContentList(APIView):
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
class CourseContentDetail(APIView): class CourseContentDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id, content_id): def get(self, request, course_id, content_id):
""" """
...@@ -316,8 +315,8 @@ class CourseContentDetail(APIView): ...@@ -316,8 +315,8 @@ class CourseContentDetail(APIView):
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
class CoursesList(APIView): class CoursesList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request): def get(self, request):
""" """
...@@ -336,8 +335,8 @@ class CoursesList(APIView): ...@@ -336,8 +335,8 @@ class CoursesList(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class CoursesDetail(APIView): class CoursesDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id): def get(self, request, course_id):
""" """
...@@ -375,8 +374,8 @@ class CoursesDetail(APIView): ...@@ -375,8 +374,8 @@ class CoursesDetail(APIView):
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
class CoursesGroupsList(APIView): class CoursesGroupsList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, course_id): def post(self, request, course_id):
""" """
...@@ -433,8 +432,9 @@ class CoursesGroupsList(APIView): ...@@ -433,8 +432,9 @@ class CoursesGroupsList(APIView):
response_status = status.HTTP_200_OK response_status = status.HTTP_200_OK
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class CoursesGroupsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) class CoursesGroupsDetail(SecureAPIView):
""" Inherit with SecureAPIView """
def get(self, request, course_id, group_id): def get(self, request, course_id, group_id):
""" """
...@@ -480,8 +480,8 @@ class CoursesGroupsDetail(APIView): ...@@ -480,8 +480,8 @@ class CoursesGroupsDetail(APIView):
return Response(response_data, status=status.HTTP_204_NO_CONTENT) return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class CoursesOverview(APIView): class CoursesOverview(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id): def get(self, request, course_id):
""" """
...@@ -507,8 +507,8 @@ class CoursesOverview(APIView): ...@@ -507,8 +507,8 @@ class CoursesOverview(APIView):
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
class CoursesUpdates(APIView): class CoursesUpdates(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id): def get(self, request, course_id):
""" """
...@@ -532,8 +532,8 @@ class CoursesUpdates(APIView): ...@@ -532,8 +532,8 @@ class CoursesUpdates(APIView):
return Response(response_data) return Response(response_data)
class CoursesStaticTabsList(APIView): class CoursesStaticTabsList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id): def get(self, request, course_id):
""" """
...@@ -564,8 +564,8 @@ class CoursesStaticTabsList(APIView): ...@@ -564,8 +564,8 @@ class CoursesStaticTabsList(APIView):
return Response(response_data) return Response(response_data)
class CoursesStaticTabsDetail(APIView): class CoursesStaticTabsDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id, tab_id): def get(self, request, course_id, tab_id):
""" """
...@@ -594,8 +594,8 @@ class CoursesStaticTabsDetail(APIView): ...@@ -594,8 +594,8 @@ class CoursesStaticTabsDetail(APIView):
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
class CoursesUsersList(APIView): class CoursesUsersList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, course_id): def post(self, request, course_id):
""" """
...@@ -673,8 +673,8 @@ class CoursesUsersList(APIView): ...@@ -673,8 +673,8 @@ class CoursesUsersList(APIView):
return Response(response_data) return Response(response_data)
class CoursesUsersDetail(APIView): class CoursesUsersDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id, user_id): def get(self, request, course_id, user_id):
""" """
...@@ -732,8 +732,8 @@ class CoursesUsersDetail(APIView): ...@@ -732,8 +732,8 @@ class CoursesUsersDetail(APIView):
return Response(response_data, status=status.HTTP_204_NO_CONTENT) return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class CourseContentGroupsList(APIView): class CourseContentGroupsList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, course_id, content_id): def post(self, request, course_id, content_id):
try: try:
...@@ -808,8 +808,8 @@ class CourseContentGroupsList(APIView): ...@@ -808,8 +808,8 @@ class CourseContentGroupsList(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class CourseContentGroupsDetail(APIView): class CourseContentGroupsDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, course_id, content_id, group_id): def get(self, request, course_id, content_id, group_id):
""" """
...@@ -841,7 +841,7 @@ class CourseContentGroupsDetail(APIView): ...@@ -841,7 +841,7 @@ class CourseContentGroupsDetail(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class CourseContentUsersList(generics.ListAPIView): class CourseContentUsersList(SecureAPIView):
""" """
### The CourseContentUsersList view allows clients to users enrolled and ### The CourseContentUsersList view allows clients to users enrolled and
users not enrolled for course within all groups of course users not enrolled for course within all groups of course
...@@ -855,15 +855,10 @@ class CourseContentUsersList(generics.ListAPIView): ...@@ -855,15 +855,10 @@ class CourseContentUsersList(generics.ListAPIView):
* An example of specific group filtering is to get the set of users who are members of a particular workgroup related to the content * An example of specific group filtering is to get the set of users who are members of a particular workgroup related to the content
* An example of group type filtering is to get all users who are members of an organization group related to the content * An example of group type filtering is to get all users who are members of an organization group related to the content
""" """
permission_classes = (ApiKeyHeaderPermission,) def get(self, request, course_id, content_id):
serializer_class = UserSerializer
def get_queryset(self):
""" """
GET /api/courses/{course_id}/content/{content_id}/users GET /api/courses/{course_id}/content/{content_id}/users
""" """
course_id = self.kwargs['course_id']
content_id = self.kwargs['content_id']
enrolled = self.request.QUERY_PARAMS.get('enrolled', 'True') enrolled = self.request.QUERY_PARAMS.get('enrolled', 'True')
group_type = self.request.QUERY_PARAMS.get('type', None) group_type = self.request.QUERY_PARAMS.get('type', None)
group_id = self.request.QUERY_PARAMS.get('group_id', None) group_id = self.request.QUERY_PARAMS.get('group_id', None)
...@@ -883,4 +878,6 @@ class CourseContentUsersList(generics.ListAPIView): ...@@ -883,4 +878,6 @@ class CourseContentUsersList(generics.ListAPIView):
queryset = enrolled_users queryset = enrolled_users
else: else:
queryset = list(itertools.ifilterfalse(lambda x: x in enrolled_users, users)) queryset = list(itertools.ifilterfalse(lambda x: x in enrolled_users, users))
return queryset
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data) # pylint: disable=E1101
...@@ -8,13 +8,13 @@ from django.core.exceptions import ObjectDoesNotExist ...@@ -8,13 +8,13 @@ from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from api_manager.permissions import ApiKeyHeaderPermission
from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from api_manager.permissions import SecureAPIView
from xmodule.modulestore import Location, InvalidLocationError from xmodule.modulestore import Location, InvalidLocationError
RELATIONSHIP_TYPES = {'hierarchical': 'h', 'graph': 'g'} RELATIONSHIP_TYPES = {'hierarchical': 'h', 'graph': 'g'}
...@@ -39,7 +39,7 @@ def _generate_base_uri(request, include_query_string=True): ...@@ -39,7 +39,7 @@ def _generate_base_uri(request, include_query_string=True):
return resource_uri return resource_uri
class GroupsList(APIView): class GroupsList(SecureAPIView):
""" """
### The GroupsList view allows clients to retrieve/append a list of Group entities ### The GroupsList view allows clients to retrieve/append a list of Group entities
- URI: ```/api/groups/``` - URI: ```/api/groups/```
...@@ -74,7 +74,6 @@ class GroupsList(APIView): ...@@ -74,7 +74,6 @@ class GroupsList(APIView):
** organization: display_name, contact_name, phone, email ** organization: display_name, contact_name, phone, email
* Ultimately, both 'type' and 'data' are determined by the client/caller. Open edX has no type or data specifications at the present time. * Ultimately, both 'type' and 'data' are determined by the client/caller. Open edX has no type or data specifications at the present time.
""" """
permissions_classes = (ApiKeyHeaderPermission,)
def post(self, request): def post(self, request):
""" """
...@@ -140,7 +139,7 @@ class GroupsList(APIView): ...@@ -140,7 +139,7 @@ class GroupsList(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class GroupsDetail(APIView): class GroupsDetail(SecureAPIView):
""" """
### The GroupsDetail view allows clients to interact with a specific Group entity ### The GroupsDetail view allows clients to interact with a specific Group entity
- URI: ```/api/groups/{group_id}``` - URI: ```/api/groups/{group_id}```
...@@ -167,7 +166,6 @@ class GroupsDetail(APIView): ...@@ -167,7 +166,6 @@ class GroupsDetail(APIView):
** Related Courses (/api/groups/{group_id}/courses) ** Related Courses (/api/groups/{group_id}/courses)
** Related Groups(/api/groups/{group_id}/groups) ** Related Groups(/api/groups/{group_id}/groups)
""" """
permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id): def post(self, request, group_id):
""" """
...@@ -231,7 +229,7 @@ class GroupsDetail(APIView): ...@@ -231,7 +229,7 @@ class GroupsDetail(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class GroupsUsersList(APIView): class GroupsUsersList(SecureAPIView):
""" """
### The GroupsUserList view allows clients to interact with the set of User entities related to the specified Group ### The GroupsUserList view allows clients to interact with the set of User entities related to the specified Group
- URI: ```/api/groups/{group_id}/users/``` - URI: ```/api/groups/{group_id}/users/```
...@@ -248,7 +246,6 @@ class GroupsUsersList(APIView): ...@@ -248,7 +246,6 @@ class GroupsUsersList(APIView):
* For example, as a newly-added member of a 'workgroup' group, a User could be presented with a list of their peers * For example, as a newly-added member of a 'workgroup' group, a User could be presented with a list of their peers
* Once a User Group exists, you can additionally link to Courses and other Groups (see GroupsCoursesList, GroupsGroupsList) * Once a User Group exists, you can additionally link to Courses and other Groups (see GroupsCoursesList, GroupsGroupsList)
""" """
permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id): def post(self, request, group_id):
""" """
...@@ -304,7 +301,7 @@ class GroupsUsersList(APIView): ...@@ -304,7 +301,7 @@ class GroupsUsersList(APIView):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class GroupsUsersDetail(APIView): class GroupsUsersDetail(SecureAPIView):
""" """
### The GroupsUsersDetail view allows clients to interact with a specific Group-User relationship ### The GroupsUsersDetail view allows clients to interact with a specific Group-User relationship
- URI: ```/api/groups/{group_id}/users/{user_id}``` - URI: ```/api/groups/{group_id}/users/{user_id}```
...@@ -314,7 +311,6 @@ class GroupsUsersDetail(APIView): ...@@ -314,7 +311,6 @@ class GroupsUsersDetail(APIView):
* Use the GroupsUsersDetail to validate that a User is a member of a specific Group * Use the GroupsUsersDetail to validate that a User is a member of a specific Group
* Cancelling a User's membership in a Group is as simple as calling DELETE on the URI * Cancelling a User's membership in a Group is as simple as calling DELETE on the URI
""" """
permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, user_id): def get(self, request, group_id, user_id):
""" """
...@@ -350,7 +346,7 @@ class GroupsUsersDetail(APIView): ...@@ -350,7 +346,7 @@ class GroupsUsersDetail(APIView):
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
class GroupsGroupsList(APIView): class GroupsGroupsList(SecureAPIView):
""" """
### The GroupsGroupsList view allows clients to interact with the set of Groups related to the specified Group ### The GroupsGroupsList view allows clients to interact with the set of Groups related to the specified Group
- URI: ```/api/groups/{group_id}/groups/``` - URI: ```/api/groups/{group_id}/groups/```
...@@ -381,7 +377,6 @@ class GroupsGroupsList(APIView): ...@@ -381,7 +377,6 @@ class GroupsGroupsList(APIView):
** GET /groups/987/groups/246 -> 200 OK ** GET /groups/987/groups/246 -> 200 OK
* Once a Group Group exists, you can additionally link to Users and Courses (see GroupsUsersList, GroupsCoursesList) * Once a Group Group exists, you can additionally link to Users and Courses (see GroupsUsersList, GroupsCoursesList)
""" """
permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id): def post(self, request, group_id):
""" """
...@@ -454,7 +449,7 @@ class GroupsGroupsList(APIView): ...@@ -454,7 +449,7 @@ class GroupsGroupsList(APIView):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class GroupsGroupsDetail(APIView): class GroupsGroupsDetail(SecureAPIView):
""" """
### The GroupsGroupsDetail view allows clients to interact with a specific Group-Group relationship ### The GroupsGroupsDetail view allows clients to interact with a specific Group-Group relationship
- URI: ```/api/groups/{group_id}/groups/{related_group_id}``` - URI: ```/api/groups/{group_id}/groups/{related_group_id}```
...@@ -466,7 +461,6 @@ class GroupsGroupsDetail(APIView): ...@@ -466,7 +461,6 @@ class GroupsGroupsDetail(APIView):
** Is the current course series linked to the specified workgroup? ** Is the current course series linked to the specified workgroup?
* To remove an existing Group-Group relationship, simply call DELETE on the URI * To remove an existing Group-Group relationship, simply call DELETE on the URI
""" """
permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, related_group_id): def get(self, request, group_id, related_group_id):
""" """
...@@ -520,7 +514,7 @@ class GroupsGroupsDetail(APIView): ...@@ -520,7 +514,7 @@ class GroupsGroupsDetail(APIView):
return Response({}, status=response_status) return Response({}, status=response_status)
class GroupsCoursesList(APIView): class GroupsCoursesList(SecureAPIView):
""" """
### The GroupsCoursesList view allows clients to interact with the set of Courses related to the specified Group ### The GroupsCoursesList view allows clients to interact with the set of Courses related to the specified Group
- URI: ```/api/groups/{group_id}/courses/``` - URI: ```/api/groups/{group_id}/courses/```
...@@ -536,7 +530,6 @@ class GroupsCoursesList(APIView): ...@@ -536,7 +530,6 @@ class GroupsCoursesList(APIView):
* Create a Group of Courses to model cases such as an academic program or topical series * Create a Group of Courses to model cases such as an academic program or topical series
* Once a Course Group exists, you can additionally link to Users and other Groups (see GroupsUsersList, GroupsGroupsList) * Once a Course Group exists, you can additionally link to Users and other Groups (see GroupsUsersList, GroupsGroupsList)
""" """
permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id): def post(self, request, group_id):
""" """
...@@ -595,7 +588,7 @@ class GroupsCoursesList(APIView): ...@@ -595,7 +588,7 @@ class GroupsCoursesList(APIView):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class GroupsCoursesDetail(APIView): class GroupsCoursesDetail(SecureAPIView):
""" """
### The GroupsCoursesDetail view allows clients to interact with a specific Group-Course relationship ### The GroupsCoursesDetail view allows clients to interact with a specific Group-Course relationship
- URI: ```/api/groups/{group_id}/courses/{course_id}``` - URI: ```/api/groups/{group_id}/courses/{course_id}```
...@@ -608,7 +601,6 @@ class GroupsCoursesDetail(APIView): ...@@ -608,7 +601,6 @@ class GroupsCoursesDetail(APIView):
* Removing a Course from a Group is as simple as calling DELETE on the URI * Removing a Course from a Group is as simple as calling DELETE on the URI
* Remove a course from the specified academic program * Remove a course from the specified academic program
""" """
permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, course_id): def get(self, request, group_id, course_id):
""" """
......
...@@ -3,7 +3,10 @@ import logging ...@@ -3,7 +3,10 @@ import logging
from django.conf import settings from django.conf import settings
from api_manager.utils import get_client_ip_address, address_exists_in_network
from rest_framework import permissions from rest_framework import permissions
from rest_framework.views import APIView
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -50,3 +53,34 @@ class ApiKeyHeaderPermission(permissions.BasePermission): ...@@ -50,3 +53,34 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
# Allow the request to take place # Allow the request to take place
return True return True
class IPAddressRestrictedPermission(permissions.BasePermission):
"""
Check for permissions by matching the request IP address
against the allowed ip address(s)
"""
def has_permission(self, request, view):
ip_address = get_client_ip_address(request)
allowed_ip_addresses = getattr(settings, 'ALLOWED_IP_ADDRESSES', None)
if allowed_ip_addresses:
for allowed_ip_address in allowed_ip_addresses:
if '/' in allowed_ip_address:
is_allowed = address_exists_in_network(ip_address, allowed_ip_address)
if is_allowed:
return is_allowed
else:
if ip_address == allowed_ip_address:
return True
log.warn("{} is not allowed to access Api".format(ip_address))
return False
else:
return True
class SecureAPIView(APIView):
"""
Inherited from APIView
"""
permission_classes = (ApiKeyHeaderPermission, IPAddressRestrictedPermission)
...@@ -10,14 +10,14 @@ from django.contrib.auth.models import AnonymousUser, User ...@@ -10,14 +10,14 @@ from django.contrib.auth.models import AnonymousUser, User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from api_manager.permissions import SecureAPIView
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from util.bad_request_rate_limiter import BadRequestRateLimiter from util.bad_request_rate_limiter import BadRequestRateLimiter
from api_manager.permissions import ApiKeyHeaderPermission
from api_manager.users.serializers import UserSerializer from api_manager.users.serializers import UserSerializer
from student.models import ( from student.models import (
LoginFailures, PasswordHistory LoginFailures, PasswordHistory
...@@ -40,8 +40,8 @@ def _generate_base_uri(request): ...@@ -40,8 +40,8 @@ def _generate_base_uri(request):
return resource_uri return resource_uri
class SessionsList(APIView): class SessionsList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request): def post(self, request):
""" """
...@@ -113,8 +113,8 @@ class SessionsList(APIView): ...@@ -113,8 +113,8 @@ class SessionsList(APIView):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class SessionsDetail(APIView): class SessionsDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, session_id): def get(self, request, session_id):
""" """
......
...@@ -3,7 +3,8 @@ from django.middleware.csrf import get_token ...@@ -3,7 +3,8 @@ from django.middleware.csrf import get_token
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from api_manager.permissions import SecureAPIView
from api_manager.permissions import ApiKeyHeaderPermission from api_manager.permissions import ApiKeyHeaderPermission
...@@ -23,9 +24,8 @@ def _generate_base_uri(request): ...@@ -23,9 +24,8 @@ def _generate_base_uri(request):
return resource_uri return resource_uri
class SystemDetail(APIView): class SystemDetail(SecureAPIView):
"""Manages system-level information about the Open edX API""" """Manages system-level information about the Open edX API"""
permission_classes = (ApiKeyHeaderPermission,)
def get(self, request): def get(self, request):
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
...@@ -37,9 +37,8 @@ class SystemDetail(APIView): ...@@ -37,9 +37,8 @@ class SystemDetail(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class ApiDetail(APIView): class ApiDetail(SecureAPIView):
"""Manages top-level information about the Open edX API""" """Manages top-level information about the Open edX API"""
permission_classes = (ApiKeyHeaderPermission,)
def get(self, request): def get(self, request):
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
......
...@@ -13,130 +13,69 @@ from django.test.utils import override_settings ...@@ -13,130 +13,69 @@ from django.test.utils import override_settings
TEST_API_KEY = "123456ABCDEF" TEST_API_KEY = "123456ABCDEF"
@override_settings(DEBUG=True, EDX_API_KEY=None) @override_settings(ALLOWED_IP_ADDRESSES=['127.0.0.1', '10.0.2.2', '192.168.0.0/24'])
class PermissionsTestsDebug(TestCase): class PermissionsTests(TestCase):
""" Test suite for Permissions helper classes """ """ Test suite for Permissions helper classes """
def setUp(self): def setUp(self):
self.test_username = str(uuid.uuid4()) self.test_username = str(uuid.uuid4())
self.username = self.test_username + str(randint(11, 99))
self.username = self.username[3:-1] # username is a 32-character field
self.test_password = str(uuid.uuid4()) self.test_password = str(uuid.uuid4())
self.test_email = str(uuid.uuid4()) + '@test.org' self.test_email = str(uuid.uuid4()) + '@test.org'
self.test_uri = '/api/users'
def do_post(self, uri, data): self.data = {'email': self.test_email, 'username': self.test_username, 'password': self.test_password}
"""Submit an HTTP POST request""" self.headers = {
headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Edx-Api-Key': str(TEST_API_KEY), 'X-Edx-Api-Key': str(TEST_API_KEY),
} }
response = self.client.post(uri, headers=headers, data=data)
return response
@override_settings(DEBUG=True, EDX_API_KEY=None)
def test_has_permission_debug_enabled(self): def test_has_permission_debug_enabled(self):
test_uri = '/api/users' response = self._do_post(self.test_uri, self.data, self.headers)
local_username = self.test_username + str(randint(11, 99))
local_username = local_username[3:-1] # username is a 32-character field
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
@override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
@override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
class PermissionsTestsApiKey(TestCase):
""" Test suite for Permissions helper classes """
def setUp(self):
self.test_username = str(uuid.uuid4())
self.test_password = str(uuid.uuid4())
self.test_email = str(uuid.uuid4()) + '@test.org'
def do_post(self, uri, data):
"""Submit an HTTP POST request"""
headers = {
'Content-Type': 'application/json',
'X-Edx-Api-Key': str(TEST_API_KEY),
}
response = self.client.post(uri, headers=headers, data=data)
return response
def test_has_permission_valid_api_key(self): def test_has_permission_valid_api_key(self):
test_uri = '/api/users' response = self._do_post(self.test_uri, self.data, self.headers)
local_username = self.test_username + str(randint(11, 99))
local_username = local_username[3:-1] # username is a 32-character field
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
@override_settings(DEBUG=False, EDX_API_KEY=None)
@override_settings(DEBUG=False, EDX_API_KEY=None)
class PermissionsTestDeniedMissingServerKey(TestCase):
""" Test suite for Permissions helper classes """
def setUp(self):
self.test_username = str(uuid.uuid4())
self.test_password = str(uuid.uuid4())
self.test_email = str(uuid.uuid4()) + '@test.org'
def do_post(self, uri, data):
"""Submit an HTTP POST request"""
headers = {
'Content-Type': 'application/json',
'X-Edx-Api-Key': str(TEST_API_KEY),
}
response = self.client.post(uri, headers=headers, data=data)
return response
def test_has_permission_missing_server_key(self): def test_has_permission_missing_server_key(self):
test_uri = '/api/users' response = self._do_post(self.test_uri, self.data, self.headers)
local_username = self.test_username + str(randint(11, 99))
local_username = local_username[3:-1] # username is a 32-character field
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@override_settings(DEBUG=False, EDX_API_KEY="67890VWXYZ")
@override_settings(DEBUG=False, EDX_API_KEY="67890VWXYZ") def test_has_permission_invalid_client_key(self):
class PermissionsTestDeniedMissingClientKey(TestCase):
""" Test suite for Permissions helper classes """
def setUp(self):
self.test_username = str(uuid.uuid4())
self.test_password = str(uuid.uuid4())
self.test_email = str(uuid.uuid4()) + '@test.org'
def do_post(self, uri, data):
"""Submit an HTTP POST request"""
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
response = self.client.post(uri, headers=headers, data=data) response = self._do_post(self.test_uri, self.data, headers)
return response self.assertEqual(response.status_code, 403)
def test_has_permission_invalid_client_key(self): @override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
test_uri = '/api/users' def test_has_permission_invalid_ip_address(self):
local_username = self.test_username + str(randint(11, 99)) response = self._do_post(self.test_uri, self.data, self.headers, ip_address={'REMOTE_ADDR': '192.1.122.22'})
local_username = local_username[3:-1] # username is a 32-character field
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
def test_has_permission_valid_ip_address(self):
response = self._do_post(self.test_uri, self.data, self.headers, ip_address={'REMOTE_ADDR': '127.0.0.1'})
self.assertEqual(response.status_code, 201)
@override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
def test_invalid_request_header_ip_address(self):
response = self._do_post(self.test_uri, self.data, self.headers, ip_address={'HTTP_X_FORWARDED_FOR': "192.0.0.2,102.0.0.22"})
self.assertEqual(response.status_code, 403)
@override_settings(DEBUG=False, EDX_API_KEY="67890VWXYZ") @override_settings(DEBUG=False, EDX_API_KEY="123456ABCDEF")
class PermissionsTestDeniedInvalidClientKey(TestCase): def test_valid_subnet_ip_address(self):
""" Test suite for Permissions helper classes """ response = self._do_post(self.test_uri, self.data, self.headers, ip_address={'REMOTE_ADDR': "192.168.0.1"})
def setUp(self): self.assertEqual(response.status_code, 201)
self.test_username = str(uuid.uuid4())
self.test_password = str(uuid.uuid4())
self.test_email = str(uuid.uuid4()) + '@test.org'
def do_post(self, uri, data): def _do_post(self, uri, data, headers, **kwargs):
"""Submit an HTTP POST request""" """Submit an HTTP POST request"""
headers = {
'Content-Type': 'application/json', ip_address = kwargs.get('ip_address', {})
'X-Edx-Api-Key': str(TEST_API_KEY), response = self.client.post(uri, headers=headers, data=data, **ip_address)
}
response = self.client.post(uri, headers=headers, data=data)
return response return response
def test_has_permission_invalid_client_key(self):
test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99))
local_username = local_username[3:-1] # username is a 32-character field
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 403)
...@@ -10,11 +10,12 @@ from django.conf import settings ...@@ -10,11 +10,12 @@ from django.conf import settings
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q from django.db.models import Q
from api_manager.permissions import SecureAPIView
from api_manager.models import GroupProfile from api_manager.models import GroupProfile
from api_manager.permissions import ApiKeyHeaderPermission
from courseware import module_render from courseware import module_render
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from courseware.views import get_module_for_descriptor, save_child_position, get_current_child from courseware.views import get_module_for_descriptor, save_child_position, get_current_child
...@@ -110,8 +111,8 @@ def _save_content_position(request, user, course_id, course_descriptor, position ...@@ -110,8 +111,8 @@ def _save_content_position(request, user, course_id, course_descriptor, position
return saved_content.id return saved_content.id
class UsersList(APIView): class UsersList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request): def post(self, request):
""" """
...@@ -211,9 +212,8 @@ class UsersList(APIView): ...@@ -211,9 +212,8 @@ class UsersList(APIView):
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
class UsersDetail(APIView): class UsersDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, user_id): def get(self, request, user_id):
""" """
GET retrieves an existing user from the system GET retrieves an existing user from the system
...@@ -377,9 +377,8 @@ class UsersDetail(APIView): ...@@ -377,9 +377,8 @@ class UsersDetail(APIView):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
class UsersGroupsList(APIView): class UsersGroupsList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, user_id): def post(self, request, user_id):
""" """
POST creates a new user-group relationship in the system POST creates a new user-group relationship in the system
...@@ -439,9 +438,8 @@ class UsersGroupsList(APIView): ...@@ -439,9 +438,8 @@ class UsersGroupsList(APIView):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
class UsersGroupsDetail(APIView): class UsersGroupsDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, user_id, group_id): def get(self, request, user_id, group_id):
""" """
GET retrieves an existing user-group relationship from the system GET retrieves an existing user-group relationship from the system
...@@ -472,9 +470,8 @@ class UsersGroupsDetail(APIView): ...@@ -472,9 +470,8 @@ class UsersGroupsDetail(APIView):
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
class UsersCoursesList(APIView): class UsersCoursesList(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, user_id): def post(self, request, user_id):
""" """
POST creates a new course enrollment for a user POST creates a new course enrollment for a user
...@@ -529,9 +526,8 @@ class UsersCoursesList(APIView): ...@@ -529,9 +526,8 @@ class UsersCoursesList(APIView):
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
class UsersCoursesDetail(APIView): class UsersCoursesDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def post(self, request, user_id, course_id): def post(self, request, user_id, course_id):
""" """
POST creates an ACTIVE course enrollment for the specified user POST creates an ACTIVE course enrollment for the specified user
...@@ -605,9 +601,8 @@ class UsersCoursesDetail(APIView): ...@@ -605,9 +601,8 @@ class UsersCoursesDetail(APIView):
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
class UsersCoursesGradesDetail(APIView): class UsersCoursesGradesDetail(SecureAPIView):
permission_classes = (ApiKeyHeaderPermission,) """ Inherit with SecureAPIView """
def get(self, request, user_id, course_id): def get(self, request, user_id, course_id):
""" """
GET returns the current gradebook for the user in a course GET returns the current gradebook for the user in a course
......
""" API implementation for Secure api calls. """
import socket
import struct
def address_exists_in_network(ip_address, net_n_bits):
"""
return True if the ip address exists in the subnet address
otherwise return False
"""
ip_address = struct.unpack('<L', socket.inet_aton(ip_address))[0]
net, bits = net_n_bits.split('/')
net_address = struct.unpack('<L', socket.inet_aton(net))[0]
net_mask = ((1L << int(bits)) - 1)
return ip_address & net_mask == net_address & net_mask
def get_client_ip_address(request):
"""
get the client IP Address
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip_address = x_forwarded_for.split(',')[-1].strip()
else:
ip_address = request.META.get('REMOTE_ADDR')
return ip_address
...@@ -573,6 +573,7 @@ if FEATURES.get('ENABLE_OAUTH2_PROVIDER'): ...@@ -573,6 +573,7 @@ if FEATURES.get('ENABLE_OAUTH2_PROVIDER'):
##### ADVANCED_SECURITY_CONFIG ##### ##### ADVANCED_SECURITY_CONFIG #####
ADVANCED_SECURITY_CONFIG = ENV_TOKENS.get('ADVANCED_SECURITY_CONFIG', {}) ADVANCED_SECURITY_CONFIG = ENV_TOKENS.get('ADVANCED_SECURITY_CONFIG', {})
ALLOWED_IP_ADDRESSES = ['127.0.0.1']
##### GOOGLE ANALYTICS IDS ##### ##### GOOGLE ANALYTICS IDS #####
GOOGLE_ANALYTICS_ACCOUNT = AUTH_TOKENS.get('GOOGLE_ANALYTICS_ACCOUNT') GOOGLE_ANALYTICS_ACCOUNT = AUTH_TOKENS.get('GOOGLE_ANALYTICS_ACCOUNT')
......
...@@ -310,3 +310,6 @@ try: ...@@ -310,3 +310,6 @@ try:
from .private import * # pylint: disable=import-error from .private import * # pylint: disable=import-error
except ImportError: except ImportError:
pass pass
########################## ALLOWED API USER IP ########################
ALLOWED_IP_ADDRESSES = ['127.0.0.1']
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment