views.py 8.77 KB
Newer Older
1 2 3 4
"""
Third Party Auth REST API views
"""
from django.contrib.auth.models import User
Pan Luo committed
5 6
from django.db.models import Q
from django.http import Http404
7
from rest_framework import exceptions, status
Pan Luo committed
8
from rest_framework.generics import ListAPIView
9 10
from rest_framework.response import Response
from rest_framework.views import APIView
Pan Luo committed
11
from rest_framework_oauth.authentication import OAuth2Authentication
12
from social_django.models import UserSocialAuth
13

14 15
from openedx.core.lib.api.authentication import (
    OAuth2AuthenticationAllowInactiveUser,
16
    SessionAuthenticationAllowInactiveUser
17
)
18
from openedx.core.lib.api.permissions import ApiKeyHeaderPermission
19
from third_party_auth import pipeline
Pan Luo committed
20 21 22
from third_party_auth.api import serializers
from third_party_auth.api.permissions import ThirdPartyAuthProviderApiPermission
from third_party_auth.provider import Registry
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98


class UserView(APIView):
    """
    List the third party auth accounts linked to the specified user account.

    **Example Request**

        GET /api/third_party_auth/v0/users/{username}

    **Response Values**

        If the request for information about the user is successful, an HTTP 200 "OK" response
        is returned.

        The HTTP 200 response has the following values.

        * active: A list of all the third party auth providers currently linked
          to the given user's account. Each object in this list has the
          following attributes:

            * provider_id: The unique identifier of this provider (string)
            * name: The name of this provider (string)
            * remote_id: The ID of the user according to the provider. This ID
              is what is used to link the user to their edX account during
              login.
    """
    authentication_classes = (
        # Users may want to view/edit the providers used for authentication before they've
        # activated their account, so we allow inactive users.
        OAuth2AuthenticationAllowInactiveUser,
        SessionAuthenticationAllowInactiveUser,
    )

    def get(self, request, username):
        """Create, read, or update enrollment information for a user.

        HTTP Endpoint for all CRUD operations for a user course enrollment. Allows creation, reading, and
        updates of the current enrollment for a particular course.

        Args:
            request (Request): The HTTP GET request
            username (str): Fetch the list of providers linked to this user

        Return:
            JSON serialized list of the providers linked to this user.

        """
        if request.user.username != username:
            # We are querying permissions for a user other than the current user.
            if not request.user.is_superuser and not ApiKeyHeaderPermission().has_permission(request, self):
                # Return a 403 (Unauthorized) without validating 'username', so that we
                # do not let users probe the existence of other user accounts.
                return Response(status=status.HTTP_403_FORBIDDEN)

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        providers = pipeline.get_provider_user_states(user)

        active_providers = [
            {
                "provider_id": assoc.provider.provider_id,
                "name": assoc.provider.name,
                "remote_id": assoc.remote_id,
            }
            for assoc in providers if assoc.has_account
        ]

        # In the future this can be trivially modified to return the inactive/disconnected providers as well.

        return Response({
            "active": active_providers
        })
Pan Luo committed
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205


class UserMappingView(ListAPIView):
    """
    Map between the third party auth account IDs (remote_id) and EdX username.

    This API is intended to be a server-to-server endpoint. An on-campus middleware or system should consume this.

    **Use Case**

        Get a paginated list of mappings between edX users and remote user IDs for all users currently
        linked to the given backend.

        The list can be filtered by edx username or third party ids. The filter is limited by the max length of URL.
        It is suggested to query no more than 50 usernames or remote_ids in each request to stay within above
        limitation

        The page size can be changed by specifying `page_size` parameter in the request.

    **Example Requests**

          GET /api/third_party_auth/v0/providers/{provider_id}/users

          GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1},{username2}

          GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1}&usernames={username2}

          GET /api/third_party_auth/v0/providers/{provider_id}/users?remote_id={remote_id1},{remote_id2}

          GET /api/third_party_auth/v0/providers/{provider_id}/users?remote_id={remote_id1}&remote_id={remote_id2}

          GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1}&remote_id={remote_id1}

    **URL Parameters**

        * provider_id: The unique identifier of third_party_auth provider (e.g. "saml-ubc", "oa2-google", etc.
          This is not the same thing as the backend_name.). (Optional/future: We may also want to allow
          this to be an 'external domain' like 'ssl:MIT' so that this API can also search the legacy
          ExternalAuthMap table used by Standford/MIT)

    **Query Parameters**

        * remote_ids: Optional. List of comma separated remote (third party) user IDs to filter the result set.
          e.g. ?remote_ids=8721384623

        * usernames: Optional. List of comma separated edX usernames to filter the result set.
          e.g. ?usernames=bob123,jane456

        * page, page_size: Optional. Used for paging the result set, especially when getting
          an unfiltered list.

    **Response Values**

        If the request for information about the user is successful, an HTTP 200 "OK" response
        is returned.

        The HTTP 200 response has the following values:

        * count: The number of mappings for the backend.

        * next: The URI to the next page of the mappings.

        * previous: The URI to the previous page of the mappings.

        * num_pages: The number of pages listing the mappings.

        * results:  A list of mappings returned. Each collection in the list
          contains these fields.

            * username: The edx username

            * remote_id: The Id from third party auth provider
    """
    authentication_classes = (
        OAuth2Authentication,
    )

    serializer_class = serializers.UserMappingSerializer
    provider = None

    def get_queryset(self):
        provider_id = self.kwargs.get('provider_id')

        # permission checking. We allow both API_KEY access and OAuth2 client credential access
        if not (
                self.request.user.is_superuser or ApiKeyHeaderPermission().has_permission(self.request, self) or
                ThirdPartyAuthProviderApiPermission(provider_id).has_permission(self.request, self)
        ):
            raise exceptions.PermissionDenied()

        # provider existence checking
        self.provider = Registry.get(provider_id)
        if not self.provider:
            raise Http404

        query_set = UserSocialAuth.objects.select_related('user').filter(provider=self.provider.backend_name)

        # build our query filters
        # When using multi-IdP backend, we only retrieve the ones that are for current IdP.
        # test if the current provider has a slug
        uid = self.provider.get_social_auth_uid('uid')
        if uid is not 'uid':
            # if yes, we add a filter for the slug on uid column
            query_set = query_set.filter(uid__startswith=uid[:-3])

        query = Q()

206 207
        usernames = self.request.query_params.getlist('username', None)
        remote_ids = self.request.query_params.getlist('remote_id', None)
Pan Luo committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

        if usernames:
            usernames = ','.join(usernames)
            usernames = set(usernames.split(',')) if usernames else set()
            if len(usernames):
                query = query | Q(user__username__in=usernames)

        if remote_ids:
            remote_ids = ','.join(remote_ids)
            remote_ids = set(remote_ids.split(',')) if remote_ids else set()
            if len(remote_ids):
                query = query | Q(uid__in=[self.provider.get_social_auth_uid(remote_id) for remote_id in remote_ids])

        return query_set.filter(query)

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class with current provider. We need the provider to
        remove idp_slug from the remote_id if there is any
        """
        context = super(UserMappingView, self).get_serializer_context()
        context['provider'] = self.provider

        return context