Users granted entry through flask_login - authentication

I have a problem with my website which is built using python, flask, gunicorn + supervisorctl, nginx stack.
The problem is: if I am logged on as a user (say admin for e.g.) after restarting my website through "sudo supervisorctl reload' after making some updates to it, any user who attempts to login afterwards becomes logged in as admin even if they enter a random username and password combination. Obviously this is very alarming.
I'm currently using the flask_login plugin to handle the login but I suspect is something to do with flask sessions and how it interacts.
My login code is below
from flask import render_template, flash, redirect, url_for, request, current_app, session
from flask_login import current_user, login_user, logout_user, login_required
#bp.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.user'))
form=LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username = form.username.data).first()
if user is None or not user.check_password(form.password.data):
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('main.user')
return redirect(next_page)
return render_template('auth/login.html', title=_('登录'), form=form)
I also use flask session to identify the current language and mange translations
in main routing:
#bp.before_app_request ####Michael: becareful of these
def before_request():
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
#bp.context_processor
def inject_conf_var():
return dict(CURRENT_LANGUAGE=session.get('language',
request.accept_languages.best_match(current_app.config['LANGUAGES'])))
and in my init
#babel.localeselector
def get_locale():
# if the user has set up the language manually it will be stored in the session,
# so we use the locale from the user settings
try:
language = session['language']
print(session['language'])
except KeyError:
language = None
if language is not None:
return language
return
request.accept_languages.best_match(current_app.config['LANGUAGES'])
I'm not really sure where to start identifying what the problem is, its a bit beyond me at the moment. Closest thing i saw was this:
Users appear to be logged in as another user
but the same thing seems to happen across computers in my case so not really a VPN issue, I'm not sure if the same solution would work.
Grateful for any help!

Related

Redirect User to original URL after Google login in Flask

I have implemented flask-dance and authlib Flask client for Google sign-in, one answer was unclear in all implementations were how to redirect a user to original user once they login. For example, a flow I wanted => clicks on /results checks if not logged in redirect to login makes them login and then again redirects back to results with logged in session.
I saw some answers using state and kwargs addition but didn't see any clear answer or a pseudo code implementation.
If you have implemented such a scenario please answer the question it will help a lot or you can also reference to your Github projects if implemented
The simple solution i discovered to my own problem was in any implementation for any such library use a session variable to record orignal url and then redirect user after login using that variable so in here i have used next param variable which stores it temp then once authorized sends user to orignal url what they asked for
see the code below
#app.route('/login')
def login():
google = oauth.create_client('google')
redirect_uri = url_for('authorize', _external=True)
return google.authorize_redirect(redirect_uri)
#app.route("/dashboard")
def protect():
if not session.get('profile'):
session['next']='/dashboard'
return redirect('/login')
if session['profile']:
#load dashboard
else:
return "forbidden"
#app.route('/authorize')
def authorize():
google = oauth.create_client('google')
token = google.authorize_access_token()
resp = google.get('userinfo')
user_info = resp.json()
user = oauth.google.userinfo()
session['profile'] = user_info
session.permanent = True
redirecti=session.get("next",None)
return redirect(redirecti)

Flask JWT to SQLAlchemy User Object?

I have an app where the user details are passed as a JWT containing information about the current user and it's roles.
Everytime the user is logged in (via a KeyCloak instance), the information from the JWT is parsed on my end in a function that updates the user object via SQLAlchemy. However, since there is no user object being passed back and forth in the backend, I have to parse the JWT for roles for every action that requires it. I also have a need for auditing, and due to the structure of the app, this module does not necessarily have access to the request objects at the time of logging.
I'm looking for a neat way to make something like flask_users current_user() functionality work by mapping JWT -> ORM user object, to be able to transparently get the current user. Is there any way to go about this? The user registration and so on is completely separate from the app, and Flask only knows which user it is based on tokens in the requests that are being sent.
TLDR; Is there a way to load a user from the DB based on an already issued JWT (which contains information to map to a user), and is there perhaps already a lib or extension to flask that supports this?
I use a decorator to parse the JWT token using pyjwt.
Then from the parsed token you can get the user and do the proper authorization.
If you don't want to add the decorator to all your functions that require authorization you can use Flasks before_request.
from functools import wraps
from flask import Response, current_app, request
from jwt import decode
from jwt.exceptions import (DecodeError, ExpiredSignatureError,
InvalidSignatureError)
def authorize(func):
#wraps(func)
def check_authorization(*args, **kwargs):
try:
jwt_token = request.cookies.get('auth_token') # get token from request
if jwt_token is None:
return Response(status=401, headers={'WWW-Authenticate': 'Bearer'})
token = decode(
jwt_token,
key='pub_key', # public key to validate key
algorithms=['RS256'], # list of algs the key could be signed
verify=True
)
# you can call another function to do check user roles here
# e.g authorize(token['sub'])
return func(*args, **kwargs)
except (InvalidSignatureError, DecodeError, ExpiredSignatureError):
return Response(
response='{ "error": "token_invalid"}',
status=401,
headers={'WWW-Authenticate': 'Bearer'})
return check_authorization
This is supported with flask-jwt-extended: https://flask-jwt-extended.readthedocs.io/en/stable/complex_objects_from_token/

