mirror of
https://github.com/wassname/flask-security.git
synced 2026-06-27 16:10:11 +08:00
Add moar tests!
This commit is contained in:
+18
-46
@@ -12,7 +12,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
from flask import Flask, render_template, current_app
|
||||
from flask import Flask, render_template
|
||||
from flask_mail import Mail
|
||||
|
||||
from flask_security import Security, MongoEngineUserDatastore, SQLAlchemyUserDatastore, \
|
||||
@@ -59,7 +59,7 @@ def app():
|
||||
def http_custom_realm():
|
||||
return render_template('index.html', content='HTTP Authentication')
|
||||
|
||||
@app.route('/token')
|
||||
@app.route('/token', methods=['GET', 'POST'])
|
||||
@auth_token_required
|
||||
def token():
|
||||
return render_template('index.html', content='Token Authentication')
|
||||
@@ -96,40 +96,6 @@ def app():
|
||||
def unauthorized():
|
||||
return render_template('unauthorized.html')
|
||||
|
||||
@app.route('/coverage/add_role_to_user')
|
||||
def add_role_to_user():
|
||||
ds = current_app.security.datastore
|
||||
u = ds.find_user(email='joe@lp.com')
|
||||
r = ds.find_role('admin')
|
||||
ds.add_role_to_user(u, r)
|
||||
return 'success'
|
||||
|
||||
@app.route('/coverage/remove_role_from_user')
|
||||
def remove_role_from_user():
|
||||
ds = current_app.security.datastore
|
||||
u = ds.find_user(email='matt@lp.com')
|
||||
ds.remove_role_from_user(u, 'admin')
|
||||
return 'success'
|
||||
|
||||
@app.route('/coverage/deactivate_user')
|
||||
def deactivate_user():
|
||||
ds = current_app.security.datastore
|
||||
u = ds.find_user(email='matt@lp.com')
|
||||
ds.deactivate_user(u)
|
||||
return 'success'
|
||||
|
||||
@app.route('/coverage/activate_user')
|
||||
def activate_user():
|
||||
ds = current_app.security.datastore
|
||||
u = ds.find_user(email='tiya@lp.com')
|
||||
ds.activate_user(u)
|
||||
return 'success'
|
||||
|
||||
@app.route('/coverage/invalid_role')
|
||||
def invalid_role():
|
||||
ds = current_app.security.datastore
|
||||
return 'success' if ds.find_role('bogus') is None else 'failure'
|
||||
|
||||
@app.route('/page1')
|
||||
def page_1():
|
||||
return 'Page 1'
|
||||
@@ -158,7 +124,7 @@ def mongoengine_datastore(request, app):
|
||||
class User(db.Document, UserMixin):
|
||||
email = db.StringField(unique=True, max_length=255)
|
||||
username = db.StringField(max_length=255)
|
||||
password = db.StringField(required=True, max_length=255)
|
||||
password = db.StringField(required=False, max_length=255)
|
||||
last_login_at = db.DateTimeField()
|
||||
current_login_at = db.DateTimeField()
|
||||
last_login_ip = db.StringField(max_length=100)
|
||||
@@ -237,7 +203,7 @@ def peewee_datastore(request, app, tmpdir):
|
||||
class User(db.Model, UserMixin):
|
||||
email = TextField()
|
||||
username = TextField()
|
||||
password = TextField()
|
||||
password = TextField(null=True)
|
||||
last_login_at = DateTimeField(null=True)
|
||||
current_login_at = DateTimeField(null=True)
|
||||
last_login_ip = TextField(null=True)
|
||||
@@ -287,14 +253,9 @@ def mongoengine_app(app, mongoengine_datastore):
|
||||
return create
|
||||
|
||||
|
||||
@pytest.fixture(params=['sqlalchemy', 'mongoengine', 'peewee'])
|
||||
def client(request, sqlalchemy_app, mongoengine_app, peewee_app):
|
||||
if request.param == 'sqlalchemy':
|
||||
app = sqlalchemy_app()
|
||||
elif request.param == 'mongoengine':
|
||||
app = mongoengine_app()
|
||||
elif request.param == 'peewee':
|
||||
app = peewee_app()
|
||||
@pytest.fixture()
|
||||
def client(request, sqlalchemy_app):
|
||||
app = sqlalchemy_app()
|
||||
populate_data(app)
|
||||
return app.test_client()
|
||||
|
||||
@@ -305,3 +266,14 @@ def get_message(app):
|
||||
rv = app.config['SECURITY_MSG_' + key][0] % kwargs
|
||||
return rv.encode('utf-8')
|
||||
return fn
|
||||
|
||||
|
||||
@pytest.fixture(params=['sqlalchemy', 'mongoengine', 'peewee'])
|
||||
def datastore(request, sqlalchemy_datastore, mongoengine_datastore, peewee_datastore):
|
||||
if request.param == 'sqlalchemy':
|
||||
rv = sqlalchemy_datastore
|
||||
elif request.param == 'mongoengine':
|
||||
rv = mongoengine_datastore
|
||||
elif request.param == 'peewee':
|
||||
rv = peewee_datastore
|
||||
return rv
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM CHANGE PASSWORD
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM FORGOT PASSWORD
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM LOGIN USER
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM REGISTER USER
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM RESET PASSWORD
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM SEND CONFIRMATION
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
CUSTOM SEND LOGIN
|
||||
|
||||
{{ foo }}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
CUSTOM RESET INSTRUCTIONS
|
||||
|
||||
{{ foo }}
|
||||
@@ -49,16 +49,22 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
|
||||
'new_password': 'newpassword',
|
||||
'new_password_confirm': 'notnewpassword'
|
||||
}, follow_redirects=True)
|
||||
assert get_message('PASSWORD_CHANGE') not in response.data
|
||||
assert get_message('RETYPE_PASSWORD_MISMATCH') in response.data
|
||||
|
||||
# Test missing password
|
||||
response = client.post('/change', data={
|
||||
'password': ' ',
|
||||
'new_password': '',
|
||||
'new_password_confirm': ''
|
||||
}, follow_redirects=True)
|
||||
assert get_message('PASSWORD_NOT_PROVIDED') in response.data
|
||||
|
||||
# Test bad password
|
||||
response = client.post('/change', data={
|
||||
'password': 'password',
|
||||
'new_password': 'a',
|
||||
'new_password_confirm': 'a'
|
||||
}, follow_redirects=True)
|
||||
assert get_message('PASSWORD_CHANGE') not in response.data
|
||||
assert get_message('PASSWORD_INVALID_LENGTH') in response.data
|
||||
|
||||
# Test same as previous
|
||||
@@ -67,7 +73,6 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
|
||||
'new_password': 'password',
|
||||
'new_password_confirm': 'password'
|
||||
}, follow_redirects=True)
|
||||
assert get_message('PASSWORD_CHANGE') not in response.data
|
||||
assert get_message('PASSWORD_IS_THE_SAME') in response.data
|
||||
|
||||
# Test successful submit sends email notification
|
||||
@@ -84,6 +89,13 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
|
||||
assert len(outbox) == 1
|
||||
assert "Your password has been changed" in outbox[0].html
|
||||
|
||||
# Test JSON
|
||||
data = ('{"password": "newpassword", "new_password": "newpassword2", '
|
||||
'"new_password_confirm": "newpassword2"}')
|
||||
response = client.post('/change', data=data, headers={'Content-Type': 'application/json'})
|
||||
assert response.status_code == 200
|
||||
assert response.headers['Content-Type'] == 'application/json'
|
||||
|
||||
|
||||
def test_custom_change_url(app, sqlalchemy_datastore, get_message):
|
||||
client = _get_client(app, sqlalchemy_datastore, **{
|
||||
|
||||
+29
-9
@@ -28,6 +28,18 @@ def test_authenticate(client):
|
||||
assert b'Hello matt@lp.com' in response.data
|
||||
|
||||
|
||||
def test_authenticate_with_next(client):
|
||||
data = dict(email='matt@lp.com', password='password')
|
||||
response = client.post('/login?next=/page1', data=data, follow_redirects=True)
|
||||
assert b'Page 1' in response.data
|
||||
|
||||
|
||||
def test_authenticate_with_invalid_next(client, get_message):
|
||||
data = dict(email='matt@lp.com', password='password')
|
||||
response = client.post('/login?next=http://google.com', data=data)
|
||||
assert get_message('INVALID_REDIRECT') in response.data
|
||||
|
||||
|
||||
def test_authenticate_case_insensitive_email(client):
|
||||
response = authenticate(client, email='MATT@lp.com', follow_redirects=True)
|
||||
assert b'Hello matt@lp.com' in response.data
|
||||
@@ -58,6 +70,11 @@ def test_inactive_user(client, get_message):
|
||||
assert get_message('DISABLED_ACCOUNT') in response.data
|
||||
|
||||
|
||||
def test_unset_password(client, get_message):
|
||||
response = authenticate(client, "jess@lp.com", "password")
|
||||
assert get_message('PASSWORD_NOT_SET') in response.data
|
||||
|
||||
|
||||
def test_logout(client):
|
||||
authenticate(client)
|
||||
response = logout(client, follow_redirects=True)
|
||||
@@ -199,6 +216,9 @@ def test_multi_auth_basic(client):
|
||||
})
|
||||
assert b'Basic' in response.data
|
||||
|
||||
response = client.get('/multi_auth')
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_multi_auth_token(client):
|
||||
response = json_authenticate(client)
|
||||
@@ -245,12 +265,12 @@ def test_token_loader_does_not_fail_with_invalid_token(client):
|
||||
assert b'BadSignature' not in response.data
|
||||
|
||||
|
||||
def test_coverage_endpoints(client):
|
||||
for endpoint in [
|
||||
'/coverage/add_role_to_user',
|
||||
'/coverage/remove_role_from_user',
|
||||
'/coverage/activate_user',
|
||||
'/coverage/deactivate_user'
|
||||
]:
|
||||
response = client.get(endpoint)
|
||||
assert b'success' in response.data
|
||||
def test_sending_auth_token_with_json(client):
|
||||
response = json_authenticate(client)
|
||||
token = response.jdata['response']['user']['authentication_token']
|
||||
data = '{"auth_token": "%s"}' % token
|
||||
response = client.post('/token', data=data, headers={'Content-Type': 'application/json'})
|
||||
assert b'Token Authentication' in response.data
|
||||
assert b'Token Authentication' in response.data
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_context_processors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Context processor tests
|
||||
"""
|
||||
|
||||
from utils import authenticate, init_app_with_options
|
||||
|
||||
|
||||
def test_context_processors(app, sqlalchemy_datastore):
|
||||
init_app_with_options(app, sqlalchemy_datastore, **{
|
||||
'SECURITY_RECOVERABLE': True,
|
||||
# 'SECURITY_PASSWORDLESS': True,
|
||||
'SECURITY_REGISTERABLE': True,
|
||||
'SECURITY_CONFIRMABLE': True,
|
||||
'SECURITY_CHANGEABLE': True,
|
||||
'SECURITY_LOGIN_WITHOUT_CONFIRMATION': True,
|
||||
'SECURITY_CHANGE_PASSWORD_TEMPLATE': 'custom_security/change_password.html',
|
||||
'SECURITY_LOGIN_USER_TEMPLATE': 'custom_security/login_user.html',
|
||||
# 'SECURITY_SEND_LOGIN_TEMPLATE': 'custom_security/send_login.html',
|
||||
'SECURITY_RESET_PASSWORD_TEMPLATE': 'custom_security/reset_password.html',
|
||||
'SECURITY_FORGOT_PASSWORD_TEMPLATE': 'custom_security/forgot_password.html',
|
||||
'SECURITY_SEND_CONFIRMATION_TEMPLATE': 'custom_security/send_confirmation.html',
|
||||
'SECURITY_REGISTER_USER_TEMPLATE': 'custom_security/register_user.html'
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
@app.security.forgot_password_context_processor
|
||||
def forgot_password():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/reset')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.login_context_processor
|
||||
def login():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/login')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.register_context_processor
|
||||
def register():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/register')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.reset_password_context_processor
|
||||
def reset_password():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/reset')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.change_password_context_processor
|
||||
def change_password():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
authenticate(client)
|
||||
response = client.get('/change')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.send_confirmation_context_processor
|
||||
def send_confirmation():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/confirm')
|
||||
assert b'bar' in response.data
|
||||
|
||||
@app.security.mail_context_processor
|
||||
def mail():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
with app.mail.record_messages() as outbox:
|
||||
client.post('/reset', data=dict(email='matt@lp.com'))
|
||||
|
||||
email = outbox[0]
|
||||
assert b'bar' in email.html
|
||||
|
||||
|
||||
def test_passwordless_login_context_processor(app, sqlalchemy_datastore):
|
||||
init_app_with_options(app, sqlalchemy_datastore, **{
|
||||
'SECURITY_PASSWORDLESS': True,
|
||||
'SECURITY_SEND_LOGIN_TEMPLATE': 'custom_security/send_login.html',
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
@app.security.send_login_context_processor
|
||||
def send_login():
|
||||
return {'foo': 'bar'}
|
||||
|
||||
response = client.get('/login')
|
||||
assert b'bar' in response.data
|
||||
|
||||
# @app.security.mail_context_processor
|
||||
# def mail():
|
||||
# return {'foo': 'bar'}
|
||||
|
||||
|
||||
|
||||
|
||||
+79
-1
@@ -8,20 +8,28 @@
|
||||
|
||||
from pytest import raises
|
||||
|
||||
from flask_security import UserMixin
|
||||
from flask_security import UserMixin, RoleMixin
|
||||
from flask_security.datastore import Datastore, UserDatastore
|
||||
|
||||
from utils import init_app_with_options
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
pass
|
||||
|
||||
|
||||
class Role(RoleMixin):
|
||||
pass
|
||||
|
||||
|
||||
def test_unimplemented_datastore_methods():
|
||||
datastore = Datastore(None)
|
||||
assert datastore.db is None
|
||||
with raises(NotImplementedError):
|
||||
datastore.put(None)
|
||||
with raises(NotImplementedError):
|
||||
datastore.delete(None)
|
||||
assert not datastore.commit()
|
||||
|
||||
|
||||
def test_unimplemented_user_datastore_methods():
|
||||
@@ -30,6 +38,8 @@ def test_unimplemented_user_datastore_methods():
|
||||
datastore.find_user(None)
|
||||
with raises(NotImplementedError):
|
||||
datastore.find_role(None)
|
||||
with raises(NotImplementedError):
|
||||
datastore.get_user(None)
|
||||
|
||||
|
||||
def test_toggle_active():
|
||||
@@ -70,3 +80,71 @@ def test_activate_returns_false_if_already_true():
|
||||
user = User()
|
||||
user.active = True
|
||||
assert not datastore.activate_user(user)
|
||||
|
||||
|
||||
def test_get_user(app, datastore):
|
||||
init_app_with_options(app, datastore, **{
|
||||
'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username')
|
||||
})
|
||||
|
||||
with app.app_context():
|
||||
user_id = datastore.find_user(email='matt@lp.com').id
|
||||
|
||||
user = datastore.get_user(user_id)
|
||||
assert user is not None
|
||||
|
||||
user = datastore.get_user('matt@lp.com')
|
||||
assert user is not None
|
||||
|
||||
user = datastore.get_user('matt')
|
||||
assert user is not None
|
||||
|
||||
|
||||
def test_find_role(app, datastore):
|
||||
init_app_with_options(app, datastore)
|
||||
|
||||
role = datastore.find_role('admin')
|
||||
assert role is not None
|
||||
|
||||
role = datastore.find_role('bogus')
|
||||
assert role is None
|
||||
|
||||
|
||||
def test_add_role_to_user(app, datastore):
|
||||
init_app_with_options(app, datastore)
|
||||
|
||||
# Test with user object
|
||||
user = datastore.find_user(email='matt@lp.com')
|
||||
assert user.has_role('editor') is False
|
||||
assert datastore.add_role_to_user(user, 'editor') is True
|
||||
assert datastore.add_role_to_user(user, 'editor') is False
|
||||
assert user.has_role('editor') is True
|
||||
|
||||
# Test with email
|
||||
assert datastore.add_role_to_user('jill@lp.com', 'editor') is True
|
||||
user = datastore.find_user(email='jill@lp.com')
|
||||
assert user.has_role('editor') is True
|
||||
|
||||
# Test remove role
|
||||
assert datastore.remove_role_from_user(user, 'editor') is True
|
||||
assert datastore.remove_role_from_user(user, 'editor') is False
|
||||
|
||||
|
||||
def test_create_user_with_roles(app, datastore):
|
||||
init_app_with_options(app, datastore)
|
||||
|
||||
user = datastore.create_user(email='dude@lp.com', username='dude',
|
||||
password='password', roles=['admin'])
|
||||
datastore.commit()
|
||||
user = datastore.find_user(email='dude@lp.com')
|
||||
assert user.has_role('admin') is True
|
||||
|
||||
|
||||
def test_delete_user(app, datastore):
|
||||
init_app_with_options(app, datastore)
|
||||
|
||||
user = datastore.find_user(email='matt@lp.com')
|
||||
datastore.delete_user(user)
|
||||
datastore.commit()
|
||||
user = datastore.find_user(email='matt@lp.com')
|
||||
assert user is None
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
Email functionality tests
|
||||
"""
|
||||
|
||||
from pytest import raises
|
||||
|
||||
from flask_security import Security
|
||||
from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \
|
||||
SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \
|
||||
@@ -140,3 +142,35 @@ def test_addition_identity_attributes(app, sqlalchemy_datastore):
|
||||
client = app.test_client()
|
||||
response = authenticate(client, email='matt', follow_redirects=True)
|
||||
assert b'Hello matt@lp.com' in response.data
|
||||
|
||||
|
||||
def test_flash_messages_off(app, sqlalchemy_datastore, get_message):
|
||||
init_app_with_options(app, sqlalchemy_datastore, **{
|
||||
'SECURITY_FLASH_MESSAGES': False
|
||||
})
|
||||
client = app.test_client()
|
||||
response = client.get('/profile')
|
||||
assert get_message('LOGIN') not in response.data
|
||||
|
||||
|
||||
def test_invalid_hash_scheme(app, sqlalchemy_datastore, get_message):
|
||||
with raises(ValueError):
|
||||
init_app_with_options(app, sqlalchemy_datastore, **{
|
||||
'SECURITY_PASSWORD_HASH': 'bogus'
|
||||
})
|
||||
|
||||
|
||||
def test_change_hash_type(app, sqlalchemy_datastore):
|
||||
init_app_with_options(app, sqlalchemy_datastore, **{
|
||||
'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext']
|
||||
})
|
||||
|
||||
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
|
||||
app.config['SECURITY_PASSWORD_SALT'] = 'salty'
|
||||
|
||||
app.security = Security(app, datastore=sqlalchemy_datastore, register_blueprint=False)
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post('/login', data=dict(email='matt@lp.com', password='password'))
|
||||
assert response.status_code == 302
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_trackable_flag(app, sqlalchemy_datastore, get_message):
|
||||
# Test login with json and invalid email
|
||||
data = '{"email": "nobody@lp.com", "password": "password"}'
|
||||
response = client.post('/login', data=data, headers={'Content-Type': 'application/json'})
|
||||
assert 'errors' in response.data
|
||||
assert b'errors' in response.data
|
||||
|
||||
# Test sends email and shows appropriate response
|
||||
with capture_passwordless_login_requests() as requests:
|
||||
|
||||
@@ -57,12 +57,21 @@ def test_registerable_flag(app, sqlalchemy_datastore, get_message):
|
||||
|
||||
# Test registering with JSON
|
||||
data = '{ "email": "dude2@lp.com", "password": "password"}'
|
||||
response = client.post('/register', data=data, content_type='application/json')
|
||||
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
|
||||
assert response.headers['content-type'] == 'application/json'
|
||||
assert response.jdata['meta']['code'] == 200
|
||||
|
||||
logout(client)
|
||||
|
||||
# Test registering with invalid JSON
|
||||
data = '{ "email": "bogus", "password": "password"}'
|
||||
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
|
||||
print response.data
|
||||
assert response.headers['content-type'] == 'application/json'
|
||||
assert response.jdata['meta']['code'] == 400
|
||||
|
||||
logout(client)
|
||||
|
||||
# Test ?next param
|
||||
data = dict(email='dude3@lp.com',
|
||||
password='password',
|
||||
|
||||
+7
-3
@@ -39,11 +39,14 @@ def create_users(ds, count=None):
|
||||
('joe@lp.com', 'joe', 'password', ['editor'], True),
|
||||
('dave@lp.com', 'dave', 'password', ['admin', 'editor'], True),
|
||||
('jill@lp.com', 'jill', 'password', ['author'], True),
|
||||
('tiya@lp.com', 'tiya', 'password', [], False)]
|
||||
('tiya@lp.com', 'tiya', 'password', [], False),
|
||||
('jess@lp.com', 'jess', None, [], True)]
|
||||
count = count or len(users)
|
||||
|
||||
for u in users[:count]:
|
||||
pw = encrypt_password(u[2])
|
||||
pw = u[2]
|
||||
if pw is not None:
|
||||
pw = encrypt_password(pw)
|
||||
roles = [ds.find_or_create_role(rn) for rn in u[3]]
|
||||
ds.commit()
|
||||
user = ds.create_user(email=u[0], username=u[1], password=pw, active=u[4])
|
||||
@@ -75,6 +78,7 @@ class Response(BaseResponse): # pragma: no cover
|
||||
|
||||
|
||||
def init_app_with_options(app, datastore, **options):
|
||||
security_args = options.pop('security_args', {})
|
||||
app.config.update(**options)
|
||||
app.security = Security(app, datastore=datastore)
|
||||
app.security = Security(app, datastore=datastore, **security_args)
|
||||
populate_data(app)
|
||||
|
||||
Reference in New Issue
Block a user