Tout, j'écris une application de fiole qui dépend de Flacon-directeur pour la gestion des rôles d'utilisateur . J'aimerais écrire quelques tests d'unité simples pour vérifier quelles vues sont accessibles par quel utilisateur. Un exemple de code est affiché sur Pastebin pour éviter d'encombrer ce poste. En bref, je définis quelques itinéraires, décorant certains de sorte qu'ils ne peuvent être accessibles que par les utilisateurs ayant le rôle approprié, puis essayez d'y accéder dans un test. P>
dans le code collé, le Toute aide ou opinion sur la complexité du traitement de contexte sera profondément appréciée. P> test_member code> et
test_admin_b code> échoue, se plaignant d'un
PermissionDenied code>. De toute évidence, je ne parviens pas à déclarer l'utilisateur correctement; Au moins, les informations sur les rôles de l'utilisateur ne sont pas dans le bon contexte. P>
3 Réponses :
Flacon-directeur ne stocke pas les informations pour vous entre les demandes. C'est à vous de faire cela cependant que vous aimez. Gardez cela à l'esprit et pensez à vos tests un instant. Vous appelez la méthode code> test_request_context code> dans la méthode code> SETUPCLASC code>. Cela crée un nouveau contexte de demande. Vous effectuez également des appels de clients de test avec self.client.get (..) code> dans vos tests. Ces appels créent des contextes de demande supplémentaires qui ne sont pas partagés entre eux. Ainsi, vos appels vers
identity_changed.send (..) code> ne se produisent pas dans le contexte des demandes qui vérifient les autorisations. Je suis allé de l'avant et j'ai édité votre code pour que les tests passent dans l'espoir de vous aider à comprendre. Payez une attention particulière au filtre
avant_request code> J'ai ajouté dans la méthode
Create_App code>.
import hmac
import unittest
from functools import wraps
from hashlib import sha1
import flask
from flask.ext.principal import Principal, Permission, RoleNeed, Identity, \
identity_changed, identity_loaded current_app
def roles_required(*roles):
"""Decorator which specifies that a user must have all the specified roles.
Example::
@app.route('/dashboard')
@roles_required('admin', 'editor')
def dashboard():
return 'Dashboard'
The current user must have both the `admin` role and `editor` role in order
to view the page.
:param args: The required roles.
Source: https://github.com/mattupstate/flask-security/
"""
def wrapper(fn):
@wraps(fn)
def decorated_view(*args, **kwargs):
perms = [Permission(RoleNeed(role)) for role in roles]
for perm in perms:
if not perm.can():
# return _get_unauthorized_view()
flask.abort(403)
return fn(*args, **kwargs)
return decorated_view
return wrapper
def roles_accepted(*roles):
"""Decorator which specifies that a user must have at least one of the
specified roles. Example::
@app.route('/create_post')
@roles_accepted('editor', 'author')
def create_post():
return 'Create Post'
The current user must have either the `editor` role or `author` role in
order to view the page.
:param args: The possible roles.
"""
def wrapper(fn):
@wraps(fn)
def decorated_view(*args, **kwargs):
perm = Permission(*[RoleNeed(role) for role in roles])
if perm.can():
return fn(*args, **kwargs)
flask.abort(403)
return decorated_view
return wrapper
def _on_principal_init(sender, identity):
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret', TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
@app.before_request
def determine_identity():
# This is where you get your user authentication information. This can
# be done many ways. For instance, you can store user information in the
# session from previous login mechanism, or look for authentication
# details in HTTP headers, the querystring, etc...
identity_changed.send(current_app._get_current_object(), identity=Identity('admin'))
@app.route('/')
def index():
return "OK"
@app.route('/member')
@roles_accepted('admin', 'member')
def role_needed():
return "OK"
@app.route('/admin')
@roles_required('admin')
def connect_admin():
return "OK"
@app.route('/admin_b')
@admin_permission.require()
def connect_admin_alt():
return "OK"
return app
admin_permission = Permission(RoleNeed('admin'))
class WorkshopTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
def test_basic(self):
r = self.client.get('/')
self.assertEqual(r.data, "OK")
def test_member(self):
r = self.client.get('/member')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
def test_admin_b(self):
r = self.client.get('/admin_b')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
if __name__ == '__main__':
unittest.main()
C'est ce dont je craignais: je me perds dans des contextes. Afaiu, votre déterminer_Identity code> sera appelé avant la procédure de traitement, en utilisant le même contexte, non? Donc, j'ai besoin de déclarer une identité quelque part dans ce contexte, ou de le récupérer d'un contexte global ou de le créer à la volée à partir de certains arguments supplémentaires passés à la demande (par exemple, le
Query_string code>) .. . Je vais essayer de poster des solutions dans une autre réponse, je serais très reconnaissant si vous pouviez me faire savoir ce que vous pensiez.
Correct. Mais je ne suis pas sûr de la raison pour laquelle vous craignez cela. Et oui, la fonction déterminer_identify code> sera appelée sur chaque requête et partagera le même contexte avec vos méthodes de vue. Déterminer l'identité dépend tout de la manière dont vous envisagez d'authentifier les utilisateurs. Par exemple, si vous souhaitez un mécanisme d'authentification basé sur une session, vous devriez faire une paire de flacon-directeur avec le ballon-connexion. Si vous construisez une API qui est apatride, vous devez passer des paramètres d'authentification dans les en-têtes ou utiliser l'autoroute HTTP de base et déterminer l'utilisateur dans le
déterminer_identity code> à partir de ces valeurs.
Une chose que je n'ai pas mentionné, c'est que le flacon-directeur, par défaut, enregistre l'identité de la session, la première fois que vous appelez la méthode identity_changed.send code>. Il stocke l'identité de la session et de charger Pour chaque demande, à l'exception des points d'extrémité statiques.
Pour remettre les choses dans le contexte (jeu de mots): Je prends une application qui fonctionne comme prévu, mais a un manque d'insuffisance (mauvais, Bad Kitty). Cette application utilise flask.login code> pour identification et
flack.principal code> pour la gestion des rôles. Je voulais ajouter les tests manquants sans modifier la logique actuelle. Une fois qu'un utilisateur est connecté, ses rôles sont définis et son identité
enregistrée dans la session, comme vous l'avez souligné. Dans les tests de l'unité, je dois changer d'identité et c'est là que votre détermination_Identify vient jouer ...
@Mattw Comment gérer les rôles et les autorisations stockées dans une DB? Dans mon cas, les autorisations sont constantes et les rôles sont créés de manière dynamique. Pour EX, un utilisateur peut avoir un certain nombre de rôles. Pour chaque rôle, il devrait y avoir un ensemble d'autorisations. Dans ce cas, comment j'identifie plus d'un rôles à l'identité?
comme Matt expliqué, ce n'est qu'une question de contexte. Grâce à ses explications, je suis venu avec deux manières différentes de changer d'identité lors des tests unitaires.
Avant tout, modifiez un peu la création d'applications: P>
class WorkshopTestTwo(unittest.TestCase): # @classmethod def setUpClass(cls): app = create_app() cls.app = app cls.client = app.test_client() cls.testing = app.test_request_context def test_admin(self): with self.testing("/admin") as c: r = c.app.full_dispatch_request() self.assertEqual(r.status_code, 403) # with self.testing("/admin") as c: identity_changed.send(c.app, identity=Identity("member")) r = c.app.full_dispatch_request() self.assertEqual(r.status_code, 403) # with self.testing("/admin") as c: identity_changed.send(c.app, identity=Identity("admin")) r = c.app.full_dispatch_request() self.assertEqual(r.status_code, 200) self.assertEqual(r.data, "OK")
Le long de la réponse de Matt, j'ai créé un gestionnaire de contexte pour rendre la clémédecine un peu plus propre: Alors, quand j'exécute mon test, on dirait: P> with identity_setter(self.app,user):
with user_set(self.app, user):
with self.app.test_client() as c:
response = c.get('/orders/' + order.public_key + '/review')