How to get clean urls? - flask

from flask import Flask, redirect, url_for, session, request, jsonify
from flask_oauthlib.client import OAuth
app = Flask(__name__)
app.config['GOOGLE_ID'] = "12"
app.config['GOOGLE_SECRET'] = "A"BC
app.debug = True
app.secret_key = 'development'
oauth = OAuth(app)
google = oauth.remote_app(
'google',
consumer_key=app.config.get('GOOGLE_ID'),
consumer_secret=app.config.get('GOOGLE_SECRET'),
request_token_params={
'scope': 'email'
},
base_url='https://www.googleapis.com/oauth2/v1/',
request_token_url=None,
access_token_method='POST',
access_token_url='https://accounts.google.com/o/oauth2/token',
authorize_url='https://accounts.google.com/o/oauth2/auth',
)
#app.route('/')
def index():
if 'google_token' in session:
me = google.get('userinfo')
return jsonify({"data": me.data})
return redirect(url_for('login'))
#app.route('/login')
def login():
return google.authorize(callback=url_for('authorized', _external=True))
#app.route('/logout')
def logout():
session.pop('google_token', None)
return redirect(url_for('index'))
#app.route('/login/authorized')
def authorized():
resp = google.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
session['google_token'] = (resp['access_token'], '')
me = google.get('userinfo')
return jsonify({"data": me.data})
#google.tokengetter
def get_google_oauth_token():
return session.get('google_token')
Here when i am logging via google, my URL changes to something like this:
http://localhost:5000/login/authorized?code=4/U89v8kn76_zspiZUuZwdv01KuifACegxtt7NWBQLF3w#
What I want is what I gave in the URL
http://localhost:5000/login/authorized
What should I do?

This sounds like expected behavior for the callback portion of the auth process.
What you want to do is redirect the user to the main route at the end of the authorized() function. that function more or less "belongs" to the OAuth process (is a good way to think about it). you just determine if the process was successful and then redirect the user where they need to go.
i like to use Message Flashing to communicate with the user during this process.
example:
#app.route('/')
def index():
if 'google_token' not in session:
flash("Please log in to see this page")
return redirect(url_for('login'))
me = google.get('userinfo')
return render_template("index.html", user=me)
#app.route('/login/authorized')
def authorized():
resp = google.authorized_response()
if resp is None:
flash("Access denied: reason={0} error={1}".format(
request.args['error_reason'],
request.args['error_description']
))
return redirect(url_for("login"))
session['google_token'] = (resp['access_token'], '')
flash("Successful login!") # superfluous, just for example
return redirect(url_for("index"))
and you should see here that the session key is present (e.g. the cyrptocookie)... also obviously you should set secret key with os.urandom(24) per the docs

Related

flask_login, login_manager.user_loader is not getting called

