how to write test cases for authenticated urls in flask - python

I am using flask with mongoengine and Login Manager for session maintaining. I want to write test cases for authenticated views. can any one help/suggestions regarding this.

First off, I recommend using pytest, and the flask-pytest library which contains some great convenience features to make all of this easier.
flask-pytest comes out of the box with a client fixture, which, per the docs, refers to Flask.test_client
What you want to do is mimic a valid session (e.g. however you app is validating that a user is "logged in").
Here is how to do this without any supporting libraries:
import app
from flask import url_for
def test_authenticated_route():
app.testing = True
client = app.test_client()
with client.session_transaction() as sess:
# here you can assign whatever you need to
# emulate a "logged in" user...
sess["user"] = {"email": "test_user#example.com"}
# now, you can access "authenticated" endpoints
response = client.get(url_for(".protected_route"))
assert response.status_code == 200
This is also discussed in the Flask docs.

Related

Cookie is not created when calling the endpoint in FastAPI

I have encountered an issue, as I have to create a cookie in the backend, which I will later use to send a request from the frontend. Both apps are on the same domain. This is the general idea behind it: https://levelup.gitconnected.com/secure-frontend-authorization-67ae11953723.
Frontend - Sending GET request to Backend
#app.get('/')
async def homepage(request: Request, response_class=HTMLResponse):
keycloak_code = 'sksdkssdk'
data = {'code': keycloak_code}
url_post = 'http://127.0.0.1:8002/keycloak_code'
post_token=requests.get(url=url_post, json = data )
return 'Sent'
if __name__ == '__main__':
uvicorn.run(app, host='local.me.me', port=7999,debug=True)
Backend
#app.get("/keycloak_code")
def get_tokens(response: Response, data: dict):
code = data['code']
print(code)
....
requests.get(url='http://local.me.me:8002/set')
return True
#app.get("/set")
async def createcookie(response: Response):
r=response.set_cookie(key='tokic3', value='helloworld', httponly=True)
return True
if __name__ == '__main__':
uvicorn.run(app, host='local.me.me', port=8002, log_level="debug")
When I open the browser and access http://local.me.me:8002/set, I can see that the cookie is created.
But when I make a GET request from my frontend to backend to the same URL, the request is received—as I can see in the terminal—but the backend does not create the cookie. Does anyone know what I might be doing wrong?
I have tried different implementations from FastAPI docs, but none has similar use cases.
127.0.0.1 and localhost (or local.me.me in your case) are two different domains (and origins). Hence, when making a request you need to use the same domain you used for creating the cookie. For example, if the cookie was created for local.me.me domain, then you should use that domain when sending the request. See related posts here, as well as here and here.
You also seem to have a second FastAPI app (listenning on a different port) acting as your frontend (as you say). If that's what you are trying to do, you would need to use Session Objects in Python requests module, or preferably, use a Client instance from httpx library, in order to persist cookies across requests. The advantage of httpx is that it offers an asynchronous API as well, using the httpx.AsyncClient(). You can find more details and examples in this answer, as well as here and here.

Failed to use auth code flow with python MSAL

