I have Django1.9 middleware class:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
#request.context['token'] = token
The issue is:
- I would like to put token to some sort of context to pass it through the application flow.
- I avoid putting it into request session, because it result in extra database reading/writing.
Could you suggest me some solution?
You can add any attribute to the HttpRequest, so you can implement this with:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
request.token = token
or if you really want some sort of context dictionary:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
if not hasattr(request, 'context'):
request.context = {}
request.context['token'] = token
I have Django1.9 middleware class.
As is documented, django-1.9 is not supported anymore since April 2017, therefore I strongly advice to update.
Related
I have question related with Django .
I am using Knox Token Authentication to generate tokens for every user when he log in on the page.
Now I want to use that token for every request that will send so I can get the corresponding user for the token. Also I am using custom function example def dashboard(request) in Django for every URL route.
I have see on youtube that there are option to get user from token but is not with functions
class UserAPI(generics.RetrieveAPIView):
permission_classes = [
permissions.IsAuthenticated,
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
So is there a whey to get the corresponding user from a token within a custom function
Great, I figured out in hours that knox doesn't come with full token_key stored in database.
Real token we can get is something like:
a512529e7ffceaa8406ceb616d088b3422ad15811a5eb470e8f4c4896c9aa649
In database token_key is stored by default a512529e. 8 digits.
Filter objects using this:
knox_object = AuthToken.objects.filter(token_key__startswith=token[:8]).first()
Then get user object
knox_object.user.username
Or you can use this, faster
from knox.settings import CONSTANTS
knox_object = AuthToken.objects.filter(token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH]).first()
From the knox source codes
class CONSTANTS:
'''
Constants cannot be changed at runtime
'''
TOKEN_KEY_LENGTH = 8
DIGEST_LENGTH = 128
SALT_LENGTH = 16
def __setattr__(self, *args, **kwargs):
raise Exception('''
Constant values must NEVER be changed at runtime, as they are
integral to the structure of database tables
''')
CONSTANTS = CONSTANTS()
You can see TOKEN_KEY_LENGTH is of 8 digits.
I wrote a simple function to do that
from knox.models import AuthToken
from knox.settings import CONSTANTS
def get_user_from_token(token):
objs = AuthToken.objects.filter(token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH])
if len(objs) == 0:
return None
return objs.first().user
Life be easier now. :)
Yes, I improved it and published it.
You may try my fork. If you just simply want to add #smart_token_user before any GET/POST/PUT/... methods.
https://github.com/xros/django-rest-knox
Just git clone, and pip install ./
I wrote a decorator.
With this,
in our app views.py
we can easily get user object by doing so,#smart_token_user will modify the request handler. We can have a request.user attr only once the token is valid. And all invalid attempts will be thrown out with HTTP 401 Unauthorized response.
Life can be easier with this decorator.
from knox.models import smart_token_user
class CheckUserEmail(generics.RetrieveAPIView):
permission_classes = (IsAuthenticated,)
#smart_token_user
def get(self, request):
return Response({
"username": request.user.username,
"email": request.user.email,
"password": request.user.password,
}, status=status.HTTP_200_OK)
Or use this like original if you want: authentication_classes = (TokenAuthentication,)
class CheckUserEmail(generics.RetrieveAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
return Response({
"username": request.user.username,
"email": request.user.email,
"password": request.user.password,
}, status=status.HTTP_200_OK)
I have to override settings in a test in Django
#override_settings(XYZ_REDIRECT="http://localhost:8000")
#override_settings(TOKEN_TIMEOUT=0)
class CustomTestCase(TestCase):
def setUp(self):
self.token = self._generate_auth_token()
self.client = Client()
def test_token_expiry(self):
feoken_count = 0
client = Client()
client.post('/api/v1/auth/login/', {'token': 'AF'})
# Over HERE TOKEN_TIMEOUT is not changed
self.assertEqual(ABCN.objects.count(), feoken_count)
The override settings decorator, however seems not to work. In the other side of the route, I have this code.
from fetchcore_server.settings import AUTH0_SERVER_URL, TOKEN_TIMEOUT
....
def post(self, request, *args, **kwargs):
if 'token' in request.data:
FetchcoreToken.objects.filter(expiry_time__lte=timezone.now()).delete()
print TOKEN_TIMEOUT # this is still original value
token = request.data['token']
try:
fetchcore_token = FetchcoreToken.objects.get(token=token)
user = fetchcore_token.user
user_id = user.id
I tried using the with self.settings(TOKEN_TIMEOUT=0) but even that did not work.
I am not sure how I'm using this wrong
Django docs on the subject: https://docs.djangoproject.com/en/1.11/topics/testing/tools/
In case it is relevant, this is how I run the test
python manage.py test api.tests.integration.v1.users.AuthUserTestCase
You problem is that you are using import of settings directly,
from fetchcore_server.settings import AUTH0_SERVER_URL, TOKEN_TIMEOUT
but you should use settings object provided by django https://docs.djangoproject.com/en/1.11/topics/settings/#using-settings-in-python-code
from django.conf import settings
....
def post(self, request, *args, **kwargs):
if 'token' in request.data:
FetchcoreToken.objects.filter(expiry_time__lte=timezone.now()).delete()
print settings.TOKEN_TIMEOUT # this is still original value
token = request.data['token']
try:
fetchcore_token = FetchcoreToken.objects.get(token=token)
user = fetchcore_token.user
user_id = user.id
Also as a sidenote, you could provide all overloaded settings at once
#override_settings(XYZ_REDIRECT="http://localhost:8000", TOKEN_TIMEOUT=0)
class CustomTestCase(TestCase):
I've created a base api view, which extends from APIView, where I log response time, log request, and other common stuffs.
Now, I also want to add request validation here, using the Serializer defined in sub-class Views. I thought the appropriate place is to put that in dispatch() method. But before I call API.dispatch() method, request.data is not prepared. So, that won't work. Can someone help me in right direction as to how to move validation to a single place?
Here's the class structure:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
# Some code here
# How to use `validation_serializer` here, to validate request data?
# `request.data` is not available here.
response = super(BaseView, self).dispatch(request, *args, **kwargs)
# Some code here
return response
class MyView(BaseView):
validation_serializer = ViewValidationSerializer
def post(self, request, *args, **kwargs):
pass
I thought another approach could be use decorator on the top of post() method. But if only there was an cleaner way, than putting decorators all across the project?
Note: It's similar to the question here: Django - DRF - dispatch method flow. But as per the suggestion there, I don't want to just copy the entire dispatch method from DRF source code.
The method that processes the django request into a DRF request (and adds the request.data property) is the APIView.initialize_request . The APIView.dispatch() method calls it and then proceeds to call the appropriate method handler (post/patch/put).
You can try to do that yourself by calling it and using the returned object:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
kwargs['context'] = self.get_serializer_context()
serializer = self.validation_serializer(data=request.data, *args, **kwargs)
# use `raise_exception=True` to raise a ValidationError
serializer.is_valid(raise_exception=True)
response = super(BaseView, self).dispatch(request, *args, **kwargs)
return response
However, I would suggest against this, as other functionality of dispatch() probably should be performed prior to handling validation; thus, you could move the above logic to the relevant post/patch/put methods instead.
In these methods you can also use self.request directly since it was already initialized by dispatch().
I think drf-tracking does what you are looking for. You may want to check it out.
I don't think you're going about this the correct way. The best way to log the request, with validation is in your Authentication Class and add the audit log to the request.
Then you can use your APIView to log the render time, against the AuditLog generated in the Authentication Class.
Here's an example using Token Authentication, assuming each request has a header Authorization: Bearer <Token>.
settings.py
...
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'common.authentication.MyTokenAuthenticationClass'
),
...,
}
common/authentication.py
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions
from accounts.models import Token, AuditLog
class MyTokenAuthenticationClass(authentication.BaseAuthentication):
def authenticate(self, request):
# Grab the Athorization Header from the HTTP Request
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != b'bearer':
return None
# Check that Token header is properly formatted and present, raise errors if not
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
# Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
token.last_ip = get_real_ip(request)
token.last_login = timezone.now()
token.save()
# Add the saved token instance to the request context
request.token = token
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
# At this point, insert the Log into your AuditLog table and add to request:
request.audit_log = AuditLog.objects.create(
user_id=token.user,
request_payload=request.body,
# Additional fields
...
)
# Return the Authenticated User associated with the Token
return (token.user, token)
Now, you have access to the AuditLog in your request. So you can log everything before and after validation.
I have a middleware from secretballot
class SecretBallotMiddleware(object):
def process_request(self, request):
request.secretballot_token = self.generate_token(request)
def generate_token(self, request):
raise NotImplementedError
class SecretBallotIpMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
return request.META['REMOTE_ADDR']
class SecretBallotIpUseragentMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
s = ''.join((request.META['REMOTE_ADDR'], request.META.get('HTTP_USER_AGENT', '')))
return md5(s.encode('utf8')).hexdigest()
and I use this in my view (e.g. 'different_view'):
token = request.secretballot_token
How can I change this token form request in my tests?
class BasicTest(TestCase):
def test_one(self):
self.client.request['secretballot_token']='asd' #??
response = self.client.post('/different_view/')
And I want to send post in this test to /different_view/ but with my own, changed token.
If you're looking to the test the view without running through the middleware, you can use RequestFactory to generate a request and pass it directly into your view.
def test_one(self):
# create a request
request = RequestFactory().post('/different_view')
request.secretballot_token = 'asd'
# function based view
response = different_view(request)
# class based view
response = DifferentView.as_view()(request)
If you need to test the middleware along with the view, you should pass HTTP headers in your tests instead
def test_one(self):
# pass http headers
response = self.client.post(path='/different_view'/,
REMOTE_ADDR='12.34.56.78',
HTTP_USER_AGENT='...'
)
I have a custom session class that I've built to extend the Django SessionBase. I did this in order to reuse a legacy Session table, so that sessions can pass between our Django pages and our PHP pages without having the user to log in and back out.
Everything's working perfectly so, far with one huge BUT.
I wrote some custom middleware in order to let the SessionStore.start() function have access to the Request Object. Unfortunately, in order to do that I used this answer: Access request.session from backend.get_user in order to remedy my problem.
I have learned that using the above answer (Essentially binding the request object to the settings, so you can access using import settings* and then settings.request) is totally horrible and the absolutely worst way to do this.
My core problem, is I don't understand how I can access the request from within the custom session backend I've written.
Maybe in middleware you could pass request to your custom SessionStore like this:
request.session = engine.SessionStore(session_key,request)
and in SessionStore:
class SessionStore(SessionBase):
def __init__(self, session_key=None, request):
self.request = request
super(SessionStore, self).__init__(session_key)
Later you can access request as self.request.
Django's SessionMiddleware does this:
class SessionMiddleware(object):
def process_request(self, request):
engine = import_module(settings.SESSION_ENGINE)
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)
can't you do this?
import mycustomsessionbackend as myengine
class MyCustomSessionMiddleware(object):
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = myengine.SessionStore(session_key, request)
...
# mycustomsessionbackend.py
class SessionStore(SessionBase):
def __init__(self, session_key=None, request=None):
super(SessionStore, self).__init__(session_key)
self.request = request