Search code examples
pythondjangooauth-2.0django-oauthdjango-oauth-toolkit

Django Oauth Toolkit: User data over introspection


Current Scenario:

I'm using Introspect to validate access token on the authentication server. This call returns only 'username' of the user from the authentication server and saves it in the resource server. The Id of the same user on the authentication server and the resource server are no necessarily the same.

Desired Scenario:

I want to receive more data about the user (email, phone number, address, etc..) and save it in the resource server.

What I have done so far:

I modified the django-oauth-toolkit/oauth2_provider/views/introspect.py/ get_token_response to return the data I need.

What is remaining:

How do I save those data in the resource server? or is it better to make an api call to the authentication server whenever I require the user data?


Solution

  • I achieved this by modifying get_token_response in IntrospectTokenView in the Auth-Server

    def get_token_response(token_value=None):
            try:
                token = get_access_token_model().objects.select_related(
                    "user", "application"
                    ).get(token=token_value)
            except ObjectDoesNotExist:
                return HttpResponse(
                    content=json.dumps({"active": False}),
                    status=401,
                    content_type="application/json"
                )
            else:
                if token.is_valid():
                    data = {
                        "active": True,
                        "scope": token.scope,
                        "exp": int(calendar.timegm(token.expires.timetuple())),
                    }
                    if token.application:
                        data["client_id"] = token.application.client_id
                    if token.user:
                        data["username"] = token.user.get_username()
    # TODO: DANGER ZONE
    # Pass extra parameters
    # ------------------------------------------------------------------------------
                        data["email"] = token.user.email
                        data["phone_number"] = token.user.phone_number
                        data["is_company"] = token.user.is_company
                        customer = token.user.customer
                        data["designation"] = customer.designation
                        company = customer.company
                        data["company"] = company.company_name
    # ------------------------------------------------------------------------------
                    return HttpResponse(content=json.dumps(data), status=200, content_type="application/json")
                else:
                    return HttpResponse(content=json.dumps({
                        "active": False,
                    }), status=200, content_type="application/json")
    

    and _get_token_from_authentication_server in OAuth2Validator in the Resource-Server

    def _get_token_from_authentication_server(
                self, token, introspection_url, introspection_token, introspection_credentials
        ):
            headers = None
            if introspection_token:
                headers = {"Authorization": "Bearer {}".format(introspection_token)}
            elif introspection_credentials:
                client_id = introspection_credentials[0].encode("utf-8")
                client_secret = introspection_credentials[1].encode("utf-8")
                basic_auth = base64.b64encode(client_id + b":" + client_secret)
                headers = {"Authorization": "Basic {}".format(basic_auth.decode("utf-8"))}
    
            try:
                response = requests.post(
                    introspection_url,
                    data={"token": token}, headers=headers
                )
            except requests.exceptions.RequestException:
                log.exception("Introspection: Failed POST to %r in token lookup", introspection_url)
                return None
    
            # Log an exception when response from auth server is not successful
            if response.status_code != http.client.OK:
                log.exception("Introspection: Failed to get a valid response "
                              "from authentication server. Status code: {}, "
                              "Reason: {}.".format(response.status_code,
                                                   response.reason))
                return None
    
            try:
                content = response.json()
            except ValueError:
                log.exception("Introspection: Failed to parse response as json")
                return None
    
            if "active" in content and content["active"] is True:
                if "username" in content:
                    user, _created = UserModel.objects.get_or_create(
                        **{UserModel.USERNAME_FIELD: content["username"]}
                    )
    # TODO: DANGER ZONE
    # Adding extra data to user profile and create company
    # ------------------------------------------------------------------------------
                    user.email = content["email"]
                    user.phone_number = content["phone_number"]
                    user.is_company = content["is_company"]
    
                    customer, _created_customer = CustomerModel.objects.get_or_create(
                        user = user
                    )
                    customer.designation = content["designation"]
    
                    company, _created_company = CompanyModel.objects.get_or_create(
                        company_name = content["company"]
                    )
                    customer.company = company
    
                    customer.save()
                    user.save()
    # ------------------------------------------------------------------------------
                else:
                    user = None
    
                max_caching_time = datetime.now() + timedelta(
                    seconds=oauth2_settings.RESOURCE_SERVER_TOKEN_CACHING_SECONDS
                )
    
                if "exp" in content:
                    expires = datetime.utcfromtimestamp(content["exp"])
                    if expires > max_caching_time:
                        expires = max_caching_time
                else:
                    expires = max_caching_time
    
                scope = content.get("scope", "")
                expires = make_aware(expires)
    
                access_token, _created = AccessToken.objects.update_or_create(
                    token=token,
                    defaults={
                        "user": user,
                        "application": None,
                        "scope": scope,
                        "expires": expires,
                    })
    
                return access_token
    

    . Now I'm wondering how can I extend the classes and add the extra codes instead of directly modifying the source code? Appreciate any help.