How to map users to flask session file in server? - flask

I still use the flask session manager for users. My config is like this:
app.config['SECRET_KEY'] = "mySecretKey"
app.config["SESSION_PERMANENT"] = True
app.config["SESSION_TYPE"] = "filesystem"
app.config['SESSION_USE_SIGNER'] = True
Whenever a user logs in a flask session file is created in the folder app\flask_session. It will be random alphanumeric file name, like 0609ece61011830ef9b6547217b9abd6.
the session file contains the email of the user. What I want to do is to delete a flask session file when a user resets their password, for security reasons. Like the user might have logged in to 5 devices and as the sessions don't expire the user gets to use the account on all 5 devices. But when the user changes the password, I need to delete all these 5 sessions which match the email id so that the user gets logged out on all the devices.
I am ready to loop through each session file. How can I achieve this?

you can use pickle package for that. Here is an example how to parse one of the session files:
import pickle
with open("sessions_dir/701dc705b498a6ee20475e5fcbcbdf99", "rb") as e:
time = pickle.load(e)
data = pickle.load(e)
print(time)
print(data)
You will need to get the email id from the data and act accordingly.

Related

How can i send a webook to my django app and only execute it on logged in user

Hi i am new to coding and only been learning python and django for about a week. so sorry if this does not make sense.
i am trying to send a webhook(this is just a test then ill add json)
so that when i send to http://127.0.0.1:8000/webhook
i am using postmen desktop so i receive the webhooks.
it will execute my session code but the problem i have that in my app i have
already set up users db and when i sent the webhook it return none since no one seems to be logged in. i know i will have to add a code something like a token to specify each user but only want to test with one user first.
i get the error None or when i remove user authenticated then it says i can't send to anonymous user. is there a way i can send it to lets say a specific user or ID or let's say a token i save with the user.
#csrf_exempt
def webhook(request):
#get current users keys
if request.user.is_authenticated:
user = request.user
database = Bybitapidatas.objects.all().filter(user=user)
for apikey in database:
apikey = apikey.apikey
for apisecret in database:
apisecret = apisecret.apisecret
session = HTTP(endpoint='https://api-testnet.bybit.com/', api_key=apikey, api_secret=apisecret,spot=True)
print(session.place_active_order(
symbol="BTCUSDT",
side="Buy",
type="MARKET",
qty="20",
timeInForce="GTC"
))

Transfer users between Django servers

I have two types of Django servers. One is a central server that contains a master database. There's only one of these.
Then there's an arbitrary number of client servers that contain their own databases. These client databases act as a cache for the master database.
This is used for users. I a user tries to log in on client server 1, that server looks for the user in its database. If it's not there, it goes out to the central server and asks that server if the user exists in the master database. If so, the central server returns the users info so that the client server can store/cache it in its own database and then log the user in. Each successive time that user tries to log in, the user is found in the client database and it no longer has to go out to the central server.
The central server returns the users information as JSON like so:
{
"username": "joe.bag.o.doughnuts",
"id": 143,
"password": "fksdfjfldskjf",
}
My issue here is the password. when I put the value in there as just user.password, it uses the encrypted version of that password. This is good because I don't want the plain text password out there in http connections for security reasons. However, how do I store the encrypted password correctly when creating a copy of the user on the client server? If I set password to the encrypted text (User.objects.create(username=<retrieved_username>, password=<retrieved_password_which_is_already_encrypted>)), it will treat it as a plain text password and will then encrypt it... even though it's already encrypted so it's encrypting the encryption, not the actual password.
I wonder if you can achieve this a different way than by way of User.objects.create(...)? Depends on the server constraints, etc. but would this work for you?
From bash, maybe as cron job, something like this:
python manage.py dumpdata auth.users --indent 2 --natural-foreign --output users.json
Then load onto the other server using something like this:
python manage.py loaddata users.json
Or in the alternative, you might be able to create a custom user manager for the importation of users with previously hashed passwords:
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, password=password, **extra_fields)
# Normally here Django uses the set_password function
# https://docs.djangoproject.com/en/2.0/ref/contrib/auth/#django.contrib.auth.models.User.set_password
user.save(using=self._db)
return user
More info on extending/overriding User class here: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html