Flask - How to require login only for post requests

I'm working in Flask and I want to allow users to enter information into a form without logging in but be required to login if they submit the form. After logging in, it should be as though a user just submitted the form (they shouldn't have to re-enter any information).
To store their information, I've used sessions like this. It works well:
if request.method == "POST":
if "arg1" not in session.keys() and "arg2" not in session.keys():
session["arg1"] = request.form.get('arg1')
session["arg2"] = request.form.get('arg2')
However, I'm having trouble with the login required part. I know I can use #login_required on the whole route but I just want #login_required to apply if the request is a post method. I've tried simply adding #login_required after checking if the method is a post request but it doesn't work.
My login route looks like this:
#app.route("/login", methods = ["POST", "GET"])
def login():
#log user in
return redirect(request.args.get("next") or url_for('index'))
It seems as though I need two things.
1: To apply #login_required solely to a post request.
2: To have request.args.get("next") call a post request, not get request
How could I go about doing these two things and achieve my goal?
Thank you!
Break out your routes. 1 for GET and 1 for POST
#app.route("/login", methods = ["GET"])
def get_login():
return stuff
#app.route("/login", methods = ["POST"])
#login_required
def post_login():
return stuff
There are a couple patterns that could be used here but this one is the most straight forward.

MyIndexView error in theme example does not work in Flask-AppBuilder

I implemented steps 1, 2 and 3 from the "Changing the index" section of this page https://flask-appbuilder.readthedocs.io/en/latest/customizing.html?highlight=theme.
I get the following error:
\app__init__.py", line 4, in
from app.index import MyIndexView
ImportError: cannot import name 'MyIndexView'
I have made these changes to a barebone Flask-AppBuilder app.
The code is exactly as is shown on the site.
I expect the example to work as described. But I receive the message I posted above when I run it.
Your index.py should look like this(base version).
# Import flask functions for redirecting and getting user status
from flask import g, url_for, redirect
# Import IndexView class to overwrite files/redirects and expose to expose custom index view
from flask_appbuilder import IndexView, expose
# File to display custom made different views based off if user is signed
class MyIndexView(IndexView):
# Checking user and redirecting for user when user goes to index view
#expose('/')
def index(self):
# Get user status
user = g.user
# Check user
if user.is_anonymous:
# user is not authenticated and gets redirected to New user page
return redirect(url_for('HomeView.new'))
else:
# user is authenticated and has an account redirect to General page
return redirect(url_for('HomeView.general'))
Then in your views.py create a simle view like this
# Views for any home paths
class HomeView(BaseView):
# add route base for views as /home
route_base = "/home"
# Route for new or logged out users
#expose('/new')
def new(self):
return self.render_template('new_user.py')
# Route for signed in users or users who want to just view data
#expose('/general')
def general(self):
return self.render_template('my_index.py')
Also, make sure to add it to your appbuilder object in your init.py
appbuilder = AppBuilder(app, db.session, indexview=MyIndexView)

how to use django REST JWT authorization and authentication in class based views

I am using JWT authentication I am using this type of authorization app wide.
I am trying to figure out how to ue it in a view.
Example. Say I only want to allow a user to create an approved venue if they have the correct permissions. What would I add to this view to get access to the user?
I know that django has request.user but how do I turn that on? Is it always on and request.user is null if there is no token passed into the header? Or is it middleware? The problem I am ultimately having is there is a lot of info getting to this point, but very little on actually using the JWT on the view.
please help.
# for creating an approved venue add ons later
class CreateApprovedVenue(CreateAPIView):
queryset = Venue.objects.all()
serializer_class = VenueSerializer
Django rest framework jwt docs
https://jpadilla.github.io/django-rest-framework-jwt/
Django rest framework permissions docs
http://www.django-rest-framework.org/api-guide/permissions/
so I discovered this resource and looking at it now.
https://code.tutsplus.com/tutorials/how-to-authenticate-with-jwt-in-django--cms-30460
This example is sheading light:
# users/views.py
class CreateUserAPIView(APIView):
# Allow any user (authenticated or not) to access this url
permission_classes = (AllowAny,)
def post(self, request):
user = request.data
serializer = UserSerializer(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
To use JWT authentication, you need to do the following installation steps: https://jpadilla.github.io/django-rest-framework-jwt/#installation
Once that is done, you can include the auth by simply adding the authentication_classes as follows
# for creating an approved venue add ons later
class CreateApprovedVenue(CreateAPIView):
authentication_classes = (JSONWebTokenAuthentication, )
queryset = Venue.objects.all()
serializer_class = VenueSerializer
And you have user available to you as request.user in all the request methods. In the case of the CreateAPIView you can do:
def post(self, request, *args, **kwargs):
user = request.user
...