How do I access Flask Session inside a slack_bolt handler? - flask

I have a Slack App that handles commands and actions, that follows the documented way of handling requests using a SlackRequestHandler for flask. However, I would like to use the flask session inside the slack_bolt function, and understandably it won't let me because it has no access to the request at that point in the code, and sessions don't exist without a request. However, I should be able to access the flask session somehow, given that the slack_bolt function is called by the SlackRequestHandler inside the flask view, which does have a request context. My question is, how can I access the session within the slack_bolt handler below?
from flask import Flask, request, Response, session
from flask_session import Session
import slack
from slackeventsapi import SlackEventAdapter
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
flask_app = Flask(__name__)
SESSION_TYPE = "filesystem"
flask_app.config.from_object(__name__)
Session(flask_app)
bolt_app = App(token=os.environ['SLACK_TOKEN'], signing_secret=os.environ['SIGNING_SECRET'])
handler = SlackRequestHandler(bolt_app)
#flask_app.route('/route1', methods=['POST'])
def route1():
return handler.handle(request)
session['test] = 'value1' # This works fine
#bolt_app.command('/route1')
def handle_route1(body, ack, respond, client, logger):
ack()
session['test'] = 'value2' # This is what I am trying to do without success
The error I get is:
Failed to run listener function (error: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.)
I tried using:
with app.request_context(####):
session['test']= 'value2'
But I don't know how to get the context object to pass to request_context, or even if this is the right thing to do. Thank you very much for any help!

Related

CSRF invalid token for API call in Flask

My objective is to build Rest API on Flask. When i send post JSON request to the server from Postman application http://127.0.0.1:5000/api/v1/posts/add/post, I get "The CSRF token is missing" Error.
Respective route is here:
#api.route('/posts/add/post', methods=['POST'])
def add_post():
subject = request.json['subject']
body = request.json['body']
myPost = Post(subject=subject, body=body, category_id=1)
db.session.add(myPost)
db.session.commit()
return post_schema.jsonify(myPost).data
I have looked at different examples and nobody mentions about CSRF for such requests, as usually its used for WTF s.
Can you please advice, what am i doing wrong and fow to fix it?
Based on this extract from the docs, is it possible you are protecting your app like this?
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
Usually, you only need this for basic flask apps (docs):
from flask import Flask
app = Flask(__name__)

Django to catch all invalid logins and unauthorized access like 401s and 403s

I want to capture all invalid logins/unauthorized access such as 401s and 403s returned from the site so I can log them to a security logging service, investigating if there is an easy way to catch all of these without putting in much custom logic.
I have tried using middleware approach:
def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
response = get_response(request)
if response.status_code in [403, 401]:
log.warning('invalid login')
return response
return middleware
Unfortunately an incorrect login to the /admin/ login, it returns status 200, however I think this would work for custom login that explicitly throws 401/403.
I have also tried using the signal approach using request_finished but all I get is just the handler class.
So... looking for ideas.
As you found out, a login attempt doesn't necessarily imply a specific response code, since you may decide to treat the attempt with a redirect or any other type of answer.
In case of Django, the default auth middleware (which I assume you are using) fires a user_login_failed signal which you can handle with your logging logic.
You can see in the documentation how to register a signal handler, so it should be something like
from django.contrib.auth.signals import user_login_failed
from django.dispatch import receiver
#receiver(request_finished)
def handle_login_failed(sender, **kwargs):
print(f"Oops, login failed using these credentials: {kwargs.get('credentials', None)}")
The signal triggering is in the source code for the auth package.

Flask with SQLAlchemy working outside of application context

I have a Flask app and I use SQLAlachemy(without Flask extension cause I need to create my own class based SQLAlchemy and so on).
My app has a connection to its database over engine and it works fine but now I need to make my engine dynamically and get db_name from Flask.g
Engine is declared in models.py
models.py
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
For the start app I use wsgi.py:
from app import app
if __name__ == "__main__":
app.run(port=5002)
when I type python wsgi.py I receive an 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 some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
In general I understand that I use an engine which is outside the context. The issue is - I cannot figure out how to pass my engine variable to context.
I try to make create app func:
def create_app():
app = Flask(__name__)
with app.app_context():
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
return app
Also I tried app.app_context().push(engine)
But it doesn't work. How I can solve this issue?
The problem is that the flask object g only ever exists when a request is currently in progress on flask. (Without a request, there is no flask g, because g is specifically a global for individual requests)
what you'd have to do is to create that engine after the request starts, which slows down the route a bit. The #app.before_request decorator might help you here:
#app.before_request
def create_engine_for_request():
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
("before_request" is already "during a request" - its just the first thing that flask does "when a request starts")

Flask Blueprint : how to run each apps in different port?

I got a flask application with different apps inside, using BluePrint.
To simplify, I got an API that manages token web authentification (and a lot of other data functions) and a website that should call the API to get the valid token, using a basic auth to start with
The issue is that when the website requests the API, it never gets any feedback from the API.
Requesting the API via POSTMAN works like a charm, but this call below, done from a website route is waiting, waiting, waiting and never ends.
So my assumption is that using the same port for both website and api is the issue
I could of course divides the flask into 2 flask apps with 2 servers, but there are many objects and tools both API and website are sharing, so I dont want to double the job
Thanks.
call from the website
from requests.auth import HTTPBasicAuth
import requests
mod = Blueprint('site', __name__, template_folder='templates/login')
def load_user(username, password):
data = requests.get('http://127.0.0.1:5000/api/login',
auth=HTTPBasicAuth('username', 'password'))
return data
#mod.route('/')
def index():
username = 'jeje'
password = 'jeje'
data = load_user(username, password)
return '<h1>load user<h1>'
the api function
#mod.route('/login')
def login():
resu = True
auth = request.authorization
if not auth or not auth.username or not auth.password:
resu = False
user = USER.query.filter_by(username = auth.username).first()
if not user:
resu = False
if validehash(user.password, auth.password):
period_in_mn = 120
payload = {
'public_id':user.public_id,
'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes = period_in_mn)
}
token = createtoken(payload, current_app.config['SECRET_KEY'])
if resu:
return jsonify({'token' : token })
else:
return jsonify({'token' : 'unknown'})
I guess your using flask 0.12 instead of 1.0. So whats happening here is that you're requesting a route from within another route.
Your browser requests /, and in your index function, you request /login. But the flask process is still working on the / request from the browser, and can't take any other requests, because flask 0.12 is working on a single core only.
Requesting like this is bad design. You could make a helper function, which returns the same data in different requests (either api or main site), or you could make the browser send another request using AJAX.
Flask 1.0 has multicore support, so I think this might work over there, but I really think you should change your design. This has absolutely nothing to do with blueprints by the way.
from flask import Flask, redirect
import requests
app = Flask(__name__)
#app.route('/')
def index():
result = requests.get('http://127.0.0.1:5000/api/login')
return 'this is the index<br>' + result.text
#app.route('/api/login')
def login():
return 'you are logged in'
Using flask 0.12, times out when visiting http://127.0.0.1:5000/.
Using flask 1.0, returns this is the index
you are logged in when visiting http://127.0.0.1:5000/.

How to use session in TestCase in Django?

I would like to read some session variables from a test (Django TestCase)
How to do that in a clean way ?
def test_add_docs(self):
"""
Test add docs
"""
# I would access to the session here:
self.request.session['documents_to_share_ids'] = [1]
response = self.client.get(reverse(self.document_add_view_id, args=[1]), follow=True)
self.assertEquals(response.status_code, 200)
As of Django 1.7+ this is much easier. Make sure you set the session as a variable instead of referencing it directly.
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
andreaspelme's workaround is only needed in older versions of django. See docs
Unfortunately, this is not a easy as you would hope for at the moment. As you might have noticed, just using self.client.session directly will not work if you have not called other views that has set up the sessions with appropriate session cookies for you. The session store/cookie must then be set up manually, or via other views.
There is an open ticket to make it easier to mock sessions with the test client: https://code.djangoproject.com/ticket/10899
In addition to the workaround in the ticket, there is a trick that can be used if you are using django.contrib.auth. The test clients login() method sets up a session store/cookie that can be used later in the test.
If you have any other views that sets sessions, requesting them will do the trick too (you probably have another view that sets sessions, otherwise your view that reads the sessions will be pretty unusable).
from django.test import TestCase
from django.contrib.auth.models import User
class YourTest(TestCase):
def test_add_docs(self):
# If you already have another user, you might want to use it instead
User.objects.create_superuser('admin', 'foo#foo.com', 'admin')
# self.client.login sets up self.client.session to be usable
self.client.login(username='admin', password='admin')
session = self.client.session
session['documents_to_share_ids'] = [1]
session.save()
response = self.client.get('/') # request.session['documents_to_share_ids'] will be available
If you need to initialize a session for the request in tests to manipulate it directly:
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
request = HttpRequest()
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
If testing my django project with pytest, I can not see any modifications to the session that are made in the view. (That is because the Sessions middleware doesn't get called.)
I found the following approach to be useful:
from unittest.mock import patch
from django.test import Client
from django.contrib.sessions.backends import db
def test_client_with_session():
client = Client()
session = {} # the session object that will persist throughout the test
with patch.object(db, "SessionStore", return_value=session):
client.post('/url-that-sets-session-key/')
assert session['some_key_set_by_the_view']
client.get('/url-that-reads-session-key/')
This approach has the benefit of not requiring database access.
You should be able to access a Client's session variales through its session property, so I guess self.client.session['documents_to_share_ids'] = [1] should be what you are looking for!