I am trying to create a flask app that will allow users to login, thereafter, they will be redirected to a specific dashboard created and being served with Bokeh.
So, for example, we have an user1, at the beginning he will start in https:myurl/login, after successful login he will be redirected to https:myurl/user1 where his specific dashboard is.
So, my question is, how I can avoid user1 accessing dashboard from other users user2, user3, etc. It is actually possible to do that? I am relatively new to flask, so my apologies if the question sounds silly.
from multiprocessing import connection
from functools import wraps
from re import A
from flask import Flask, render_template, request, flash, redirect, url_for, session
import sqlite3
from sqlalchemy import DATE
# Setup
app = Flask(__name__)
app.secret_key = "my_key"
# Routes
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
connection = sqlite3.connect("user_login.db")
cursor = connection.cursor()
# Get what the user has typed in the HTML form
username = request.form["username"]
password = request.form["password"]
# SQL query
cursor.execute(
"SELECT * FROM users WHERE username=? AND password=?", (username, password)
)
data = cursor.fetchone()
if data:
session["username"] = data[1]
session["password"] = data[2]
return redirect(url_for("user({data[1]})"))
# return redirect(f"https://myurl/{session['username']}", code=302)
else:
flash("Username and Password Mismatch", "DANGER! Please try again")
# Render HTML template
return render_template("login.html")
# Check if user is logged in
# def is_logged_in(f):
# #wraps(f)
# def secure_function(*args, **kwargs):
# if "logged_in" in session:
# return f(*args, **kwargs)
# else:
# flash("Unauthorized, please login", "danger")
# return redirect(url_for("login"))
# return secure_function
#app.route("/<username>")
def user(username):
if username == session['username']:
return redirect(
f"https://myurl/{session['username']}", code=302
)
else:
return flash("Unauthorized")
# #app.route('/')
# def logged():
# return redirect(f"https://myurl/{session['username']}", code=302)
if __name__ == "__main__":
app.run(debug=True)
How about verifying if the
current_user.username == myurl/<username>
.username being the name of your user in your Models(if it is name then current_user.name, etc.)
Like
#app.route("/dashboard/<username>")
def dashboard(username):
if username == current_user.username:
#proceed
else:
return "Access Denied"
*** Edit ***
Your provided code for the return statement
redirect(url_for("user({data[1]})"))
Could be written as:
return redirect(url_for('user', username = data[1]))
Related
I used the same code for google oauth and I used flask-dance. This worked with google, but didn't work with discord. I get an error message saying that I need to be logged in to reach the home page, meaning that the discord account hasn't been registered in the sqlite database.
Here is my code
from flask import Flask, render_template, redirect, url_for, flash, Blueprint
from flask_login import current_user, login_user, login_required
from flask_dance.contrib.google import make_google_blueprint, google
from flask_dance.contrib.discord import make_discord_blueprint, discord
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
from sqlalchemy.orm.exc import NoResultFound
from __init__ import db
from models import User, OAuth
discord_blueprint = make_discord_blueprint(client_id= "1001891783061553233", client_secret="QMvsUwbFGCLgYWgr8GAQ5ae2WibPTeDB", scope=["identify", "email"])
discord_bp = make_discord_blueprint(storage = SQLAlchemyStorage(OAuth, db.session, user = current_user))
#oauth_authorized.connect_via(discord_blueprint)
def discord_logged_in(blueprint, token):
if not token:
flash("Failed to log in", category="error")
return
resp = blueprint.session.get("/users/#me")
if not resp:
msg = "Failed to fetch user info"
flash(msg, category="error")
return
discord_name = resp.json()["name"]
discord_user_id = resp.json() ["id"]
query = OAuth.query.filter_by(
provider = blueprint.name, provider_user_id = discord_user_id
)
try:
oauth = query.one()
except(NoResultFound):
discord_user_login = discord_name
oauth = OAuth(provider = blueprint.name,
provider_user_id = discord_user_id,
provider_user_login = discord_user_login,
token=token,
)
if current_user.is_anonymous:
if oauth.user:
login_user(oauth.user)
else:
user = User(username = discord_name)
oauth.user = user
db.session.add_all([user, oauth])
db.session.commit()
login_user(user)
else:
if oauth.user:
if current_user != oauth.user:
url = url_for("auth.merge", username = oauth.user.username)
return redirect(url)
else:
oauth.user = current_user
db.session.add(oauth)
db.commit()
return redirect(url_for("main.profile"))
The google code is the same as the discord one and my redirect uri is localhost:5000/login/"oauthprovider"/authorized. For some reason the discord user isnt registered in the database?
I took me many hours to figure it out because there's literally 0 guides for this, but this the approach that worked for me in the end. This is an extract from my source, so you can see the full implementation here. I'm still working out some kinks like trying to be able to turn off os.environ['OAUTHLIB_INSECURE_TRANSPORT'] but I can login at least.
#REST_API.route('/register', methods=['GET', 'POST'])
def register():
discord_data = None
if discord.authorized:
discord_info_endpoint = '/api/users/#me'
try:
discord_data = discord.get(discord_info_endpoint).json()
except oauthlib.oauth2.rfc6749.errors.TokenExpiredError:
pass
#REST_API.route('/discord')
def discord_login():
return redirect(url_for('discord.login'))
if __name__ == "__main__":
discord_client_id = os.getenv("DISCORD_CLIENT_ID")
discord_client_secret = os.getenv("DISCORD_CLIENT_SECRET")
REST_API.secret_key = os.getenv("secret_key")
discord_blueprint = make_discord_blueprint(
client_id = discord_client_id,
client_secret = discord_client_secret,
scope = ["identify"],
)
REST_API.register_blueprint(discord_blueprint,url_prefix="/discord")
I'm using a modified version of this script: https://gist.github.com/ashish01/2a4a0f9b525096633ca2 to test out Google Logins with Flask:
import json
from flask import Flask, url_for, redirect, session
from flask_login import (UserMixin, login_required, login_user, logout_user, current_user)
from flask_googlelogin import GoogleLogin
users = {}
app = Flask(__name__)
app.config.update(
SECRET_KEY='asdf',
GOOGLE_LOGIN_CLIENT_ID='(redacted)',
GOOGLE_LOGIN_CLIENT_SECRET='(redacted)',
GOOGLE_LOGIN_REDIRECT_URI='http://localhost:5000/home',
GOOGLE_LOGIN_SCOPES='https://www.googleapis.com/auth/userinfo.email')
googlelogin = GoogleLogin(app)
class User(UserMixin):
def __init__(self, userinfo):
self.id = userinfo['id']
self.name = userinfo['name']
self.picture = userinfo.get('picture')
self.email = userinfo.get('email')
#googlelogin.user_loader
def get_user(userid):
return users.get(userid)
#app.route('/oauth2callback')
#googlelogin.oauth2callback
def login(token, userinfo, **params):
user = users[userinfo['id']] = User(userinfo)
login_user(user)
session['token'] = json.dumps(token)
session['extra'] = params.get('extra')
return redirect(params.get('next', url_for('home')))
#app.route('/logout')
def logout():
logout_user()
session.clear()
return """<p>Logged out</p><p>Return to /</p>"""
#app.route('/')
def index():
return 'Access the app'
#app.route('/home')
#login_required
def home():
return """
<p>Hello, %s</p>
<p><img src="%s" width="100" height="100"></p>
<p>Token: %r</p>
<p>Extra: %r</p>
<p>Logout</p>
""" % (current_user.email, current_user.picture, session.get('token'),
session.get('extra'))
app.run(debug=True)
But accessing /home gives a "too many redirects" error in the browser.
It seems to be caught in a loop but I don't understand why. As I see it, the path through the system is:
Go to / and click on the /home link
/home is #login_required, so redirects you to the google signin form, which all works fine
Sign-in form redirects you back to the /home page, which you can now access because #login_required is surely successful
What could be the problem here?
I have successfully followed the examples in the documentation of Flask-Dance to add Oauth from GitHub, and manage the users with SQLAlchemy. However, I am unable to add more providers. I tried to simply add another blueprint for Twitter and registering it, but I get various errors when trying to login with Twitter. Also, I end up with lots of duplicated code, so this is not a good approach.
Is there a better way to add another provider?
import sys
from flask import Flask, redirect, url_for, flash, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin, SQLAlchemyBackend
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_login import (
LoginManager, UserMixin, current_user,
login_required, login_user, logout_user
)
# setup Flask application
app = Flask(__name__)
app.secret_key = "supersekrit"
blueprint = make_github_blueprint(
client_id="my-key-here",
client_secret="my-secret-here",
)
app.register_blueprint(blueprint, url_prefix="/login")
# setup database models
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///multi.db"
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(256), unique=True)
# ... other columns as needed
class OAuth(db.Model, OAuthConsumerMixin):
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User)
# setup login manager
login_manager = LoginManager()
login_manager.login_view = 'github.login'
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# setup SQLAlchemy backend
blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user)
# create/login local user on successful OAuth login
#oauth_authorized.connect_via(blueprint)
def github_logged_in(blueprint, token):
if not token:
flash("Failed to log in with {name}".format(name=blueprint.name))
return
# figure out who the user is
resp = blueprint.session.get("/user")
if resp.ok:
username = resp.json()["login"]
query = User.query.filter_by(username=username)
try:
user = query.one()
except NoResultFound:
# create a user
user = User(username=username)
db.session.add(user)
db.session.commit()
login_user(user)
flash("Successfully signed in with GitHub")
else:
msg = "Failed to fetch user info from {name}".format(name=blueprint.name)
flash(msg, category="error")
# notify on OAuth provider error
#oauth_error.connect_via(blueprint)
def github_error(blueprint, error, error_description=None, error_uri=None):
msg = (
"OAuth error from {name}! "
"error={error} description={description} uri={uri}"
).format(
name=blueprint.name,
error=error,
description=error_description,
uri=error_uri,
)
flash(msg, category="error")
#app.route("/logout")
#login_required
def logout():
logout_user()
flash("You have logged out")
return redirect(url_for("index"))
#app.route("/")
def index():
return render_template("home.html")
# hook up extensions to app
db.init_app(app)
login_manager.init_app(app)
if __name__ == "__main__":
if "--setup" in sys.argv:
with app.app_context():
db.create_all()
db.session.commit()
print("Database tables created")
else:
app.run(debug=True)
Here is a sample project that uses multiple providers simultaneously: flask-dance-multi-provider. You can read through the code to see how it works. Does that help?
I'm trying to figure out why my Flask test is not working correctly. I'm testing a view function '/register' that successfully redirects to a dashboard when I run the site on localhost.
My test for this behavior fails and instead feeds back a 200 response.
Traceback (most recent call last):
File "/Users/casey/python/storm/project/tests/test_views.py", line 30, in test_register_user
self.assertEqual(resp.status_code, 302)
AssertionError: 200 != 302
My test and view code are below:
# tests/helpers.py
from unittest import TestCase
from views import app
class PhotogTestCase(TestCase):
def setUp(self):
self.client = app.test_client()
self.client.testing = True
app.config['WTF_CSRF_ENABLED'] = False
def tearDown(self):
pass
# tests/test_views.py
from helpers import PhotogTestCase
class TestRegister(PhotogTestCase):
"""Test our registration view."""
def test_register_user(self):
# Ensure page loads with correct text
resp = self.client.get('/register')
assert 'Register for an Account' in resp.data
# Ensure that valid fields result in success.
resp = self.client.post('/register', {
'email': 'c#gmail.com',
'password': 'woot1LoveCookies!',
'password_again': 'woot1LoveCookies!'
}, follow_redirects=True)
self.assertEqual(resp.status_code, 302)
# views.py
import uuid
from forms import RegistrationForm
from flask import Flask, redirect, render_template, \
request, url_for, flash, current_app, abort
from flask.ext.stormpath import StormpathManager, login_required, \
groups_required, user, User
from stormpath.error import Error as StormpathError
from flask.ext.login import login_user
#app.route('/register', methods=['GET', 'POST'])
def register():
"""
Register a new user with Stormpath.
"""
form = RegistrationForm()
# If we received a POST request with valid information, we'll continue
# processing.
if form.validate_on_submit():
data = {}
# Attempt to create the user's account on Stormpath.
try:
# email and password
data['email'] = request.form['email']
data['password'] = request.form['password']
# given_name and surname are required fields
data['given_name'] = 'Anonymous'
data['surname'] = 'Anonymous'
# create a tenant ID
tenant_id = str(uuid.uuid4())
data['custom_data'] = {
'tenant_id': tenant_id,
'site_admin': True
}
# Create the user account on Stormpath. If this fails, an
# exception will be raised.
account = User.create(**data)
# create a new stormpath group
directory = stormpath_manager.application.default_account_store_mapping.account_store
tenant_group = directory.groups.create({
'name': tenant_id,
'description': data['email']
})
# assign new user to the newly created group
account.add_group(tenant_group)
account.add_group('site_admin')
# If we're able to successfully create the user's account,
# we'll log the user in (creating a secure session using
# Flask-Login), then redirect the user to the
# STORMPATH_REDIRECT_URL setting.
login_user(account, remember=True)
# redirect to dashboard
redirect_url = app.config['STORMPATH_REDIRECT_URL']
return redirect(redirect_url)
except StormpathError as err:
flash(err.message.get('message'))
return render_template(
'account/register.html',
form=form,
)
When making the request, you set follow_redirects=True, so naturally you see the final page rather than the intermediate redirect. Set follow_redirects=False instead.
The following test code does not pass even though manually submitting the form on my web interface actually does work.
import os
from flask.ext.testing import TestCase
from flask import url_for
from config import _basedir
from app import app, db
from app.users.models import User
class TestUser(TestCase):
def create_app(self):
"""
Required method. Always implement this so that app is returned with context.
"""
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(_basedir, 'test.db')
app.config['WTF_CSRF_ENABLED'] = False # This must be disabled for post to succeed during tests
self.client = app.test_client()
ctx = app.app_context()
ctx.push()
return app
def setUp(self):
db.create_all()
#pass
#app.teardown_appcontext
def tearDown(self):
db.session.remove()
db.drop_all()
#pass
def test_admin_home(self):
# url is the admin home page
url = url_for('admin.index')
resp = self.client.get(url)
self.assertTrue(resp.status_code == 200)
def test_admin_registration(self):
url = url_for('admin.register_view')
data = {'username': 'admin', 'email': 'admin#example.com', 'password': 'admin'}
resp = self.client.post(url, data)
self.assertTrue(resp.status_code == 200)
u = User.query.filter_by(username=u'admin').first()
self.assertTrue(u.username == 'admin') # <----- This fails. Why?
After the test client has post to the register_view url and returns a 200 OK response, I fail to retrieve the 'admin' user from the test database. Why is this so?
Here's the view code (this is a flask admin view)
from flask import request
from flask.ext.admin import expose, AdminIndexView, helpers
from app.auth.forms import LoginForm, RegistrationForm
from app.users.models import User
from app import db
class MyAdminIndexView(AdminIndexView):
#expose('/', methods=('GET', 'POST'))
def index(self):
# handle user login
form = LoginForm(request.form)
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
#expose('/register/', methods=('GET', 'POST'))
def register_view(self):
# handle user registration
form = RegistrationForm(request.form)
if helpers.validate_form_on_submit(form):
user = User()
form.populate_obj(user)
db.session.add(user)
db.session.commit()
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
Dumbest mistake ever.
The offending line in my test code is
resp = self.client.post(url, data)
It should be
resp = self.client.post(url, data=data)
I managed to track it down by painstakingly walking through the logic and inserting ipdb.set_trace() step by step until I found the bad POST request made by my client.