flask_dance: Cannot get OAuth token without an associated user

I want to migrate flask_dance with my application to make the user authorize using google and another social networks.
I am getting this error:
Cannot get OAuth token without an associated user
Before i do the connection between the blueprint and sqlalchemy backend, the application worked just fine, if i removed the google_blueprint.backend line the error disappear.
Here is my __init__.py:
import os
from flask import Flask, redirect, url_for, current_app
from flask_login import current_user
from develop.models import (
db,
User,
OAuth
)
from flask_dance.contrib.google import make_google_blueprint
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend
from flask_dance.consumer import oauth_authorized
from sqlalchemy.orm.exc import NoResultFound
def create_app(config_object):
app = Flask(__name__)
app.config.from_object(config_object)
db.init_app(app)
login_manager.init_app(app)
google_blueprint = make_google_blueprint(
client_id=app.config['GOOGLE_CLIENT_ID'],
client_secret=app.config['GOOGLE_CLIENT_SECRET'],
scope=["profile", "email"]
)
app.register_blueprint(google_blueprint, url_prefix='/login')
#oauth_authorized.connect_via(google_blueprint)
def google_logged_in(blueprint, token):
resp = blueprint.session.get("/oauth2/v2/userinfo")
if resp.ok:
account_info_json = resp.json()
email = account_info_json['email']
query = User.query.filter_by(email=email)
try:
user = query.one()
except NoResultFound:
user = User()
user.image = account_info_json['picture']
user.fullname = account_info_json['name']
user.username = account_info_json['given_name']
user.email = account_info_json['email']
db.session.add(user)
db.session.commit()
login_user(get_user, remember=True)
identity_changed.send(
current_app._get_current_object(),
identity=Identity(get_user.id)
)
#login_manager.user_loader
def load_user(userid):
return User.query.get(userid)
google_blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user)
return app
Here is also my tables how i organized them in models.py:
class User(db.Model, UserMixin):
id = db.Column(db.Integer(), primary_key=True)
image = db.Column(db.String(), nullable=True)
fullname = db.Column(db.String())
username = db.Column(db.String(), unique=True)
password = db.Column(db.String())
email = db.Column(db.String(), unique=True)
class OAuth(OAuthConsumerMixin, db.Model):
user_id = db.Column(db.Integer(), db.ForeignKey(User.id))
user = db.relationship(User)
Please any help would be appreciated :)
TL;DR: You can disable this exception by setting user_required=False on the SQLAlchemyStorage object. However, the exception is being raised for a reason, and if you simply disable it like this, your database may get into an unexpected state where some OAuth tokens are not linked to users. There's a better way to solve this problem. Read on for details.
I am the author of Flask-Dance. This Cannot get OAuth token without an associated user exception is only present in version 0.13.0 and above of Flask-Dance. (CHANGELOG is here.) The pull request introducing this change has some more context for why the change was made.
There are several different ways to use OAuth. Here are some example use cases, all of which Flask-Dance supports:
I want to build a bot that can connect to one specific service, such as a Twitter bot that tweets to a specific account, or a Slack bot that connects to a specific Slack team. I want this bot to respond to HTTP requests, so it has to run as a website, even though I don't expect people to actually use this website directly.
I want to build a website where users can log in. Users need to create an account on my website using a username and password. After they have created an account, users may decide to link their account to other OAuth providers, like Google or Facebook, to unlock additional functionality.
I want to build a website where users can log in. Users should be able to create their account simply by logging in with GitHub (or any other OAuth provider). Users should not need to create a new password for my website.
Use case 1 is the simplest: do not pass a user or user_id argument to your SQLAlchemyStorage, and it will assume that your application does not use multiple user accounts. This means that your website can only link to one particular account on the remote service: only one Twitter account, only one Slack team, etc.
Use case 2 is also pretty simple: pass a user or user_id argument to your SQLAlchemyStorage. Flask-Dance will save the OAuth token into your database automatically, and link it to the user that is currently logged in.
Use case 3 is more complex, since it involves automatically creating both the OAuth token and the local user account at the same time. Different applications have different requirements for creating user accounts, and there's no way for Flask-Dance to know what those requirements are. As a result, Flask-Dance cannot handle this use case automatically. You must hook into the oauth_authorized signal, create the user account and associate it with the OAuth token manually, and return False to tell Flask-Dance to not attempt to handle the OAuth token automatically.
Before version 0.13.0, it was possible to accidentally create OAuth tokens in the database that were not linked with any users at all. In use case 3, the OAuth token is created before a local user account exists for that user, so Flask-Dance would save the OAuth token to the database without any linked local user account. You could use the oauth_authorized handler to associate the OAuth token with a local user account afterwards, but if your code is buggy and raises an exception, then the OAuth token could remain in your database, forever unlinked to any users.
Starting in version 0.13.0, Flask-Dance detects this problem and raises an exception, instead of saving an OAuth token to your database without an associated local user account. There are two ways to resolve this problem:
Rewrite your code to manually create the user account and associate it with the OAuth token. The documentation contains some example code you can use for this.
Disable this check, and tell Flask-Dance that it's OK to create OAuth tokens without associated users. You can disable this check by setting user_required=False on the SQLAlchemyStorage object.
I believe that option 1 is the better solution by far, but it requires more understanding of what Flask-Dance is actually doing behind the scenes. I've written some documentation that describes how to handle multi-user setups, which discusses this problem as well.

