Access URL Parameter Variables in Custom ScopedRateThrottle - django

I'm using Django Framework and Django REST Framework.
I have an endpoint that needs to be throttled using the (user, token) pair, with user being the user making the request and token being the URL variable specified in the urls.py
url(r'^api/v2/(?P<token>\w+)/action$', ActionEndpoint.as_view())
I have created a custom ScopedRateThrottle to accomplish this:
class CustomThrottle(ScopedRateThrottle):
rate = '2/day'
def get_cache_key(self, request, view):
user_id = request.user.pk
token = (?????????)
identity = "%s_%s" % (user_id, token)
cache_key = self.cache_format % {
'scope': self.scope,
'ident': identity
}
return cache_key
Question: How should I retrieve the token variable from the request object in this scenario?

You should use the view object to retrieve parameters from the url() pattern:
token = view.kwargs['token']

Related

How to add token in response after registering user - DRF Social OAuth2

I am using DRF Social OAuth2 for social authentication, it's giving me the token when I log in, but I want to return the token in response when I register a user with an email and password. How can I do that
We would need to see your endpoint in order to answer the question better. Here is a suggestion if you are using token auth.
from rest_framework.authtoken.models import Token
def get_token_response(user):
token, _ = Token.objects.get_or_create(user=user)
response = {"token": "Token " + str(token)}
return response
And then your endpoint would look something like this (if you are using a viewset):
class UserViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
user = User.objects.get(id=response.data["id"])
return Response(get_token_response(user), status=201)
My point is that you need to get the token from the database and adjust your create user endpoint (aka registration) to return it.
Hope this helps.

Store extra data like refresh_token when signing up via access_token

I am trying to signup using access token for google. My frontend is next.js with next-auth.js fetching the access_token, refresh_token, etc. I am using python social auth with Django to persist the user information. I use the access_token provided by next-auth to signup the user in Django. How do I make sure that the other response fields like refresh_token, and expires are saved in the Django DB? I can pass the required fields from next-auth to an API in Django but not sure what the expected format is.
https://python-social-auth.readthedocs.io/en/latest/use_cases.html#signup-by-oauth-access-token
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)
Sounds like you need a table to store these tokens (not sure if you have one already), lets take this model as an example:
class StoredToken(models.Model):
refresh_token = models.CharField()
access_token = models.CharField()
# Maybe you need the related user too?
user = models.ForeignKey(User, on_delete=models.CASCADE)
After you created your table, all you need is to save your data when you have it, from your example:
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
update_last_login(None, user)
StoredToken.objects.create(refresh_token=refresh, access_token=refresh.access_token, user=user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
You obviously need to do migrations to generate your table, but I assume you have the knowledge to do that. Otherwise refer to the django documentation to create models
I was able to store the refresh token along with other EXTRA_DATA in python social auth by adding response kwarg. This response kwarg is nothing but the dictionary containing the extra data.
For example: With Google, response is the return value of https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
#psa
def register_by_access_token(request, backend):
# If it's needed, request.backend and request.strategy will be loaded with the current
# backend and strategy.
response = json.loads(request.body.decode('utf-8'))
user = request.backend.do_auth(response['access_token'], response=response)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)

Get the value of the token from authentication in Django

I have made a customized token Authentication for my app. It is based on a specific model that stores the tokens that I will be generating.
class ApiKeyAuthentication(TokenAuthentication):
keyword = "api-key"
def get_token_from_auth_header(self, auth):
auth = auth.split()
#validations here
def authenticate(self, request):
auth = get_authorization_header(request)
token = self.get_token_from_auth_header(auth)
if token:
return self.authenticate_credentials(token)
def authenticate_credentials(self, key):
try:
token = TokenModel.objects.get(key=key)
except TokenModel.DoesNotExist:
raise exceptions.AuthenticationFailed("Invalid Api key.")
user = User.objects.first()
return (user, token)
Is there a possibility to get the values of the token that was returned upon authenticate_credentials? I have to access a field from that model ( TokenModel ) on the views.py.
You can dynamically add an attribute to the request object.
def authenticate(self, request):
auth = get_authorization_header(request)
token = self.get_token_from_auth_header(auth)
if token:
usr, tok = self.authenticate_credentials(token)
setattr(request, 'token', tok)
return (usr, tok)
It is not the best nor the prettiest solution, but it does allow you to retrieve the TokenModel from the request object in your views.py.
I tried to do some research on this to see what is the best way in accessing the returned parameters. There is no need to manually set the token instance on your request. You can access it through request.auth. With that, you can access the passed parameters on your views.

Generate access token using user instance (not username, password and grant_type)

