How to log a user out in django (without the request) - python

In django I can log a user out with:
from django.contrib.auth import logout
logout(request)
However, how would I manually log a use out -- for example I want to "sign users out of all tabs" -- that is, how do I flush all session for that user in my db?

You can add the following method to your user object:
from django.utils import timezone
from django.contrib.sessions.models import Session
class User(models.model):
...
def remove_all_sessions(self):
user_sessions = []
all_sessions = Session.objects.filter(expire_date__gte=timezone.now())
for session in Session.objects.all():
if str(self.pk) == session.get_decoded().get('_auth_user_id'):
user_sessions.append(session.pk)
return Session.objects.filter(pk__in=user_sessions).delete()

You can iterate over all sessions in DB, decode all of them, and delete those belonging to that user. But it's slow, particularly if your site has high traffic and there are lots of sessions.
If you need a faster solution, you can use a session backend that lets you query and get the sessions of a specific user. In these session backends, Session has a foreign key to User, so you don't need to iterate over all session objects:
django-qsessions (based on django's db, cached_db session backends)
django-user-sessions (based on django's db session backend)
Using these backends, deleting all sessions of a user can be done in a single line of code:
user.session_set.all().delete()
Disclaimer: I am the author of django-qsessions.

To understand this problem, consider what happens with the database backend. When a user logs in, Django adds a row to the django_session database table. Django updates this row each time the session data changes. If the user logs out manually, Django deletes the row. But if the user does not log out, the row never gets deleted. A similar process happens with the file backend.
Django does not provide automatic purging of expired sessions. Therefore, it’s your job to purge expired sessions on a regular basis. Django provides a clean-up management command for this purpose: clearsessions. It’s recommended to call this command on a regular basis, for example as a daily cron job.
Note that the cache backend isn’t vulnerable to this problem, because caches automatically delete stale data. Neither is the cookie backed, because the session data is stored by the users’ browsers.
so run:
$ ./manage.py clearsessions

Related

How to list available authentication backends for a Django user?

I have a project that uses Python 3.6 and Django 1.11 where I use the built-in User model.
The user objects are all inside the default database (which is postgres), but the project uses a second authentication backend because some users need to be authenticated against a legacy Oracle database.
# settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', # new postgres DB
'project_config.auth_backends.OtherBackend', # legacy Oracle DB
]
This works fine so far, but now I have 3 groups of users:
some users can only authenticate in ModelBackend because they are not in the legacy DB (because they are new users).
some users can only authenticate in the legacy DB; they have usr.has_usable_password() == False because they have not set their password in the new postgres DB yet.
some users can authenticate in both backends, maybe even with different passwords in each one; this is because they changed their password in the new system, but by design that change is not transmitted back to the legacy DB (don't shoot me, the only way to change the password in the legacy DB is to do it manually through the user interface).
For auditing purposes, I want to list all users and see which backends each one has available (ignoring the is_active flag for now) to make auditing tasks easier.
My idea was to use a loop similar to this one:
for usr in User.objects.all():
backend_list = []
if usr.has_usable_password():
backend_list.append('ModelBackend')
if ... : # what should I check here ?
backend_list.append('OtherBackend')
print(usr, backend_list)
I don't have the passwords for each user for the legacy database, so is may idea even possible?
I have not found a way, but I am open to suggestions.
In the end, I had to go with the suggestion from #ivissani and query the users table in the legacy Oracle DB:
select * from all_users;
With this information at hand, I could compare it to the users in the postgres DB and work out which users appear only in one or in both.

Django one user across multiple django sites

I have a case where I have three sites A, B, C . A is more of administrative site, B and C are consumer facing applications. Each of them have their own separate databases. What I want to do now is as follows:
I want all three A, B, C to be connected in such a way that when a new user registers at B and when registration is successful, the same user should be able to login into A or C. If there is any change in users details they reflect on A and C as well.
What I thought as solution till now is:
When a user registers at B, I can replicate user on A and C with same credentials, so this would allow user to login into any of the three sites.
I was also pondering upon idea of having a central database to store django auth models, but I am not sure in that case how authentication would work.
I need suggestion as to how this can be done. Also, does this qualify to be called as SSO?
In case if someone finds question to be misleading or vague or inappropriate do mention as to why before downvoting
There are two solutions to your problem:
Use routing to multiple databases. Django supports different databases for different models (more info on # https://docs.djangoproject.com/en/1.8/topics/db/multi-db/). So you could route all queries for your auth models to the authentication database. Django documentation already provides such a configuration # https://docs.djangoproject.com/en/1.8/topics/db/multi-db/#an-example. I've not tested this configuration however this will have the problem of not being able to use foreign keys to your user model (since it will be stored in a different database). To resolve this, you could have a UserProxy model (or something similarly named) in all your projects that will just keep the username of the User so you'll be able to create foreign key relations from your models to the UserProxy. Any user data that would need to be retrieved would be forwarded to the correct User object by the UserProxy.
Use django authentication. Django can be configured to use a number of different authentication methods (check the docs # https://docs.djangoproject.com/en/1.8/topics/auth/customizing/). For your sites B and C you can configure an authentication backend that uses the database of the administrative site (A) to login. I am using this method succesfully - here how you could do it:
class RegUsrBackend(django.contrib.auth.backends.ModelBackend):
def authenticate(self, username=None, password=None):
try:
conn = django.db.connections['users']
cursor = conn.cursor()
cursor.execute("select pn.password, pn.username, au.first_name, au.last_name from auth_user au where au.username = %s ", [username])
row = cursor.fetchone()
if row and check_password(password, row[0]):
user, created = get_user_model().objects.get_or_create(username = row[1] )
if user.first_name != row[2] or user.last_name != row[3] :
user.first_name = row[2]
user.last_name = row[3]
user.set_unusable_password()
user.save()
return user
except:
return None
As you can see, here I've also configured a different users database (named users) and I am issuing a raw query to this database to get the user with the passed username and its password hash. After that, I check if the user exists and has the correct password (using the check_password) and if everything checks, I use get_or_create to either retrieve or create the local instance (i.e the instance of the user in the application's database) of that user. Finally, before returning the user I check to see if there's a change in his first or last name to the administrative application and update them in the local one.
Finally, you need to put this backend in the in the AUTHENTICATION_BACKENDS settings of the applications using it.
Using this method you won't have to use any UserProxy to support foreign keys (since there will exist a local User model) models but I feel that it is a more hackish method than the first one. Also, if the details of a user has been changed in the administrative database the details in the others will be updated when he logs in. Also you could change your backend to something even more exotic, for example instead of querying the database you could create a REST api in your administrative backend and use this to login to the other applications.
Finally, to answer your question about SSO, what I've described above is not SSO. SSO means that when a user logs in to a site he won't have to log in again to the others becase a single session key is kept and shared in all these sites. A common solution for SSO is to use CAS (http://jasig.github.io/cas/4.1.x/index.html) however I don't have good experience with it (and you'll need to have another, java based server to host CAS).

Flask: How To Prevent Replay Attacks

I'm trying to implement a logic in my Flask application to prevent reply attacks.
Regarding to the question asked here, My idea is to set the current session lifetime when user logs out from the system. In general, it is suggested to set the session lifetime this way:
#app.before_request
def before_request():
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=10)
However, I want to set my current session life time when user logs out from the system. Something like the following code:
#app.after_request
def app_after_request(response):
response.headers["X-Frame-Options"] = "SAMEORIGIN"
if "__logged_out__" in session and session["__logged_out__"] is True:
session.clear()
response.set_cookie(app.session_cookie_name, '', expires=0)
return response
I also checked this question, but the problem is that I'm dealing with some confidential data and I have to ensure that session is cleared after user logged out from the system. Is there any way to set one session lifetime after creation manually? or is there any easy way to handle this situation with flask-login?
I found the solution. I should simply use Flask-KVSession package to store session data in database (or any other data storage) instead of server memory. As the package website introduced:
Flask-KVSession is an MIT-licensed server-side session replacement for
Flask‘s signed client-based session management. Instead of storing
data on the client, only a securely generated ID is stored on the
client, while the actual session data resides on the server.
You also need to create a key-value paired table in your database (it has named sessions by default, but you can change the name and schema as well) and point it to your flask app object. More information can be found here.

Django TestCase: don't flush the DB after each test

I'm working on a Django API project with a rather unusual configuration (I think):
I have indeed two Django projects: one is the main API and one is the user API. Whenever I create a user using the main API, the user is in fact created in the database of the user API (I communicate between the two API using http requests). In the main API, I keep a table of users that contains only a unique id. When the user is created in the user API, it's created with the same unique id as in the main API.
I have to do this because in production, I have to store the data in different servers.
Now comes my problem.
I want to write tests for my main API (for instance test the user creation, user update, user deletion). The problem is that when I run the tests, I have to run a separate instance of Django (using another port) that represents the user API. Running the tests, Django creates a test database for the main API but since I use http requests to communicate with the user API, there is no test database for the user API so I have to flush the DB after I run all the tests. Until now, I used the unittest library and everything was fine. But I would like to be able to override some settings during the tests (for instance, the address of the user API is a setting and I would like to have a different address for the tests). For that, I have to use django.test.TestCase but I have the following problem:
imagine I have a test_a method that creates a user A and a test_b method that creates a user B. With django.test.TestCase, test_a is run, user A is created with id 1. Then I believe that the test database (of the main API) is flushed because when test_b is run, user B is created with id 1 also. The problem is that between the two tests, the database of the user API is not flushed so I get an error because I cannot create user B in the test database.
I'm looking for an elegant way to deal with this problem but I really have not idea.
(Sorry, this is quite long but I wanted to be a little bit precise).
Can't you do the DB flushing in the setUp method of your TestCase? Since that method runs once before each test you can have a clean DB for test_a and test_b.
To flush the db using a bash script, you can use subprocess, like so:
def setUp(self):
import subprocess
subprocess.call(['<path-to-bash-script>', 'arg1', 'arg2'])

Somewhat confused about how auth and sessions work in Python/Django

I'm using an existing database for my latest Django project, so unless I change my Models or the Django auth code, it's going to be rather difficult to merge the two.
Rather than messing with the existing auth backend, I'm planning on just writing my own authentication app.
Anyway, all of my previous authentication apps have been written in PHP, where basically i just throw everything in session variables and verify them on every page... Here's what I'm a bit confused about. It appears that when a user is authenticated/logged in, the entire user is added to a session, but I can't figure out where or how that is occurring.
In the default Django login function, it's assigning user to request.user ... is this being saved as a session variable somehow or is it just passed into the next view? If it is just being passed to the next view, how are future requests authenticated without requiring further login requests?
The default Django auth login is below..
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request.
"""
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.id:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.id
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
I also tried to follow the user_logged_in.send(), which is in django.dispatch.dispatcher.send but I'm not entirely sure what that's supposed to do either.
def send(self, sender, **named):
"""
Send signal from sender to all connected receivers.
If any receiver raises an error, the error propagates back through send,
terminating the dispatch loop, so it is quite possible to not have all
receivers called if a raises an error.
Arguments:
sender
The sender of the signal Either a specific object or None.
named
Named arguments which will be passed to receivers.
Returns a list of tuple pairs [(receiver, response), ... ].
"""
responses = []
if not self.receivers:
return responses
for receiver in self._live_receivers(_make_id(sender)):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
return responses
Basically what I'm looking for is for someone to explain an efficient way to save user session data in Python that does not depend on the Django framework. A little run-through of the Django authentication would be nice as well.
HTTP is stateless; regardless of the server used, the framework or language, there is no intrinsic way for an HTTP client to say "this request is part of that session". That's part of the design of HTTP.
So sessions are always a feature of the web application; either supported by the a web app framework or implemented in the app itself. The most usual way for a stateful session to be created from the stateless protocol is with cookies; Clients will store cookies at the request of a server and return those same cookies to that server in future requests.
Session data can be serialized and stored in the cookie itself, but that's both insecure (secret information could be forged or eavesdropped), and inefficient (takes bandwidth even though the individual bytes are of no use to the client), and so the preferred solution is to use an opaque (and even better, single use) session key is stored in a cookie, and the web application will store session data out of band; either in memory, in the filesystem, or a database backend, or some other option.
django takes care of most of this transparently in "middleware", modules that modify incoming requests and outgoing responses. The auth middleware will read a cookie and check if that represents a logged in user, and if so, add a user object to the request; it also attaches cookies to responses when a user gets logged in. the session middlware works in a similar fashion, checking for a cookie, and reading the session data from wherever it was stored between requests, and also grabbing session data from responses and storing them, along with setting a cookie to associate the client's session with the session data it just stored.
Since both of these features are useful, independent of each other (I tend to avoid sessions, but usually use some kind of authentication), they do not depend on each other. Session-like authentication is implemented in a similar manner as sessions, but authenticated users are not stored in "The Session", nor are sessions attached to "The authenticated User".
You might not think so, but django's authentication system is designed to be extended; if you already have a database of valid users you'd like to authenticate against, it's very simple to add a new auth backend that dovetails neatly into the standard django auth application (which means you can also use other applications that depend on it in turn).

Categories

Resources