I am using flask-sqlalchemy and flask-login.
My flask login's user loader currently looks like this:
#login_manager.user_loader
def load_user(user_id):
return User.query.filter_by(id=user_id).options(load_only('id')).first()
This means that every time a user needs to be logged in the load_user()
a database query will be run to return a User object. In my case I almost never require other user fields like email, name, etc., so I specify sqlalchemy to only load the id field. This will allow the mysql optimiser to execute the query fairly fast.
However, I don't even need to perform this query because:
When user logged in and is issued a session, there's a guarantee that a unique id exists for him.
Flask-login's cookies are signed using my secret key, so there is no way for a random user to spoof his user id to impersonate some other user by changing his id.
Also, due to some specific business reasons, every logged in user has to send a heartbeat signal every 1 second. This means every second, this user id fetch query will be performed, creating a considerable load on my database server (I have about 1000 users doing this simultaneously). This requirement is non negotiable, and I can't improve speed by other methods, like by using an in-memory server.
I wanted to do this instead:
#login_manager.user_loader
def load_user(user_id):
return User(id=user_id)
Note that in this case a fake object with the user's id is returned.
From other files, I do
from flask_login import current_user
The above line give me a handle on the currently logged in user.
However, using my proposed method wont work when I want to fetch other attributes of the user, and more importantly its relationships.
It means that I cannot simply do current_user.email, or:
current_user.friends #this is a relationship
I tried this, and it simply returns None. (However, I can get the id of the user like I would normally)
Is there any way to instantiate a model and make sqlalchemy think that whatever attributes I specified when creating the model were the result of a sqlalchemy query?
Essentially make sqlalchemy think that only some attributes were loaded using the load_only() method.
This way if I do current_user.email it will fetch the email if it was not loaded already.
After discussing this issue with Michael Bayer (creator of sqlalchemy) here, this is the solution he suggested:
make the object act like detached and expired, then associate with the session:
from sqlalchemy.orm.session import make_transient_to_detached
#login_manager.user_loader
def load_user(user_id):
u = User(id=int(user_id)) # will not do any db query
make_transient_to_detached(u) # This is the suggested solution
db.session.add(u) # add the detached object to session
return u
Later on, its possible to do :
from flask_login import current_user
and access other attributes of the current_user like:
print(current_user.email) # will perform a database query to fetch email
Related
In my website I'd like to add logging of user actions (eg. user added/modified/deleted something, user visited some page, user logged-in/out). Just to be clear I'd need this identifier be unique but allow me to follow user and not change on every site (everything happens within my website).
I have already logger in place, but I'm not sure how I can get unique user identifier. Currently it looks like this:
logger.info('User %s entered website X', self.request.user)
I know I could use request.user for logged-in users, but what with the rest? Right now everyone are AnonymousUser, but instead I'd like to have some unique identifier.
I assume Django is already providing that, but question is - how I could access it.
For anonymous users I'd set my own session ID
When SessionMiddleware is activated, each HttpRequest object – the first argument to any Django view function – will have a session attribute, which is a dictionary-like object.
ref: https://docs.djangoproject.com/en/3.0/topics/http/sessions/#using-sessions-in-views
import uuid
request.session['anonymous_id'] = uuid.uuid4()
# Then you can do
logger.info('User %s entered website X', request.session['anonymous_id'])
How you decide to set this sesssion attribute is up to you, I'd suggest a middleware approach where you check, is the users logged in. If not then check if there is already a session id. If not then set one.
I would like to follow this guideline and avoid a nullable ForeignKey.
I have a ForeignKey to the django User model.
If I try to store request.user in an instance of this model, I get this error:
ValueError: Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser>>":
"MyModel.user" must be a "User" instance.
I think it is feasible to avoid the non-conditionless data schema (nullable foreign key).
How could I solve this?
Those guidelines suggest you create an instance of user that reflects an anonymous user. Otherwise known as a sentinel value. You'd have to keep track of it via some unique key, likely the username. Then make sure it exists and nobody else has actually created a user with that key otherwise you run into other problems.
However, because of those outlined issues above I disagree with those guidelines. If your data model allows for optional relationships, then you absolutely should use NULL values.
Regarding the comment:
If there is no NULL in your data, then there will be no NullPointerException in your source code while processing the data :-)
Simply because there are no NULL fields, doesn't mean those conditions don't exist. You still are handling these edge cases, but changing the names and some of the syntax. You're still vulnerable to bugs because you still have as many conditions (and potentially more given that you have to now make sure your sentinel value is unique).
Hey it's my first attempt at answering a question! I'm a newbie, but I had a similar error recently. I suppose this is a naive version Nigel222's answer which calls for doing this with a migration, but maybe there is something of value here nonetheless for another newbie who needs a simpler solution. I was influenced by an answer to this post by Ayman Al-Absi that suggests that you may need to reference this user by it's auto-generated primary key.
By default, request.user is AnonymousUser when not authenticated. It seems from the error message, AnonymousUser can't be used as value for your foreign key in the User table.
Proposed solution:
from django.contrib.auth.models import User
# Start by creating a user in your User table called something like anon in some kind of initialization method:
tempUser= User.objects.create_user(username="anon", email="none", first_name="none", last_name="none")
tempUser.save()
#when the user is unauthenticated, before calling a method that takes request as a parameter do:
if request.user.is_anonymous:
anonUser = User.objects.get(username='anon')
request.user=User.objects.get(id=anonUser.id)
Another comment for the newbie. I made my own table called User in models.py. This became confusing. I had to import it with an alias:
from .models import User as my_user_table
It would have been better just to call it my_user_table to begin with.
Create a special instance of User for this purpose. It's The best place to do so is in a data migration for the model which will rely on being able to create a ForeignKey to this special User object. When you deploy your app and run makemigrations and migrate, it will create the special user objects before there are any actual users in the DB.
There's a lot of detail on creating data migrations here
Here's an example of making sure that some Group objects will exist as of this migration for any future deployment.
# Generated by Django 2.2.8 on 2020-03-05 09:53
from django.db import migrations
def apply_migration(apps, schema_editor):
Group = apps.get_model("auth", "Group")
Group.objects.bulk_create(
[Group(name="orderadmin"),
Group(name="production"),
Group(name="shipping")]
)
def revert_migration(apps, schema_editor):
Group = apps.get_model("auth", "Group")
Group.objects.filter(name__in=["orderadmin", "production", "shipping"]).delete()
class Migration(migrations.Migration):
dependencies = [
('jobs', '0034_auto_20200303_1810'),
]
operations = [
migrations.RunPython(apply_migration, revert_migration)
]
Sorry, I am still new to Django, hopefully the question isn't out of place.
When I have the following in my template:
<td>{{ item.last_name }}</td>
By clicking on last name the user will be redirected to the following link in order to edit it.
http://127.0.0.1:8000/contact/edit/?id=1
But then what prevents any logged in user to just inject a different id in there on the browser and edit a record that doesn't belong to him?
Update
I just had an idea when I read the comment and answer below. Rather than using a third party app, couldn't I just create a UserProfile for each user and attach a unique company wide uuid.uuid1(). Each time a loggedin user attempts to edit something, his unique company uuid will be also passed in the link as an additional parameter.
On the edit side, it would harvest this guid and compare it against the logged in user and see if they match. If they do match, he is authorized to proceed with the editing, otherwise he will be redirected.
What do you think? Any weaknesses?
If you use Django's new class based views, e.g. the generic UpdateView, you can extend the dispatch handler.
def dispatch(self, request, *args, **kwargs):
handler = super(MyEditView, self).dispatch(request, *args, **kwargs)
# Only allow editing if current user is owner
if self.object.author != request.user:
return HttpResponseForbidden(u"Can't touch this.")
return handler
In this case, the code verifies that the author field of the model object corresponds to the currently logged in user, before even handling the rest of the request.
You can see a reallife example of this in a project of mine.
When you're using Django auth, always rely on the session mechanism to identify an user instead of making some other id such as uuid1() (except, for example, when you need extra sessions in an e-commerce site under HTTPS).
For the permission part, you could check the ownership directly, mainly as described by Koliber Services. The relationships between Company, User and Contact are crucial for the logic of permission checking. There are many ways to model the relationships and thus the code would differ much. For example:
# a modeling way
User.company -> Company : an user belongs to a company
Contact.contributor -> User : a contact is contributed by an user, would be invalid is user is leaving the company
# could check accessibility by
can_view = contact.contributor.company_id == current_user.company_id
# another modeling way
User.company -> Company : an user belongs to a company
Contact.company -> Company : a contact info is owned by a company, does not share globally
# could check accessibility by
can_view = contact.company_id == current_user.company_id
When can_view is False, user should get a 403 for his unauthorized attempting and get logged.
Normally the above method is enough for content protection(not yet in Django Admin). However, when you have many different types of permission checking and even row-permission checkings, it's better to use some uniform permission API.
Take Django-guardian for example, you could simply map companies to groups and assign can_view permission for a contact for the group representing the user's company. Or, assign the can_view permission to all users in a company when a contact is created by using signal or celery task.
Furthermore, you could use /contact/1/edit/ instead of /contact/edit/?id=1. In this way the int(request.GET('id')) part is moved to urlconf like r'^contact/(?P<pk>\d+)/$', less code and much clearer.
There are some third-party apps that does what you want, its called "row-level permission" where you can give different users different access to specific objects, "Row level" comes from SQL where each object is a row in the database
I use django-guardian to do the job
In the function handling the saving of the data, check to see if the object being edited has the same ID as the presently logged in user.
For example, if the object in question is called EmailPrefs and it has a field called user_id:
Load the EmailPrefs object with the ID of the object being edited
If the user_id does not match the current user, stop further processing
Modify the EmailPrefs object
Save the EmailPrefs object to the database
I have an AppEngine app that I'm migrating to run in Django, using app-engine-patch to get all the goodness of Django - particularly the Admin interface.
One of my models looks like (partially) this:
class Request(db.Model):
requestor = db.UserProperty(auto_current_user_add=True)
When I display a form based on this model I don't display the requestor field, and so when I call the Model's put() method the entity I'm saving doesn't have the requestor property set. This triggers the auto_current_user_add magic, and the user who created the request is automatically added.
Under Django, I'm using the provided Users table. I want this to display as a list of the users of my app, so the model becomes:
from ragendja.auth.google_models import User
class Request(db.Model):
requestor = db.ReferenceProperty(User)
However, this breaks the auto_current_user_add magic in the admin interface - if the user of the admin interface doesn't enter a value for the requestor property, Django sets the property to None, when I'd really like for the Request to have their username inserted automatically.
How can I restore the magic?
My solutions relies on three things:
First: it's possible to override the model's put() method.
Second: users.get_current_user() still provides the correct user, and
Third: ragendja.auth.google_models.User.get_djangouser_for_user() takes a google.appengine.api.users.user object and returns the corresponding Django User object - creating it first if it didn't already exist.
Putting this all together, I have:
class Request(db.Model):
requestor = db.ReferenceProperty(User)
def put(self):
if not self.requestor:
self.requestor = User.get_djangouser_for_user(users.get_current_user())
super(Request, self).put()
This works nicely with the admin interface: the admin can assign any existing user (or use the supplied + sign to create a new user) - if they leave it blank, they'll be assigned as the requestor.
Later when I add a view for users to manage their own requests, this value will be on the 'excluded' list, and the same method will add in their username every time they create a new request.
I'm not sure if this is an optimal solution though; I'm new to Django, so maybe there's a better way to achieve this.
I am writing an application where I will be accessing the database from django and from a stand alone application. Both need to do session verification and the session should be the same for both of them. Django has a built in authentication/session verification, which is what I am using, now I need to figure out how to reuse the same session for my stand alone application.
My question is how can I look up a session_key for a particular user?
From what it looks there is nothing that ties together auth_user and django_session
This answer is being posted five years after the original question, but this SO thread is one of the top Google results when searching for a solution to this problem (and it's still something that isn't supported out of the box with Django).
I've got an alternate solution for the use case where you're only concerned with logged in user sessions, which uses an additional UserSession model to map users to their sessions, something like this:
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
class UserSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
session = models.ForeignKey(Session)
Then you can simply save a new UserSession instance any time a user logs in:
from django.contrib.auth.signals import user_logged_in
def user_logged_in_handler(sender, request, user, **kwargs):
UserSession.objects.get_or_create(user = user, session_id = request.session.session_key)
user_logged_in.connect(user_logged_in_handler)
And finally when you'd like to list (and potentially clear) the sessions for a particular user:
from .models import UserSession
def delete_user_sessions(user):
user_sessions = UserSession.objects.filter(user = user)
for user_session in user_sessions:
user_session.session.delete()
That's the nuts and bolts of it, if you'd like more detail I have a blog post covering it.
I found this code snippet
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
session_key = '8cae76c505f15432b48c8292a7dd0e54'
session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
user = User.objects.get(pk=uid)
print user.username, user.get_full_name(), user.email
here
http://scottbarnham.com/blog/2008/12/04/get-user-from-session-key-in-django/
Have not verified it yet, but looks pretty straight forward.
This is somewhat tricky to do, because not every session is necessarily associated with an authenticated user; Django's session framework supports anonymous sessions as well, and anyone who visits your site will have a session, regardless of whether they're logged in.
This is made trickier still by the fact that the session object itself is serialized -- since Django has no way of knowing which data exactly you want to store, it simply serializes the dictionary of session data into a string (using Python's standard "pickle" module) and stuffs that into your database.
If you have the session key (which will be sent by the user's browser as the cookie value "sessionid"), the easiest way to get at the data is simply to query the Session table for the session with that key, which returns a Session object. You can then call that object's "get_decoded()" method to get the dictionary of session data. If you're not using Django, you can look at the source code (django/contrib/sessions/models.py) to see how the session data is deserialized.
If you have the user id, however, you'll need to loop through all of the Session objects, deserializing each one and looking for one which has a key named "_auth_user_id", and for which the value of that key is the user id.
Modifying the django_session table to add an explicit user_id can make life a lot easier. Assuming you do that (or something similar), here are four approaches to munging things to your liking:
Fork the django.contrib.session code. I know, I know, that's a horrible thing to suggest. But it's only 500 lines including all backends and minus the tests. It's pretty straightforward to hack. This is the best route only if you are going to do some serious rearranging of things.
If you don't want to fork, you could try connecting to the Session.post_save signal and munge there.
Or you could MonkeyPatch contrib.session.models.Session.save(). Just wrap the existing method (or create a new one), breakout/synthesize whatever values you need, store them in your new fields, and then super(Session, self).save().
Yet another way of doing this is to put in 2 (yes, two) middleware classes -- one before and one after SessionMiddleware in your settings.py file. This is because of the way middleware is processed. The one listed after SessionMiddleware will get, on the inbound request, a request with the session already attached to it. The one listed before can do any processing on the response and/or change/resave the session.
We used a variation on this last technique to create pseudo-sessions for search engine spiders to give them special access to material that is normally member-only. We also detect inbound links where the REFERER field is from the associated search engine and we give the user full access to that one article.
Update:
My answer is now quite ancient, although it still is mostly correct. See #Gavin_Ballard's much more recent answer (9/29/2014) below for yet another approach to this problem.
Peter Rowell, thanks for your response. It was a tremendous help. This is what I did to get it working. Only had to change one file in djang.contrib.sessions.
In django/contrib/sessions/models.py, add the user_id to the table (add to DB table manually or drop table and run manage.py syncdb).
class Session(models.Model):
...
user_id = models.IntegerField(_('user_id'), null=True)
...
def save(self, *args, **kwargs):
user_id = self.get_decoded().get('_auth_user_id')
if ( user_id != None ):
self.user_id = user_id
# Call the "real" save() method.
super(Session, self).save(*args, **kwargs)
Now in your view where you do login (if you use django's base login, you will have to override it)
# On login, destroy all prev sessions
# This disallows multiple logins from different browsers
dbSessions = Session.objects.filter( user_id = request.user.id )
for index, dbSession in enumerate( dbSessions ):
if ( dbSession.session_key != request.session.session_key ):
dbSession.delete()
This worked for me.
I ran across this problem when I wanted to kick out a spammer. It seems setting their account to "inactive" isn't enough, because they can still come in on their previous session. So - how to delete a session for a specific user, or how to deliberately expire a session for a specific user?
The answer is to user the last_login field to track down the time at which the session was disabled, which tells you that the expire_date is two weeks later, which lets you perform a useful filter on the sessions table:
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
from datetime import datetime
from dateutil.relativedelta import relativedelta
baduser = User.objects.get(username="whoever")
two_weeks = relativedelta(weeks=2)
two_hours = relativedelta(hours=2)
expiry = baduser.last_login + two_weeks
sessions = Session.objects.filter(
expire_date__gt=expiry - two_hours,
expire_date__lt=expiry + two_hours
)
print sessions.count() # hopefully a manageable number
for s in sessions:
if s.get_decoded().get('_auth_user_id') == baduser.id:
print(s)
s.delete()