I'm using Django REST Framework and using this library to provide token based authentication to the frontend applications.
There is Login with Google implementation using django-allauth plugin.
I want to generate access token when user login using social account.
For handling social login and generating social account, I have created this view.
class GoogleLoginView(LoginView):
"""
Enable login using google
"""
adapter_class = GoogleOAuth2Adapter
serializer_class = CustomSocialLoginSerializer
def login(self):
self.user = self.serializer.validated_data['user']
self.token = TokenView().create_token_response(self.request)
return self.token
def post(self, request, *args, **kwargs):
self.request = request
self.serializer = self.get_serializer(
data=self.request.data,
context={'request': request}
)
self.serializer.is_valid(raise_exception=True)
url, header, body, status_ = self.login()
return Response(json.loads(body), status=status_)
The request data has user instance along with client_id and client_secret of the application.
But this gives error
'{"error": "unsupported_grant_type"}'
Version
django-oauth-toolkit==1.3.0
Got it solved by passing client_id and client_secret along with the social network access token and append other fields in the view like
def login(self):
self.user = self.serializer.validated_data['user']
# Store request
request = self.request
# Change request data to mutable
request.data._mutable = True
# Add required data to the request
request.data['grant_type'] = 'password' # Call Password-owned grant type
request.data['username'] = self.user.username # Fake request data to oauth-toolkit
request.data['password'] = '-' # Fake request data to oauth-toolkit
request.data['social_login'] = True # Important, if not set will use username, password
request.data['user'] = self.user # Important, assign user obj
# Change request data to non-mutable
request.data._mutable = False
# Generate token
self.token = TokenView().create_token_response(request)
return self.token

Generating an access token programatically with Django OAuth2 Toolkit

I'm using Python Social Auth and Django OAuth Toolkit to manage my user accounts and restrict access to my REST API.
I can create a token for users that sign up manually with my app by using the regular
curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/
But when I register my users with PSA by their access token, I'd like to create a OAuth2 Toolkit token for my own app and return it as JSON to the client so it can use it for making requests with my API.
Presently, I generate token simply using generate_token from oauthlib, is that good practice? Should I take into consideration other factors?
from oauthlib.common import generate_token
...
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
login(request, user)
app = Application.objects.get(name="myapp")
# We delete the old one
try:
old = AccessToken.objects.get(user=user, application=app)
except:
pass
else:
old.delete()
# We create a new one
tok = generate_token()
AccessToken.objects.get_or_create(user=user,
application=app,
expires=now() + timedelta(days=365),
token=tok)
return "OK" # I will eventually return JSON with the token
else:
return "ERROR"
I've recently used https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 for this purpose like user Felix D. suggested. Below is my implementation:
class TokenHandler:
application = Application.objects.filter(name=APPLICATION_NAME).values('client_id', 'client_secret')
def handle_token(self, request):
"""
Gets the latest token (to access my API) and if it's expired, check to see if the social token has expired.
If the social token has expired, then the user must log back in to access the API. If it hasn't expired,
(my) token is refreshed.
"""
try:
token_list = AccessToken.objects.filter(user=request.user)\
.order_by('-id').values('token', 'expires')
if token_list[0]['expires'] < datetime.now(timezone.utc):
if not self.social_token_is_expired(request):
token = self.refresh_token(request)
else:
token = 'no_valid_token'
else:
token = token_list[0]['token']
except IndexError: # happens where there are no old tokens to check
token = self.convert_social_token(request)
except TypeError: # happens when an anonymous user attempts to get a token for the API
token = 'no_valid_token'
return token
def convert_social_token(self, request):
grant_type = 'convert_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
user_social_auth = request.user.social_auth.filter(user=request.user).values('provider', 'extra_data')[0]
backend = user_social_auth['provider']
token = user_social_auth['extra_data']['access_token']
url = get_base_url(request) + reverse('convert_token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'backend': backend,
'token': token}
if backend == 'azuread-oauth2':
fields['id_token'] = user_social_auth['extra_data']['id_token']
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except IndexError:
return {'error': 'You must use an OAuth account to access the API.'}
except UserSocialAuth.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
def refresh_token(self, request):
grant_type = 'refresh_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
refresh_token_object = RefreshToken.objects.filter(user=request.user).order_by('-id').values('token')[0]
token = refresh_token_object['token']
url = get_base_url(request) + reverse('token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'refresh_token': token}
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except RefreshToken.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
#staticmethod
def social_token_is_expired(request):
user_social_auth = UserSocialAuth.objects.filter(user=request.user).values('provider', 'extra_data')[0]
try:
return float(user_social_auth['extra_data']['expires_on']) <= datetime.now().timestamp()
except KeyError: # social API did not provide an expiration
return True # if our token is expired and social API did not provide a time, we do consider them expired