I am using flask_login, It worked in local but when i moved to different server it is did not work. On trying to debug i realized login_manager.user_loader is not getting on remote machine but the same worked in my local.
This is issue is specific to okta sso authentication on remote machine only. Post okta authentication, i am calling below to save data in session.
login_user(user)
Post this, i was expecting user_loaded to be called but that is not happening. Tried going through document but could not understand. Can you anyone help me as why user_loader is getting called in my local machine but not on remote machine.
init.py
import os
from flask import Flask
from flask_login import LoginManager
from config import app_config
login_manager = LoginManager()
def create_app(config_name):
app = Flask(__name__,
instance_path=os.path.join(os.path.abspath(os.curdir), 'instance'),
instance_relative_config=True)
app.config.from_object(app_config[config_name])
app.config.from_pyfile('config.py')
login_manager.init_app(app)
login_manager.login_message = 'You must be logged in to access this page'
login_manager.login_view = 'admin.login'
login_manager.login_message_category = 'info'
login_manager.session_protection = "strong"
#login_manager.refresh_view ='admin.login'
#login_manager.needs_refresh_message = (u"To protect your account, please reauthenticate to access this page.")
from .home import home as home_blueprint
app.register_blueprint(home_blueprint)
from .restapi import restapi as restapi_blueprint
app.register_blueprint(restapi_blueprint, url_prefix='/steps/api/v1')
return app
In my blueprint view, i am calling login_user to set current user. I have placed user_loader in one seperate file.
from flask import current_app
from flask_login import UserMixin
from app import login_manager, get_connection
from .encryption import encrypt_json, encrypt_password, decrypt_password # noqa
class User(UserMixin):
"""Custom User class."""
id = None
name = None
email = None
description = None
role = None
def __init__(self, id_, name, email, description, role):
print(name)
self.id = str(id_)
self.name = name
self.email = email
self.description = description
self.role = role
def __repr__(self):
return self.name
def claims(self):
"""Use this method to render all assigned claims on profile page."""
return {'name': self.name,
'email': self.email}.items()
#staticmethod
def get(user_id):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE ID = {}"
cursor = connection.cursor()
cursor.execute(user_sql.format(user_id))
data = cursor.fetchone()
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
# print(user_id)
# print(USERS_DB)
# return USERS_DB.get(user_id)
#staticmethod
def getByName(user_name):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE USER_NAME = '{}' "
cursor = connection.cursor()
cursor.execute(user_sql.format(user_name))
data = cursor.fetchone()
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
#staticmethod
def getByEmail(emailid):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE EMAIL = '{}' "
cursor = connection.cursor(buffered=True)
cursor.execute(user_sql.format(emailid))
data = cursor.fetchone()
print(data)
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
#staticmethod
def verify_password(username, password):
try:
# mysql_hook = MySqlHook(mysql_conn_id="STEPS", schema="STEPS")
# connection = mysql_hook.get_conn()
connection = get_connection()
cursor = connection.cursor()
encFlag, decryptedPassword = decrypt_password(password)
lognReqTuple = (username, password)
user_sql = "SELECT USER_NAME, PASSWORD from USER WHERE USER_NAME = '{}'"
cursor.execute(user_sql.format(username))
all_users = cursor.fetchall()
print(all_users)
for user in all_users:
encFlag, decryptedPassword = decrypt_password(user[1]) if user[1] else (False, password)
existingUserTuple = user[0], decryptedPassword
if existingUserTuple == lognReqTuple:
# logging.info('User {} authenticated.'.format(username))
return 0
# logging.info('Un Authorized access, user authentication failed.')
return 1
except Exception as ex:
# logging.error('Error in verifying login details :{}'.format(ex))
print(ex)
return 1
finally:
cursor.close()
connection.close()
#staticmethod
def create(user_id, name, email):
# USERS_DB[user_id] = User(user_id, name, email)
pass
#login_manager.user_loader
def user_loader(user_id):
print('user loader', type(user_id), user_id)
return User.get(user_id)
Problem is that, same setup is working in my local ubuntu environment but when i moved to production vm on centos, it works for one view where i am using local authentication but not in case of okta.
user_loader is how you obtain the User object. You give it an id and it gives you back the User. See the docs.
In your case you already have the User object so login_user(user) has no need to call the user_loader.
user_loader is likely called wherever you do something like user = ....
Not sure about the issue but i changed the flask app folder structure. changed the way i was using blueprints and issue got solved. All in all, had to change complete application structure.

Unit testing of OAuth on Flask