I simply can't get acquire_token_by_auth_code_flow() from the MSAL package to work outside a flask app using the basic example giving in the MSAL documentation.
I think the problem comes from using the wrong authentication response which must be a "dict of the query string received from auth server" according to the documentation. In a flask app, I can simply use request.args which I'm not quite sure how to use outside of flask.
I've already tried using requests.request as well as urlsplit. The device flow is working fine as well as using the MSAL package in Java and connecting via R. So the app seems to be set up correctly.
The basic example code from the MSAL app below produces the error:
state mismatch: XXXXXXXXXXXX vs None
(so auth_response is wrong).
Any thoughts?
import requests
import msal
CLIENT_ID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" # Application (client) ID of app registration
CLIENT_SECRET = "XX-XXXXXXXX-XXXXXXXX.XX~XXXXX~XXXX" # Placeholder - for use ONLY during testing.
AUTHORITY = "https://login.microsoftonline.com/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX"
REDIRECT_PATH = "/getAToken" # Used for forming an absolute URL to your redirect URI.
# The absolute URL must match the redirect URI you set
# in the app's registration in the Azure portal.
ENDPOINT = 'https://graph.microsoft.com/v1.0/me'
SCOPE = ["https://graph.microsoft.com/.default"]
# Cache
cache = msal.SerializableTokenCache()
# Build msal app
app = msal.ConfidentialClientApplication(
CLIENT_ID, authority=AUTHORITY,
client_credential=CLIENT_SECRET, token_cache=cache)
# Initiate auth code flow
session = requests.Session()
session.flow = app.initiate_auth_code_flow(scopes=SCOPE, redirect_uri=REDIRECT_PATH)
# Aquire token
result = app.acquire_token_by_auth_code_flow(auth_code_flow=session.flow, auth_response = dict(parse.parse_qsl(parse.urlsplit(REDIRECT_PATH).query)))
The equivalent code for the last bit from the flask app looks like this with REDIRECT_PATH = "/getAToken":
#app.route(app_config.REDIRECT_PATH) # Its absolute URL must match your app's redirect_uri set in AAD
def authorized():
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
return redirect(url_for("index"))
Getting a token requires few requests according to documentation. To make it possible you need to create flow and store it inside session before navigating to microsoft login page.
session["flow"] = _build_auth_code_flow(authority=app_config.AUTHORITY, scopes=app_config.SCOPE)
After navigation back to your application you should use this flow object as you did in your example
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
Make sure that you didn't create it twice. In this case error will be similar, but state mismatch: XXXXXXXXXXXX vs XXXXXXXXXXXX. It may happened if you route called twice.
auth_response must be a dictionary built from the current HTTP request query params.
If this is a desktop application you must switch to PublicClientApplication. You can find a sample here.

Using OAuth to authenticate Office 365/Graph users with Django

We are creating an application for use in our organization, but we only want people in our organization to be able to use the app. We had the idea of using Microsoft's OAuth endpoint in order to authenticate whether a user is part of our org or not. The idea is to bring up a sign in screen where the user can enter their Office 365 username and password, which will then allow them to use our app upon submission of their credentials.
Our app is running on Django, and I've only found a solution to this problem using Flask and Microsoft's Graph API connect sample for Python (See code snippet below). This sample uses a similar idea to the one above to log in to the app. Are there any similar methods of authentication for Django?
import requests
from flask import Flask, redirect, url_for, session, request, render_template
from flask_oauthlib.client import OAuth
# read private credentials from text file
client_id, client_secret, *_ = open('_PRIVATE.txt').read().split('\n')
if (client_id.startswith('*') and client_id.endswith('*')) or \
(client_secret.startswith('*') and client_secret.endswith('*')):
print('MISSING CONFIGURATION: the _PRIVATE.txt file needs to be edited ' + \
'to add client ID and secret.')
sys.exit(1)
app = Flask(__name__)
app.debug = True
app.secret_key = 'development'
oauth = OAuth(app)
# since this sample runs locally without HTTPS, disable InsecureRequestWarning
requests.packages.urllib3.disable_warnings()
msgraphapi = oauth.remote_app( \
'microsoft',
consumer_key=client_id,
consumer_secret=client_secret,
request_token_params={'scope': 'User.Read Mail.Send'},
base_url='https://graph.microsoft.com/v1.0/',
request_token_url=None,
access_token_method='POST',
access_token_url='https://login.microsoftonline.com/common/oauth2/v2.0/token',
authorize_url='https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
)
#app.route('/login')
def login():
"""Handler for login route."""
guid = uuid.uuid4() # guid used to only accept initiated logins
session['state'] = guid
return msgraphapi.authorize(callback=url_for('authorized', _external=True), state=guid)
#app.route('/login/authorized')
def authorized():
"""Handler for login/authorized route."""
response = msgraphapi.authorized_response()
if response is None:
return "Access Denied: Reason={0}\nError={1}".format( \
request.args['error'], request.args['error_description'])
# Check response for state
if str(session['state']) != str(request.args['state']):
raise Exception('State has been messed with, end authentication')
session['state'] = '' # reset session state to prevent re-use
# Okay to store this in a local variable, encrypt if it's going to client
# machine or database. Treat as a password.
session['microsoft_token'] = (response['access_token'], '')
# Store the token in another session variable for easy access
session['access_token'] = response['access_token']
me_response = msgraphapi.get('me')
me_data = json.loads(json.dumps(me_response.data))
username = me_data['displayName']
email_address = me_data['userPrincipalName']
session['alias'] = username
session['userEmailAddress'] = email_address
return redirect('main')
You should be able to use just about any OAUTH 2.0 library for Python. I've not worked with Django but I know there are several out there for Python.
I came across django-azure-ad-auth which seems to be exactly what you're looking for.
I also found a general OAUTH library called django-allauth which seems to have a lot of activity. It doesn't have a built-in provider but the model they use for providers seems simple enough that you may be able to extend it without too much trouble.

