Reputation: 713
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?
Upvotes: 2
Views: 1978
Reputation: 509
Answering on how to extend IntrospectTokenView
to override get_token_response
method. Make a new "oauth2" directory on the same level as the other Django app folders. Add new empty __init__.py
and views.py
files.
oauth2/views.py:
from calendar import timegm
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
from oauth2_provider.views.introspect import IntrospectTokenView
from oauth2_provider.models import get_access_token_model
class CustomIntrospectView(IntrospectTokenView):
@staticmethod
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 JsonResponse({"active": False}, status=401)
else:
if token.is_valid():
data = {
"active": True,
"scope": token.scope,
"exp": int(timegm(token.expires.timetuple())),
}
if token.application:
data["client_id"] = token.application.client_id
if token.user:
data["username"] = token.user.get_username()
# add extra key-value pairs here:
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 JsonResponse(data)
else:
return JsonResponse({"active": False})
Depending on how you include oauth2_views
to main project urls.py
you need to include this modified view class. If you follow Django Oauth Toolkit documentation, you need to change:
...
urlpatterns = [
path('admin/', admin.site.urls),
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
# ...
]
To:
from django.contrib import admin
from django.urls import path, include
from oauth2.views import CustomIntrospectView
from oauth2_provider import views as oauth2_views
oauth2_endpoint_views = [
path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"),
path('token/', oauth2_views.TokenView.as_view(), name="token"),
path('revoke-token/', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
path('introspect/', CustomIntrospectView.as_view(), name="introspect"),
]
urlpatterns = [
path('admin/', admin.site.urls),
path('o/', include((oauth2_endpoint_views, 'oauth2_provider'), namespace='oauth2_provider')),
# ...
]
Reference used: https://django-oauth-toolkit.readthedocs.io/en/latest/advanced_topics.html#overriding-views
Upvotes: 0
Reputation: 713
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.
Upvotes: 3