I'd like to create a session with specific user email and password, for that I need to pass these two pieces of information to a pytest fixture as arguments.
My attempt to do so:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def user_session():
def _update_credentials(email, password):
user_credentials = {
"email": email,
"password": password
}
return user_credentials
session = ApiMethods()
session.get_authorized(_update_credentials)
return session
The problem is that _update_credentials returns a function object instead of JSON, which raises TypeError: Object of type function is not JSON serializable
What sould I do to provide email and password to this fixture correctly? Help me, please :)
What it seems you're trying to do is to use the factory as fixture pattern like this:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def update_credentials():
def _update_credentials(email, password):
user_credentials = {
"email": email,
"password": password
}
return user_credentials
return _update_credentials
#fixture(scope="session")
def user_session(update_credentials):
user_credentials = update_credentials("email1", "password1")
session = ApiMethods()
session.get_authorized(user_credentials)
return session
But it seems you could just do this instead:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def user_session():
user_credentials = {
"email": email,
"password": password
}
session = ApiMethods()
session.get_authorized(user_credentials)
return session
Related
I'm using a fixture in pytest that returns a client that has been logged in:
#pytest.fixture
def create_client(create_user) -> APIClient:
data = {
"email": create_user.email,
"password": "TestPassword",
}
client = APIClient()
client.post(path="/user/login/", data=data)
return client
How I get the user that has been logged in in the test?
I understand that I might be able to use a get a request from the client and get it that way:
def test_get_user(create_client):
response = create_client.get(path="/some/random/path/")
user = response.user
return user
but is there a better way to do this?
I'm sure that the create_user is another fixture and thus you can have that in your test_get_user(...) as
def test_get_user(create_client, create_user):
print(create_user)
# do some tests here
I am trying to authenticate and get an access token. I have created user class, I am trying to run the POST method from POSTMAN while authenticating, but I am receiving some error:
{
"description": "Invalid credentials",
"error": "Bad Request",
"status_code": 401
}
and I couldn't find any solution.
Code for app.py
from flask import Flask, request
from flask_restful import Resource, Api
from flask_jwt import JWT, jwt_required
from security import authenticate, identity
# creating flask app
app = Flask(__name__)
app.secret_key = 'vishwas'
api = Api(app)
jwt = JWT(app, authenticate, identity) # /auth
# empty list of items
items = []
class Item(Resource):
#jwt_required()
def get(self,name):
# next return first value that found by the filter function
# next(filter(), None) -> 'None' to handle the eroor if the list is empty
item = next(filter(lambda x: x['name'] == name,items), None)
return {'item': item}, 200 if item else 404
# http://127.0.0.1.5000/item/<string:name>
api.add_resource(Item, '/item/<string:name>')
app.run(port=5000, debug=True)
Code for security.py
from werkzeug.security import safe_str_cmp
from user import User
# list of users
users = [
User(1,"bob","pass")
]
# users information using their username
username_mapping = {user.username: user for user in users}
# users information using their userid
userid_mapping = {user.id: user for user in users}
def authenticate(username,password):
user = userid_mapping.get(username, None)
if user and safe_str_cmp(user.password, password):
return user
def identity(payload):
user_id = payload['identity']
return userid_mapping.get(user_id, None)
Code for user.py
class User:
def __init__(self,_id,username,password):
self.id = _id
self.username = username
self.password = password
As you can see that I have implemented the code correctly but still I am getting this 'Invalid Credentials' or 'Bad Request' error.
if you look at what is stored in the data:
def authenticate(username, password):
print(username_mapping)
we will see:
{('bob',): <user.User object at 0x000002C7DC982B00>}
that is, the ('bob',) key, not bob
I myself only study potshon, so the decision to make only this
def authenticate(username, password):
user = username_mapping.get(('{}'.format(username),), None)
if user and safe_str_cmp(user.password, password):
return user
And you mistake there, instead username_mapping, you're using userid_mapping
And accordingly:
def identity(payload):
user_id = payload['identity']
return userid_mapping.get((user_id[0],), None)
I don’t know how much is correct, most likely it is necessary to bring the initial data to the correct type, but it works. Maybe someone will tell you how it is more correct.
Make sure to use username instead of name
"name": "mostafa",
"password": "pass"
}
should be this
"username": "mostafa",
"password": "pass"
}
I am using the firebase-admin-python SDK to handle authentication between an iOS app and a flask backend (python). This is my backend authentication endpoint, following the firebase guide:
from flask import request
from firebase_admin import auth
def get():
"""accessed via '/api/authtoken' """
try:
fir_token = request.headers["Authorization"]
decoded_token = auth.verify_id_token(fir_token)
fir_auth_id = decoded_token["uid"]
except:
...
How do I mock the fir_token for a unit test? How do I also mock auth.verify_id_token such that I don't need to actually connect to the firebase server?
Put the logic behind an interface.
class TokenVerifier(object):
def verify(self, token):
raise NotImplementedError()
class FirebaseTokenVerifier(TokenVerifier):
def verify(self, token):
return auth.verify_id_token(token)
class MockTokenVerifier(TokenVerifier):
def verify(self, token):
# Return mock object that will help pass your tests.
# Or raise an error to simulate token validation failures.
Then make sure during unit tests your code uses the MockTokenVerifier.
It is also possible to create mock ID tokens, and stub out parts of the Admin SDK so that the auth.verify_id_token() runs normally during tests (see unit tests of the SDK). But I prefer the above solution since it's easier, cleaner and doesn't require messing with the internals of the SDK.
I achieved this using the patch decorator from unittest.mock library.
auth.py
...
from flask_restful import Resource, reqparse
from firebase_admin import auth
class Token(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('token', type=str, required=True)
# route for /api/auth/token
def post(self):
args = self.parser.parse_args()
try:
firebase_user = auth.verify_id_token(args['token'])
except Exception:
abort(401)
# use firebase_user
test_auth.py
from unittest import mock
mock_firebase_user = {
'user_id': 'firebasegenerateduserid',
'email': 'testuser#gmail.com',
# ... add more firebase return values
}
# client is from conftest.py
def test_auth(client):
with mock.patch('auth.auth.verify_id_token') as magic_mock:
magic_mock.return_value = mock_firebase_user
post_data = {
'token': 'firebaserusertokenid'
}
response = client.post('/api/auth/token', data=post_data)
assert response.status_code == 200
In the Flask initalization file, I have
app = Flask("myapp")
jwt = JWT(app, AuthController.staff_login, identity)
api.add_resource(StaffLogin, '/login')
StaffLogin checks for the username / password, and will in turn call AuthController.staff_login
class AuthController():
def __init__(self):
# TODO: Create a config.yaml
self.engine = create_engine(DefaultConfig.SQLALCHEMY_DATABASE_URI)
Base.metadata.create_all(self.engine)
DBSession = sessionmaker(bind=self.engine)
self.session = DBSession()
def staff_login(self, staff_name, staff_password):
result = self.session.query(Staff) # Irrelevant details of query removed
if Staff_Authentication.check_password(staff_password, query['staff_password']):
# Build login_response as JSON
return login_response
Now I need a custom login response in this format
[
{
"projects": [
{
"project_id": 1,
"project_name": "omnipresence",
"project_root": "/path/to/project/files"
}
],
"staff_id": 13,
"staff_name": "adalovelace"
}
]
Question
How do I get Flask-JWT to use my function as authenticate?
Flask-JWT is old and has been abandoned. Check out flask-jwt-extended or flask-jwt-simple as alternatives, they are better designed and still maintained (I am the author of those extensions, so I am of course biased).
They are setup so you provide your own endpoint instead of having the extension manage an endpoint for you.
I'm facing a strange issue while testing a CherryPy app when testing.
Basically, session data is lost between requests while testing, as when running the server and testing manually this does not happen.
The app itself is quite simple, but some resource are protected using the mechanism of hook to be triggered before the request is processed.
Let's see the main file:
import cherrypy
import hashlib
import json
import sys
from bson import json_util
from cr.db.store import global_settings as settings
from cr.db.store import connect
SESSION_KEY = 'user'
main = None
def protect(*args, **kwargs):
"""
Just a hook for checking protected resources
:param args:
:param kwargs:
:return: 401 if unauthenticated access found (based on session id)
"""
# Check if provided endpoint requires authentication
condition = cherrypy.request.config.get('auth.require', None)
if condition is not None:
try:
# Try to get the current session
cherrypy.session[SESSION_KEY]
# cherrypy.session.regenerate()
cherrypy.request.login = cherrypy.session[SESSION_KEY]
except KeyError:
raise cherrypy.HTTPError(401, u'Not authorized to access this resource. Please login.')
# Specify the hook
cherrypy.tools.crunch = cherrypy.Tool('before_handler', protect)
class Root(object):
def __init__(self, db_settings):
self.db = connect(db_settings)
#cherrypy.expose
#cherrypy.config(**{'auth.require': True, 'tools.crunch.on': False})
def index(self):
# If authenticated, return to users view
if SESSION_KEY in cherrypy.session:
raise cherrypy.HTTPRedirect(u'/users', status=301)
else:
return 'Welcome to this site. Please login.'
#cherrypy.tools.allow(methods=['GET', 'POST'])
#cherrypy.expose
#cherrypy.config(**{'auth.require': True})
#cherrypy.tools.json_in()
def users(self, *args, **kwargs):
if cherrypy.request.method == 'GET':
return json.dumps({'users': [u for u in self.db.users.find()]}, default=json_util.default)
elif cherrypy.request.method == 'POST':
# Get post form data and create a new user
input_json = cherrypy.request.json
new_id = self.db.users.insert_one(input_json)
new_user = self.db.users.find_one(new_id.inserted_id)
cherrypy.response.status = 201
return json.dumps(new_user, default=json_util.default)
#cherrypy.tools.allow(methods=['GET', 'POST'])
#cherrypy.expose
#cherrypy.config(**{'tools.crunch.on': False})
def login(self, *args, **kwargs):
if cherrypy.request.method == 'GET':
# Check if user is logged in already
if SESSION_KEY in cherrypy.session:
return """<html>
<head></head>
<body>
<form method="post" action="logout">
<label>Click button to logout</label>
<button type="submit">Logout</button>
</form>
</body>
</html>"""
return """<html>
<head></head>
<body>
<form method="post" action="login">
<input type="text" value="Enter email" name="username" />
<input type="password" value="Enter password" name="password" />
<button type="submit">Login</button>
</form>
</body>
</html>"""
elif cherrypy.request.method == 'POST':
# Get post form data and create a new user
if 'password' and 'username' in kwargs:
user = kwargs['username']
password = kwargs['password']
if self.user_verify(user, password):
cherrypy.session.regenerate()
cherrypy.session[SESSION_KEY] = cherrypy.request.login = user
# Redirect to users
raise cherrypy.HTTPRedirect(u'/users', status=301)
else:
raise cherrypy.HTTPError(u'401 Unauthorized')
else:
raise cherrypy.HTTPError(u'401 Please provide username and password')
#cherrypy.tools.allow(methods=['GET'])
#cherrypy.expose
def logout(self):
if SESSION_KEY in cherrypy.session:
cherrypy.session.regenerate()
return 'Logged out, we will miss you dearly!.'
else:
raise cherrypy.HTTPRedirect(u'/', status=301)
def user_verify(self, username, password):
"""
Simply checks if a user with provided email and pass exists in db
:param username: User email
:param password: User pass
:return: True if user found
"""
users = self.db.users
user = users.find_one({"email": username})
if user:
password = hashlib.sha1(password.encode()).hexdigest()
return password == user['hash']
return False
if __name__ == '__main__':
config_root = {'/': {
'tools.crunch.on': True,
'tools.sessions.on': True,
'tools.sessions.name': 'crunch', }
}
# This simply specifies the URL for the Mongo db
settings.update(json.load(open(sys.argv[1])))
main = Root(settings)
cherrypy.quickstart(main, '/', config=config_root)
cr.db is a very simple wrapper over pymongo that exposes the db functionality, nothing special.
As you can see the users view is protected, basically if the SESSION['user'] key is not set, we ask to login.
If I fire up the server and try to access /users directly I'm redirected to /login. Once loged in, visiting /users again works fine, since
cherrypy.session[SESSION_KEY]
Does not throw an KeyError since it was properly set in /login. Everything cool.
Now this is my test file, based on official docs about testing, located at same level as file above.
import urllib
from unittest.mock import patch
import cherrypy
from cherrypy.test import helper
from cherrypy.lib.sessions import RamSession
from .server import Root
from cr.db.store import global_settings as settings
from cr.db.loader import load_data
DB_URL = 'mongodb://localhost:27017/test_db'
SERVER = 'http://127.0.0.1'
class SimpleCPTest(helper.CPWebCase):
def setup_server():
cherrypy.config.update({'environment': "test_suite",
'tools.sessions.on': True,
'tools.sessions.name': 'crunch',
'tools.crunch.on': True,
})
db = {
"url": DB_URL
}
settings.update(db)
main = Root(settings)
# Simply loads some dummy users into test db
load_data(settings, True)
cherrypy.tree.mount(main, '/')
setup_server = staticmethod(setup_server)
# HELPER METHODS
def get_redirect_path(self, data):
"""
Tries to extract the path from the cookie data obtained in a response
:param data: The cookie data from the response
:return: The path if possible, None otherwise
"""
path = None
location = None
# Get the Location from response, if possible
for tuples in data:
if tuples[0] == 'Location':
location = tuples[1]
break
if location:
if SERVER in location:
index = location.find(SERVER)
# Path plus equal
index = index + len(SERVER) + 6
# Get the actual path
path = location[index:]
return path
def test_login_shown_if_not_logged_in(self):
response = self.getPage('/')
self.assertStatus('200 OK')
self.assertIn('Welcome to Crunch. Please login.', response[2].decode())
def test_login_redirect_to_users(self):
# Try to authenticate with a wrong password
data = {
'username': 'john#doe.com',
'password': 'admin',
}
query_string = urllib.parse.urlencode(data)
self.getPage("/login", method='POST', body=query_string)
# Login should show 401
self.assertStatus('401 Unauthorized')
# Try to authenticate with a correct password
data = {
'username': 'john#doe.com',
'password': '123456',
}
query_string = urllib.parse.urlencode(data)
# Login should work and be redirected to users
self.getPage('/login', method='POST', body=query_string)
self.assertStatus('301 Moved Permanently')
def test_login_no_credentials_throws_401(self):
# Login should show 401
response = self.getPage('/login', method='POST')
self.assertStatus('401 Please provide username and password')
def test_login_shows_login_logout_forms(self):
# Unauthenticated GET should show login form
response = self.getPage('/login', method='GET')
self.assertStatus('200 OK')
self.assertIn('<form method="post" action="login">', response[2].decode())
# Try to authenticate
data = {
'username': 'john#doe.com',
'password': '123456',
}
query_string = urllib.parse.urlencode(data)
# Login should work and be redirected to users
response = self.getPage('/login', method='POST', body=query_string)
self.assertStatus('301 Moved Permanently')
# FIXME: Had to mock the session, not sure why between requests while testing the session loses
# values, this would require more investigation, since when firing up the real server works fine
# For now let's just mock it
sess_mock = RamSession()
sess_mock['user'] = 'john#doe.com'
with patch('cherrypy.session', sess_mock, create=True):
# Make a GET again
response = self.getPage('/login', method='GET')
self.assertStatus('200 OK')
self.assertIn('<form method="post" action="logout">', response[2].decode())
As you can see in last method, after login, we should have cherrpy.session[SESSION_KEY] set, but for some reason the session does not have the key. That's the reason I actually had to mock it...this works, but is hacking something that should actually work...
To me it looks like when testing the session is not being kept between requests. Before digging into CherrPy internals I wanted to ask this in case someone stumbled upon something similar in the past.
Notice I'm using Python 3.4 here.
Thanks
getPage() accepts headers argument and produces self.cookies iterable. But it does not pass it over to the next request automatically, so it doesn't get the same session cookies.
I've crafted a simple example of how to persist session with the next request:
>>> test_cp.py <<<
import cherrypy
from cherrypy.test import helper
class SimpleCPTest(helper.CPWebCase):
#staticmethod
def setup_server():
class Root:
#cherrypy.expose
def login(self):
if cherrypy.request.method == 'POST':
cherrypy.session['test_key'] = 'test_value'
return 'Hello'
elif cherrypy.request.method in ['GET', 'HEAD']:
try:
return cherrypy.session['test_key']
except KeyError:
return 'Oops'
cherrypy.config.update({'environment': "test_suite",
'tools.sessions.on': True,
'tools.sessions.name': 'crunch',
})
main = Root()
# Simply loads some dummy users into test db
cherrypy.tree.mount(main, '')
def test_session_sharing(self):
# Unauthenticated GET
response = self.getPage('/login', method='GET')
self.assertIn('Oops', response[2].decode())
# Authenticate
response = self.getPage('/login', method='POST')
self.assertIn('Hello', response[2].decode())
# Make a GET again <<== INCLUDE headers=self.cookies below:
response = self.getPage('/login', headers=self.cookies, method='GET')
self.assertIn('test_value', response[2].decode())
Running it
$ pytest
Test session starts (platform: linux, Python 3.6.1, pytest 3.0.7, pytest-sugar 0.8.0)
rootdir: ~/src/test, inifile:
plugins: sugar-0.8.0, backports.unittest-mock-1.3
test_cp.py ✓✓ 100% ██████████
Results (0.41s):
2 passed
P.S. Of course, ideally I'd inherit testcase class and add additional method to encapsulate this ;)