How can I detect multiple logins into a Django web application from different locations?

I want to only allow one authenticated session at a time for an individual login in my Django application. So if a user is logged into the webpage on a given IP address, and those same user credentials are used to login from a different IP address I want to do something (either logout the first user or deny access to the second user.)
Not sure if this is still needed but thought I would share my solution:
1) Install django-tracking (thankyou for that tip Van Gale Google Maps + GeoIP is amazing!)
2) Add this middleware:
from django.contrib.sessions.models import Session
from tracking.models import Visitor
from datetime import datetime
class UserRestrictMiddleware(object):
"""
Prevents more than one user logging in at once from two different IPs
"""
def process_request(self, request):
ip_address = request.META.get('REMOTE_ADDR','')
try:
last_login = request.user.last_login
except:
last_login = 0
if unicode(last_login)==unicode(datetime.now())[:19]:
previous_visitors = Visitor.objects.filter(user=request.user).exclude(ip_address=ip_address)
for visitor in previous_visitors:
Session.objects.filter(session_key=visitor.session_key).delete()
visitor.user = None
visitor.save()
3) Make sure it goes after the VisitorTrackingMiddleware and you should find previous logins are automatically bumped when someone new logs in :)
If you're already using django-tracking as suggested here, there's a much easier way to implement this:
Define a signal handler:
# myapp/signals.py
def kick_my_other_sessions(sender, request=None, user=None, **kwargs):
from tracking.models import Visitor
from django.contrib.sessions.models import Session
keys = [v.session_key for v in Visitor.objects.filter(user=request.user).exclude(session_key=request.session.session_key)]
Session.objects.filter(session_key__in=keys).delete()
Create a listener for the user_logged_in signal:
# myapp/__init__.py
from myapp.signals import kick_my_other_sessions
from django.contrib.auth.signals import user_logged_in
user_logged_in.connect(kick_my_other_sessions, sender=User)
This will institute a sort of "last user to login wins" system. If you want to allow multiple logins by the same user from the same ip, you can add an .exclude() to the Visitors lookup.
Django's middleware will probably help you achieve this. The issue is that you will probably want to allow multiple anonymous sessions from the same IP address, even authenticated sessions for different users, but not authenticated sessions for the same user.
You'll want to:
Create a user profile model to store the IP address of a user's last login. See Django's Storing additional information about users documentation.
Implement a custom authentication backend. This backend, when triggered and successfully authenticating a user (just call super) would wipe out the user's last login IP in the profile model.
Implement a subclass of Django's django.contrib.sessions.SessionMiddleware class. Implement process_request. If the request.user object's profile model has no IP address, set it and allow the request. If it has an IP, and the IP is different from the current request's IP (request.META.REMOTE_ADDR), then do whatever you like to either log out the other user, or return an error to the requestor.
Update your settings.py file so that your custom auth backend is processed first, and so that your custom session middleware is also processed first. This involves updating settings.AUTHENTICATION_BACKENDS and settings.MIDDLEWARE_CLASSES.
You'll need to do this with custom middleware.
In your middleware process_request() method you will have access to the request object so you can do something like the following:
session_key = request.session.session_key
ip_address = request.META.get('REMOTE_ADDR', '')
Now you know the IP address, so check a model you create that (roughly) would look like this:
class SessionIPS(models.Model):
session = models.ForeignKey(Session)
IP = models.CharField(max_length=20)
So when a session is created or deleted you will modifiy your session ip's table accordingly, and when a request comes in make sure the IP address isn't being used for another session. If if is, then return a Http404 (or something like it) from the middleware.
A pluggable app that can show you a lot more detail (and even includes IP address in its own model) is django-tracking.

