So far in our code we have implemented Django built-in signals for handling user login, logout, and failed login as well as db actions with pre-save, post_save and post_delete signals. I am looking to maintain consistency by performing some operations on the http response after a request has been made using signals. However, I do not see any built-in signal that grants access to the response object. I am aware of the request_finished signal but this does not seem to provide access to the response object. I have successfully achieved what I need using Django's process_response function in middleware but was wondering if there was a way I could maintain consistency and use a signal here as well.
We are using Django 2.2 and DRF 3.8.2
Related
I use tornado + sockjs for websockets and Django rest framework for main app.
Also I use rest-framework-jwt for auth on Django app.
Now I have to determine user in tornado. How I see it:
User send jwt in message when sockjs connected to tornado server.
Tornado server parse jwt and determine is valid jwt or not? But for this solution I have to read database. But this operation is sync which is not good, because tornado is async.
Also I thought use Celery. When user connected to tornado, tornado creats task for celery and in this task jwt will be parsed. In that case solution is not blocking tornado. But how then to notice user via websockets about check jwt?
UPDATE:
You shouldn't need to read database to validate a JWT. JWT is a signed token, all you need is the secret key to validate the JWT.
OLD ANSWER:
(Note: The diagram below is rather redundant for JWT, as a JWT is already a signed token and can be validated without needing to be saved anywhere. This diagram, however, works for cookie based auth.)
You can share auth tokens between Django and Tornado using a message broker (such as Redis):
Redis is fast and lightweight and you can connect with it asynchronously from Tornado.
There's no need for Celery here.
If will you use SQLAlchemy, to connect and handler database, you could use tornado-sqlalchemy to make the async query on the database and notify your user via WebSocket without break event loop of the tornado.
#coroutine
def open(self):
with self.make_session() as session:
jtw = request.params.get('authorization')
jwt_valid = yield self.check_jwt_valid(jwt, session)
if not jwt_valid:
self.write('JWT Inválid')
self.close()
#coroutine
def check_jwt_valid(self, jwt, session):
jwt_found = session.query(JWT_QUERY...).first()
return jwt_found
Why does Django REST Framework implement a different Authentication mechanism than the built-in Django mechanism?
To wit, there are two settings classes that one can configure:
settings.AUTHENTICATION_BACKENDS which handles the Django-level authentication, and
settings.REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] which authenticates at the REST-Framework level
The problem I'm experiencing is that I have a Middleware layer which checks whether a user is logged-in or not.
When using a web client which authenticates via sessions, this works fine. However, from mobile or when running the test suite (i.e. authenticating using HTTP headers and tokens), the middleware detects the user as an AnonymousUser, but by the time we get to the REST Framework layer, the HTTP Authorization header is read, and the user is logged-in.
Why do these not both happen BEFORE the middleware? Furthermore, why doesn't REST Framework's authentication methods not rely on the Django authentication backend?
Django Rest Framework does not perform authentication in middleware by default for the same reason that Django does not perform authentication in middleware by default: middleware applies to ALL views, and is overkill when you only want to authenticate access to a small portion of your views. Also, having the ability to provide different authentication methods for different API endpoints is a very handy feature.
Rest Framework's authentication methods do not rely on the Django authentication backend because the Django's backend is optimised for the common case, and is intimitely linked to the user model. Rest Framework aims to make it easy to:
Use many different authentication methods. (You want HMAC based authentication? done! This is not possible with django auth framework)
Serve API data without ever needing a database behind it. (You have a redis database with all your data in-memory? Serve it in milliseconds without ever waiting for a round trip to DB user model.)
Thomas' answer explains the why quite well. My answer deals more with what you can do about it. I also have a middleware class that determines the current user. I need this because we have some automatic processes (cron) and I want to attribute these to a super user, and I have some model functions that don't have access to the request object but need to know who the current user is for various functions. So this created some issues as you noticed when we added an API via the REST framework. Here's my solution (yours will need to be adjusted to your use case):
from threading import local
_user = local() # thread-safe storage for current user used by middleware below
class CurrentUserMiddleware(object):
""" Defines Middleware for storing the currently active user """
def process_request(self, request):
if not request:
# Clear the current user if also clearing the request.
_user.value = 1 # One represents automatic actions done by super admin
else:
_user.value = request.user
if not _user.value or not _user.value.is_authenticated:
try:
authenticator = TokenAuthentication()
auth = authenticator.authenticate(request)
if auth:
_user.value = auth[0]
except Exception as e:
pass
def process_response(self, request, response):
_user.value = None
return response
The authenticator bit deals with seeing if the request has been authenticated by the REST Framework and getting the user.
I have created a REST web service using Django. This web service has a log file. I'd like to log all web service (http) requests in the log file. However, the web service request handling is done by Django, I only setup url-request handlers mapping and create the request handlers (views in Django nomenclature). Is there a way to log all requests in a central point, without needing to log each request in its associated request handler (view)?
Thanks in advance.
Yes Django has a built in signals framework.
It allows you to register a function to be called everytime a request starts.
This documenation page explains how to do it step by step
Using the decorator method:
from django.core.signals import request_started
from django.dispatch import receiver
#receiver(request_started)
def my_callback(sender, **kwargs):
# log the request here
pass
Where should this code live? You can put signal handling and
registration code anywhere you like. However, you’ll need to make sure
that the module it’s in gets imported early on so that the signal
handling gets registered before any signals need to be sent. This
makes your app’s models.py a good place to put registration of signal
handlers.
Are there basic authentication examples with Django and Tastypie?. I'm a little bit confused about how the authentication in Django works, specially with Tastypie.I wanna know how the authentication works with api keys and how to authenticate a user with the built-in User model which Django has. Any suggestion or code are really appreciated.
Thanks.
Just to answer your questions regarding authentication:
How the authentication in Django works?
Django authentication required SessionMiddleware to work. Once a session has been loaded, the Django authentication backend reads a special cookie _auth_user (IIRC) which contains currently logged in user's ID. If you have access to the django shell, you can manipulate it and make yourself logged in as any user! Once the backend notices there is a _auth_user key, it then adds a lazy User object to the request (so it delays the User.objects.get(...) until it is really needed). If there is no such key in the session dict, the user is claimed to be anonymous and an instance of AnonymousUser is added to the request object instead.
How does the authentication work in Tastypie?
Before your resource view is executed, a Resource.is_authenticated(request) method is called, which in turn calls the is_authenticated(request) method of the authentication backend of your the Resource of your choice. If the method returns False, the authentication is claimed to be failed and returns with Unauthorized error. If the method returns a HttpResponse, the response is returned instead. If the method returns True, the request is claimed to have been authenticated.
How does User model authentication work in Tastypie?
The User model authentication can be performed using SessionAuthentication backend provided by the Tastypie itself. What it does is creating a session for the current request so that the authentication middleware can then automatically insert relevant user model to the request. Notice that for this method to work, your API client has to support storing cookies and resending them in future requests.
You might find this useful. It allows you to authenticate the user based on the Django session cookie.
https://github.com/amezcua/TastyPie-DjangoCookie-Auth/blob/master/DjangoCookieAuth.py
I am using this in my application and it works!
I'm attempting to expose a single API call using three different authentication mechanisms: django's login_required , HTTP basic auth, and OAuth. I have decorators for all three but can't quite figure out how to have them all get along smoothly.
The required logic is to allow access to the view if any of the decorators / authentication mechanisms are valid for the user's request - basically an OR. However, if I simply include all three decorators then they all want to be satisfied before letting the request through - an AND.
What's a good way to deal with this?
I'm not sure you can. Suppose the user isn't logged in: if using login_required the server would redirect to a login form, whereas using basic auth, the server would return a 401 error page with a WWW-Authenticate response header. Which of these do you want to happen? I don't see how it could be both.