simple Authentication and ACL using cornice

I have a RESTful API written in pyramid/cornice. It provides an API for an Ember client.
I have followed the cornice tutorial and have a valid_token validator which I use on many views as methods of resource classes.
def valid_token(request):
header = 'Authorization'
token = request.headers.get(header)
if token is None:
request.errors.add('headers', header, "Missing token")
request.errors.status = 401
return
session = DBSession.query(Session).get(token)
if not session:
request.errors.add('headers', header, "invalid token")
request.errors.status = 401
request.validated['session'] = session
Now I want to start selectively protecting resources. The Pyramid way seems to be to register authentication/authorization policies. The ACLAuthorizationPolicy seems to provide access to the nice ACL tooling in pyramid. However, it seems that pyramid needs both authentication and authorization policies to function. Since I'm authenticating with my validator this is confusing me.
Can I use ACL to control authorization whilst authenticating using my cornice valid_token validator? Do I need to register pyramid authentication or authorization policies?
I'm a bit confused, having little experience of using ACL in pyramid.
It is not an easy question :)
Shortly:
What you implemented in your validator is already taken care of by Pyramid with an AuthenticationPolicy
Start setting up a SessionAuthenticationPolicy with your custom callback (see code)
Once this authn setup, you will have those 401 responses, and your session value in the request.authenticated_userid attribute. You can also custom stuff in the request.registry object.
The only reason to keep your validator is if you want to return the invalid token messages in the 401 response. But for that, you can define a custom 401 pyramid view (using #forbidden_view_config)
Once you have that, you can setup a custom authorization for your views. You can find a very simple example in Cliquet first versions here : authz code and view perm
Good luck!
You may wanna do something like:
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from your_module import valid_token
authn_policy = SessionAuthenticationPolicy(debug=True, callback=valid_token)
authz_policy = ACLAuthorizationPolicy()
config = Configurator(authentication_policy=authn_policy,authorization_policy=authz_policy)
And ofcourse in the Configuration will receive other arguments like settigns, locale_negociator, ...........
Hope this will help

Passing Credentials From Desktop Application to Django App

This is more of a general newbie question:
How do desktop applications that hook up to a service typically verify users? How would I do this for a Django app? Would it be as simple as passing the credentials to a blank view that checks the username / password?
How is it typically passed?
Thanks
You can create a custom login view on the django side, as detailed here. Have it return a message based on the whether the username and password parameters (should probably be sent via a HTTP POST, preferably over SSL) were valid. On the desktop client, if the response is valid, it should get the value of the cookie that got sent along with the response, and it should keep that cookie with every HTTP request that is made for the rest of the session.
Not sure what you meant, but for example if you want to check user's credentials from desktop python application by some method in django app, you can use httplib module and send POST request and then check the response you get. Make something kind of:
>>> import urllib
>>> params = urllib.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
>>> f = urllib.urlopen("http://www.musi-cal.com/cgi-bin/query", params)
>>> print f.read()
(Example taken from Python docs)

Categories