How do I determine when a user has an idle timeout in Django?

I would like to audit when a user has experienced an idle timeout in my Django application. In other words, if the user's session cookie's expiration date exceeds the SESSION_COOKIE_AGE found in settings.py, the user is redirected to the login page. When that occurs, an audit should also occur. By "audit", I mean a record should be written to my person.audit table.
Currently, I have configured some middleware to capture these events. Unfortunately, Django generates a new cookie when the user is redirected to the login page, so I cannot determine if the user was taken to the login page via an idle timeout or some other event.
From what I can tell, I would need to work with the "django_session" table. However, the records in this table cannot be associated with that user because the sessionid value in the cookie is reset when the redirect occurs.
I'm guessing I'm not the first to encounter this dilemma. Does anyone have insight into how to resolve the problem?
Update:
After a bit of testing, I realize that the code below doesn't answer your question. Although it works, and the signal handler gets called, prev_session_data if it exists, won't contain any useful information.
First, an inside peek at the sessions framework:
When a new visitor requests an application URL, a new session is generated for them - at this point, they're still anonymous (request.user is an instance of AnonymousUser).
If they request a view that requires authentication, they're redirected to the login view.
When the login view is requested, it sets a test value in the user's session (SessionStore._session); this automatically sets the accessed and modified flags on the current session.
During the response phase of the above request, the SessionMiddleware saves the current session, effectively creating a new Session instance in the django_session table (if you're using the default database-backed sessions, provided by django.contrib.sessions.backends.db). The id of the new session is saved in the settings.SESSION_COOKIE_NAME cookie.
When the user types in their username and password and submits the form, they are authenticated. If authentication succeeds, the login method from django.contrib.auth is called. login checks if the current session contains a user ID; if it does, and the ID is the same as the ID of the logged in user, SessionStore.cycle_key is called to create a new session key, while retaining the session data. Otherwise, SessionStore.flush is called, to remove all data and generate a new session. Both these methods should delete the previous session (for the anonymous user), and call SessionStore.create to create a new session.
At this point, the user is authenticated, and they have a new session. Their ID is saved in the session, along with the backend used to authenticate them. The session middleware saves this data to the database, and saves their new session ID in settings.SESSION_COOKIE_NAME.
So you see, the big problem with the previous solution is by the time create gets called (step 5.), the previous session's ID is long gone. As others have pointed out, this happens because once the session cookie expires, it is silently deleted by the browser.
Building on Alex Gaynor's suggestion, I think I've come up with another approach, that seems to do what you're asking, though it's still a little rough around the edges. Basically, I use a second long-lived "audit" cookie, to mirror the session ID, and some middleware to check for the presence of that cookie. For any request:
if neither the audit cookie nor the session cookie exist, this is probably a new user
if the audit cookie exists, but the session cookie doesn't, this is probably a user whose session just expired
if both cookies exist, and have the same value, this is an active session
Here's the code so far:
sessionaudit.middleware.py:
from django.conf import settings
from django.db.models import signals
from django.utils.http import cookie_date
import time
session_expired = signals.Signal(providing_args=['previous_session_key'])
AUDIT_COOKIE_NAME = 'sessionaudit'
class SessionAuditMiddleware(object):
def process_request(self, request):
# The 'print' statements are helpful if you're using the development server
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
audit_cookie = request.COOKIES.get(AUDIT_COOKIE_NAME, None)
if audit_cookie is None and session_key is None:
print "** Got new user **"
elif audit_cookie and session_key is None:
print "** User session expired, Session ID: %s **" % audit_cookie
session_expired.send(self.__class__, previous_session_key=audit_cookie)
elif audit_cookie == session_key:
print "** User session active, Session ID: %s **" % audit_cookie
def process_response(self, request, response):
if request.session.session_key:
audit_cookie = request.COOKIES.get(AUDIT_COOKIE_NAME, None)
if audit_cookie != request.session.session_key:
# New Session ID - update audit cookie:
max_age = 60 * 60 * 24 * 365 # 1 year
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
response.set_cookie(
AUDIT_COOKIE_NAME,
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None
)
return response
audit.models.py:
from django.contrib.sessions.models import Session
from sessionaudit.middleware import session_expired
def audit_session_expire(sender, **kwargs):
try:
prev_session = Session.objects.get(session_key=kwargs['previous_session_key'])
prev_session_data = prev_session.get_decoded()
user_id = prev_session_data.get('_auth_user_id')
except Session.DoesNotExist:
pass
session_expired.connect(audit_session_expire)
settings.py:
MIDDLEWARE_CLASSES = (
...
'django.contrib.sessions.middleware.SessionMiddleware',
'sessionaudit.middleware.SessionAuditMiddleware',
...
)
INSTALLED_APPS = (
...
'django.contrib.sessions',
'audit',
...
)
If you're using this, you should implement a custom logout view, that explicitly deletes the audit cookie when the user logs out. Also, I'd suggest using the django signed-cookies middleware (but you're probably already doing that, aren't you?)
OLD:
I think you should be able to do this using a custom session backend. Here's some (untested) sample code:
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.db.models import signals
session_created = signals.Signal(providing_args=['previous_session_key', 'new_session_key'])
class SessionStore(DBStore):
"""
Override the default database session store.
The `create` method is called by the framework to:
* Create a new session, if we have a new user
* Generate a new session, if the current user's session has expired
What we want to do is override this method, so we can send a signal
whenever it is called.
"""
def create(self):
# Save the current session ID:
prev_session_id = self.session_key
# Call the superclass 'create' to create a new session:
super(SessionStore, self).create()
# We should have a new session - raise 'session_created' signal:
session_created.send(self.__class__, previous_session_key=prev_session_id, new_session_key=self.session_key)
Save the code above as 'customdb.py' and add that to your django project. In your settings.py, set or replace 'SESSION_ENGINE' with the path to the above file, e.g.:
SESSION_ENGINE = 'yourproject.customdb'
Then in your middleware, or models.py, provide a handler for the 'session_created' signal, like so:
from django.contrib.sessions.models import Session
from yourproject.customdb import session_created
def audit_session_expire(sender, **kwargs):
# remember that 'previous_session_key' can be None if we have a new user
try:
prev_session = Session.objects.get(kwargs['previous_session_key'])
prev_session_data = prev_session.get_decoded()
user_id = prev_session_data['_auth_user_id']
# do something with the user_id
except Session.DoesNotExist:
# new user; do something else...
session_created.connect(audit_session_expire)
Don't forget to include the app containing the models.py in INSTALLED_APPS.
I don't know about Django, but can you, simply create a non-persistent cookie, which stores the last access time to a page on your site (you update the cookie on each page load)
Then, on your login page, you can check if your user has your cookie, but no session, then, you know that the user's session has probably timed out. Since you have the time of the last access to a page on your site, you can also calculate, based on the duration of the session, if it has timed out.
SESSION_COOKIE_AGE = 1500 # 25 minutes
Put that in your settings and that should take care of that and expire the session.