I'm new to coding and I feel really stuck. I decided to unit test my micro Flask app. I use "Google sign in" to authenticate my users, but I'm not able to get through the Google authentication and assert what is really on the page. I have no idea if I need to mock, play with the session or application context.
Here is the code for authentication process:
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
user = dict(session).get('profile', None)
if user is not None:
email = session['profile']['email']
else:
email = None
if user:
return f(*args, **kwargs)
return render_template('login.html', email=email)
return decorated_function
#app.route('/login/')
def login():
google = oauth.create_client('google') # create the google oauth client
redirect_uri = url_for('authorize', _external=True)
return google.authorize_redirect(redirect_uri)
#app.route('/authorize')
def authorize():
google = oauth.create_client('google') # create the google oauth client
token = google.authorize_access_token() # Access token from google (needed to get user info)
resp = google.get('userinfo') # userinfo specificed in the scrope
user_info = resp.json()
# Loading and cleaning all user emails
auth_users = db.session.query(users.email).all()
auth_users = [str(i).strip("(),'") for i in auth_users]
# Checking if user is in the list
if user_info['email'] in auth_users:
flash("Boli ste úspešne prihlásený.", category="success")
else:
flash("Nemáte práva na prístup.", category="primary")
return redirect('/')
session['profile'] = user_info
# make the session pernament so it keeps existing after broweser gets closed
session.permanent = True
return redirect('/')
The test structure look like this:
class Uvo_page(unittest.TestCase):
def setUp(self):
self.client = app.test_client(self)
self.user_info = {'email': 'xxx'}
self.auth_users = ['xxx', 'yyy']
# UVO page shows results
def test_uvo_page_show_results(self):
response = self.client.get('/show/1',
content_type='html/text',
follow_redirects=True)
test_string = bytes('Sledované stránky', 'utf-8')
self.assertTrue(test_string in response.data)
I thought that by defining the "user_info" it will be used for authentication evaluation. Instead of it it does not continue to "/authorize" and authentication, but stays on the login page.

Custom basic authentication function not working with Bottle hook

I cannot make this work using bottle.hook('before_request'), any idea why the basic auth popup never show?
from bottle import *
auth_enabled = True
#hook('before_request')
def custom_auth_basic():
def decorator(func):
def wrapper(*a, **ka):
if auth_enabled:
user, password = request.auth or (None, None)
if user is None or not check_credentials(user, password):
err = HTTPError(401, "Access denied")
err.add_header('WWW-Authenticate', 'Basic realm="Private"')
return err
return func(*a, **ka)
else:
return func(*a, **ka)
return wrapper
return decorator
def check_credentials(user, pw):
username = "test"
password = "test"
if pw == password and user == username:
return True
return False
#route('/')
def root():
return Response("Test")
run(host='localhost', port=8080)
I cannot get it to work and to be totally honest, I do not fully understand those 3 nested functions and how they are called. It's based on a previous question I had: Enable or disable #auth_basic() programmatically
Thanks!

Setting up flask test_client

I am trying to test my flask app but I am getting this error
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in a way. To solve
this set up an application context with app.app_context(). See the
documentation for more information.`
I have tried understanding the error but all I kknow is that there is a client instance that should be instantiated to be used in testing. Help.
My code:
import unittest
from flask import jsonify
class TestAuth(unittest.TestCase):
"""Class for testing all the API endpoints"""
def setUp(self):
"""Initializing a test client and making the environment a testing one"""
app.app.config['TESTING'] = True
self.app = app.app.test_client()
self.app.testing = True
def sign_in(self, email='user#gmail.com', password='testpass'):
user_data = jsonify({"email": email, "password": password})
return self.app.post('/api/v1/auth/signup/', data=user_data)
def log_in(self, email='user#gmail.com', password='testpass'):
user_data = jsonify({"email": email, "password": password})
return self.app.post('/api/v1/auth/login/', data=user_data)
def test_home_status_code(self):
result = self.app.get('/api/v1/')
self.assertEqual(result.status_code, 200)
def test_signin_status_code(self):
result = self.sign_in()
self.assertEqual(result.status_code, 200)
def test_login_correct_login(self):
"""test login after signing in"""
self.sign_in()
result = self.log_in()
self.assertEqual(result.status_code, 200)
self.assertIn(b'Success', result.message)
def test_login_with_wrong_credentials(self):
"""test successful login"""
self.sign_in() # must sign in first for successful login
result = self.log_in(email='wrong#mail', password='wrongpass')
self.assertIn(b'Wrong Username or Password', result.message)
if __name__ == "__main__":
unittest.main()
try this:
def test_home_status_code(self):
with self.app as client:
result = client.get('/api/v1/')
self.assertEqual(result.status_code, 200)

rauth/flask: How to login via Twitter?

The provided example in rauth is using the PIN instead of the callback. I don't understand how this should work via web callback.
1) Minor problem:
According to twitter, if oauth_callback URL is passed in, then it should be used instead whatever entry is in the https://dev.twitter.com settings. However this doesn't seem to be true, if I dont set it to http://127.0.0.1:8080/twitter/authorized it would never get to that Url after a successful authorization.
app.add_url_rule('/twitter/login', view_func=views.twitter_login)
app.add_url_rule('/twitter/authorized', 'twitter_authorized', view_func=views.twitter_authorized)
def twitter_login():
request_token, request_token_secret = twitter.get_request_token()
redirect_uri = url_for('twitter_authorized', _external=True)
params = {'oauth_callback': redirect_uri, 'request_token':request_token}
return redirect(twitter.get_authorize_url(**params))
2) Major problem is here:
I can see the request.args has both ['oauth_token'] and ['oauth_verifier'].
But I don't understand how to use them to get the twitter session for obtaining user details such as picture and display name:
def twitter_authorized():
tw_session = twitter.get_auth_session(request_token ??? , request_token_secret ???)
resp = tw_session.get("account/verify_credentials.json", params={'format':'json'})
me = resp.json()
user = User.get_or_create(...)
if user:
login_user(user)
return redirect(url_for('index'))
If someone could shed some light on this, would be highly appreciated.
Here's a working Twitter sign-in examples using Flask based on the Facebook example:
'''
twitter
-------
A simple Flask demo app that shows how to login with Twitter via rauth.
Please note: you must do `from twitter import db; db.create_all()` from
the interpreter before running this example!
'''
from flask import (Flask, flash, request, redirect, render_template, session,
url_for)
from flask.ext.sqlalchemy import SQLAlchemy
from rauth.service import OAuth1Service
from rauth.utils import parse_utf8_qsl
# Flask config
SQLALCHEMY_DATABASE_URI = 'sqlite:///twitter.db'
SECRET_KEY = '\xfb\x12\xdf\xa1#i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16'
DEBUG = True
TW_KEY = 'oZSbVzKCeyAZTDxw1RKog'
TW_SECRET = 'TuNoqA6NzEBS3Zrb8test7bxQfKTlBfLTXsZ8RaKAo'
# Flask setup
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
# rauth OAuth 1.0 service wrapper
twitter = OAuth1Service(
name='twitter',
consumer_key=TW_KEY,
consumer_secret=TW_SECRET,
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authorize',
base_url='https://api.twitter.com/1.1/')
# models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
fb_id = db.Column(db.String(120))
def __init__(self, username, fb_id):
self.username = username
self.fb_id = fb_id
def __repr__(self):
return '<User %r>' % self.username
#staticmethod
def get_or_create(username, fb_id):
user = User.query.filter_by(username=username).first()
if user is None:
user = User(username, fb_id)
db.session.add(user)
db.session.commit()
return user
# views
#app.route('/')
def index():
return render_template('login.html')
#app.route('/twitter/login')
def login():
oauth_callback = url_for('authorized', _external=True)
params = {'oauth_callback': oauth_callback}
r = twitter.get_raw_request_token(params=params)
data = parse_utf8_qsl(r.content)
session['twitter_oauth'] = (data['oauth_token'],
data['oauth_token_secret'])
return redirect(twitter.get_authorize_url(data['oauth_token'], **params))
#app.route('/twitter/authorized')
def authorized():
request_token, request_token_secret = session.pop('twitter_oauth')
# check to make sure the user authorized the request
if not 'oauth_token' in request.args:
flash('You did not authorize the request')
return redirect(url_for('index'))
try:
creds = {'request_token': request_token,
'request_token_secret': request_token_secret}
params = {'oauth_verifier': request.args['oauth_verifier']}
sess = twitter.get_auth_session(params=params, **creds)
except Exception, e:
flash('There was a problem logging into Twitter: ' + str(e))
return redirect(url_for('index'))
verify = sess.get('account/verify_credentials.json',
params={'format':'json'}).json()
User.get_or_create(verify['screen_name'], verify['id'])
flash('Logged in as ' + verify['name'])
return redirect(url_for('index'))
if __name__ == '__main__':
db.create_all()
app.run()
Hope that helps!