Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • uffd/uffd
  • rixx/uffd
  • thies/uffd
  • leona/uffd
  • enbewe/uffd
  • strifel/uffd
  • thies/uffd-2
7 results
Show changes
Showing
with 4569 additions and 832 deletions
import time
import unittest
from flask import url_for
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.session.views import get_current_user, login_required, is_valid_session
from uffd import create_app, db
from utils import dump, UffdTestCase
class TestSession(UffdTestCase):
def setUpApp(self):
self.app.config['SESSION_LIFETIME_SECONDS'] = 2
@self.app.route('/test_login_required')
@login_required()
def test_login_required():
return 'SUCCESS', 200
@self.app.route('/test_group_required1')
@login_required(group='users')
def test_group_required1():
return 'SUCCESS', 200
@self.app.route('/test_group_required2')
@login_required(group='notagroup')
def test_group_required2():
return 'SUCCESS', 200
def setUp(self):
super().setUp()
self.assertFalse(is_valid_session())
def login(self):
self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
self.assertTrue(is_valid_session())
def assertLogin(self):
self.assertTrue(is_valid_session())
self.assertEqual(self.client.get(path=url_for('test_login_required'),
follow_redirects=True).data, b'SUCCESS')
self.assertEqual(get_current_user().loginname, 'testuser')
def assertLogout(self):
self.assertFalse(is_valid_session())
self.assertNotEqual(self.client.get(path=url_for('test_login_required'),
follow_redirects=True).data, b'SUCCESS')
self.assertEqual(get_current_user(), None)
def test_login(self):
self.assertLogout()
r = self.client.get(path=url_for('session.login'), follow_redirects=True)
dump('login', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
dump('login_post', r)
self.assertEqual(r.status_code, 200)
self.assertLogin()
def test_redirect(self):
r = self.client.post(path=url_for('session.login', ref=url_for('test_login_required')),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, b'SUCCESS')
def test_wrong_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'wrongpassword'}, follow_redirects=True)
dump('login_wrong_password', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
def test_empty_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': ''}, follow_redirects=True)
dump('login_empty_password', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
def test_wrong_user(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'nouser', 'password': 'userpassword'}, follow_redirects=True)
dump('login_wrong_user', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
def test_empty_user(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': '', 'password': 'userpassword'}, follow_redirects=True)
dump('login_empty_user', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
def test_no_access(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testservice', 'password': 'servicepassword'}, follow_redirects=True)
dump('login_no_access', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
def test_group_required(self):
self.login()
self.assertEqual(self.client.get(path=url_for('test_group_required1'),
follow_redirects=True).data, b'SUCCESS')
self.assertNotEqual(self.client.get(path=url_for('test_group_required2'),
follow_redirects=True).data, b'SUCCESS')
def test_logout(self):
self.login()
r = self.client.get(path=url_for('session.logout'), follow_redirects=True)
dump('logout', r)
self.assertEqual(r.status_code, 200)
self.assertLogout()
@unittest.skip('See #29')
def test_timeout(self):
self.login()
time.sleep(3)
self.assertLogout()
def test_ratelimit(self):
for i in range(20):
self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'wrongpassword_%i'%i}, follow_redirects=True)
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
dump('login_ratelimit', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(is_valid_session())
class TestSessionOL(TestSession):
use_openldap = True
import unittest
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from uffd.tasks import CleanupTask
class TestCleanupTask(unittest.TestCase):
def test(self):
app = Flask(__name__)
app.testing = True
app.debug = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
db = SQLAlchemy(app)
cleanup_task = CleanupTask()
@cleanup_task.delete_by_attribute('delete_me')
class TestModel(db.Model):
id = db.Column(db.Integer(), primary_key=True, autoincrement=True)
delete_me = db.Column(db.Boolean(), default=False, nullable=False)
with app.test_request_context():
db.create_all()
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=False))
db.session.add(TestModel(delete_me=False))
db.session.commit()
db.session.expire_all()
self.assertEqual(TestModel.query.count(), 5)
with app.test_request_context():
cleanup_task.run()
db.session.commit()
db.session.expire_all()
with app.test_request_context():
self.assertEqual(TestModel.query.count(), 2)
import datetime
import time
import unittest
from flask import url_for, session
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.user.models import User
from uffd.role.models import Role
from uffd.session.views import get_current_user, is_valid_session
from uffd.mfa.models import MFAMethod, MFAType, RecoveryCodeMethod, TOTPMethod, WebauthnMethod, _hotp
from uffd import create_app, db
from utils import dump, UffdTestCase
def get_user():
return User.query.get('uid=testuser,ou=users,dc=example,dc=com')
def get_user_password():
return get_user().pwhash
def get_admin():
return User.query.get('uid=testadmin,ou=users,dc=example,dc=com')
class TestUserModel(UffdTestCase):
def test_has_permission(self):
user = get_user() # has 'users' and 'uffd_access' group
admin = get_admin() # has 'users', 'uffd_access' and 'uffd_admin' group
self.assertTrue(user.has_permission(None))
self.assertTrue(admin.has_permission(None))
self.assertTrue(user.has_permission('users'))
self.assertTrue(admin.has_permission('users'))
self.assertFalse(user.has_permission('notagroup'))
self.assertFalse(admin.has_permission('notagroup'))
self.assertFalse(user.has_permission('uffd_admin'))
self.assertTrue(admin.has_permission('uffd_admin'))
self.assertFalse(user.has_permission(['uffd_admin']))
self.assertTrue(admin.has_permission(['uffd_admin']))
self.assertFalse(user.has_permission(['uffd_admin', 'notagroup']))
self.assertTrue(admin.has_permission(['uffd_admin', 'notagroup']))
self.assertFalse(user.has_permission(['notagroup', 'uffd_admin']))
self.assertTrue(admin.has_permission(['notagroup', 'uffd_admin']))
self.assertTrue(user.has_permission(['uffd_admin', 'users']))
self.assertTrue(admin.has_permission(['uffd_admin', 'users']))
self.assertTrue(user.has_permission([['uffd_admin', 'users'], ['users', 'uffd_access']]))
self.assertTrue(admin.has_permission([['uffd_admin', 'users'], ['users', 'uffd_access']]))
self.assertFalse(user.has_permission(['uffd_admin', ['users', 'notagroup']]))
self.assertTrue(admin.has_permission(['uffd_admin', ['users', 'notagroup']]))
class TestUserModelOL(TestUserModel):
use_openldap = True
class TestUserViews(UffdTestCase):
def setUp(self):
super().setUp()
self.client.post(path=url_for('session.login'),
data={'loginname': 'testadmin', 'password': 'adminpassword'}, follow_redirects=True)
def test_index(self):
r = self.client.get(path=url_for('user.index'), follow_redirects=True)
dump('user_index', r)
self.assertEqual(r.status_code, 200)
def test_new(self):
db.session.add(Role(name='base'))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
db.session.commit()
role1_id = role1.id
r = self.client.get(path=url_for('user.show'), follow_redirects=True)
dump('user_new', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com'))
r = self.client.post(path=url_for('user.update'),
data={'loginname': 'newuser', 'mail': 'newuser@example.com', 'displayname': 'New User',
f'role-{role1_id}': '1', 'password': 'newpassword'}, follow_redirects=True)
dump('user_new_submit', r)
self.assertEqual(r.status_code, 200)
user = User.query.get('uid=newuser,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'newuser@example.com')
self.assertTrue(user.uid)
role1 = Role(name='role1')
print('test_new', role1.db_members, role1.members, user.roles)
self.assertEqual(roles, ['base', 'role1'])
# TODO: check password hash
def test_new_invalid_loginname(self):
r = self.client.post(path=url_for('user.update'),
data={'loginname': '!newuser', 'mail': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_invalid_loginname', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com'))
def test_new_empty_loginname(self):
r = self.client.post(path=url_for('user.update'),
data={'loginname': '', 'mail': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_empty_loginname', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com'))
def test_new_empty_email(self):
r = self.client.post(path=url_for('user.update'),
data={'loginname': 'newuser', 'mail': '', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_empty_email', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com'))
def test_new_invalid_display_name(self):
r = self.client.post(path=url_for('user.update'),
data={'loginname': 'newuser', 'mail': 'newuser@example.com', 'displayname': 'A'*200,
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_invalid_display_name', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com'))
def test_update(self):
user = get_user()
db.session.add(Role(name='base'))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
role2.members.add(user)
db.session.commit()
role1_id = role1.id
oldpw = get_user_password()
r = self.client.get(path=url_for('user.show', uid=user.uid), follow_redirects=True)
dump('user_update', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', uid=user.uid),
data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'New User',
f'role-{role1_id}': '1', 'password': ''}, follow_redirects=True)
dump('user_update_submit', r)
self.assertEqual(r.status_code, 200)
_user = get_user()
roles = sorted([r.name for r in _user.roles])
self.assertEqual(_user.displayname, 'New User')
self.assertEqual(_user.mail, 'newuser@example.com')
self.assertEqual(_user.uid, user.uid)
self.assertEqual(_user.loginname, user.loginname)
self.assertEqual(get_user_password(), oldpw)
self.assertEqual(roles, ['base', 'role1'])
def test_update_password(self):
user = get_user()
oldpw = get_user_password()
r = self.client.get(path=url_for('user.show', uid=user.uid), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', uid=user.uid),
data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_update_password', r)
self.assertEqual(r.status_code, 200)
_user = get_user()
self.assertEqual(_user.displayname, 'New User')
self.assertEqual(_user.mail, 'newuser@example.com')
self.assertEqual(_user.uid, user.uid)
self.assertEqual(_user.loginname, user.loginname)
self.assertNotEqual(get_user_password(), oldpw)
@unittest.skip('See #28')
def test_update_invalid_password(self):
user = get_user()
oldpw = get_user_password()
r = self.client.get(path=url_for('user.show', uid=user.uid), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', uid=user.uid),
data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'New User',
'password': 'A'}, follow_redirects=True)
dump('user_update_password', r)
self.assertEqual(r.status_code, 200)
_user = get_user()
self.assertEqual(get_user_password(), oldpw)
self.assertEqual(_user.displayname, user.displayname)
self.assertEqual(_user.mail, user.mail)
self.assertEqual(_user.loginname, user.loginname)
def test_update_empty_email(self):
user = get_user()
oldpw = get_user_password()
r = self.client.get(path=url_for('user.show', uid=user.uid), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', uid=user.uid),
data={'loginname': 'testuser', 'mail': '', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_update_empty_mail', r)
self.assertEqual(r.status_code, 200)
_user = get_user()
self.assertEqual(_user.displayname, user.displayname)
self.assertEqual(_user.mail, user.mail)
self.assertEqual(_user.loginname, user.loginname)
self.assertEqual(get_user_password(), oldpw)
def test_update_invalid_display_name(self):
user = get_user()
oldpw = get_user_password()
r = self.client.get(path=url_for('user.show', uid=user.uid), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', uid=user.uid),
data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'A'*200,
'password': 'newpassword'}, follow_redirects=True)
dump('user_update_invalid_display_name', r)
self.assertEqual(r.status_code, 200)
_user = get_user()
self.assertEqual(_user.displayname, user.displayname)
self.assertEqual(_user.mail, user.mail)
self.assertEqual(_user.loginname, user.loginname)
self.assertEqual(get_user_password(), oldpw)
def test_show(self):
r = self.client.get(path=url_for('user.show', uid=get_user().uid), follow_redirects=True)
dump('user_show', r)
self.assertEqual(r.status_code, 200)
def test_delete(self):
user = get_user()
r = self.client.get(path=url_for('user.delete', uid=user.uid), follow_redirects=True)
dump('user_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(get_user())
def test_csvimport(self):
db.session.add(Role(name='base'))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
db.session.commit()
data = f'''\
newuser1,newuser1@example.com,
newuser2,newuser2@example.com,{role1.id}
newuser3,newuser3@example.com,{role1.id};{role2.id}
newuser4,newuser4@example.com,9999
newuser5,newuser5@example.com,notanumber
newuser6,newuser6@example.com,{role1.id};{role2.id};
newuser7,invalidmail,
newuser8,,
,newuser9@example.com,
,,
,,,
newuser10,newuser10@example.com,
newuser11,newuser11@example.com, {role1.id};{role2.id}
newuser12,newuser12@example.com,{role1.id};{role1.id}
<invalid tag-like thingy>'''
r = self.client.post(path=url_for('user.csvimport'), data={'csv': data}, follow_redirects=True)
dump('user_csvimport', r)
self.assertEqual(r.status_code, 200)
user = User.query.get('uid=newuser1,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser1')
self.assertEqual(user.displayname, 'newuser1')
self.assertEqual(user.mail, 'newuser1@example.com')
self.assertEqual(roles, ['base'])
user = User.query.get('uid=newuser2,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser2')
self.assertEqual(user.displayname, 'newuser2')
self.assertEqual(user.mail, 'newuser2@example.com')
self.assertEqual(roles, ['base', 'role1'])
user = User.query.get('uid=newuser3,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser3')
self.assertEqual(user.displayname, 'newuser3')
self.assertEqual(user.mail, 'newuser3@example.com')
self.assertEqual(roles, ['base', 'role1', 'role2'])
user = User.query.get('uid=newuser4,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser4')
self.assertEqual(user.displayname, 'newuser4')
self.assertEqual(user.mail, 'newuser4@example.com')
self.assertEqual(roles, ['base'])
user = User.query.get('uid=newuser5,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser5')
self.assertEqual(user.displayname, 'newuser5')
self.assertEqual(user.mail, 'newuser5@example.com')
self.assertEqual(roles, ['base'])
user = User.query.get('uid=newuser6,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser6')
self.assertEqual(user.displayname, 'newuser6')
self.assertEqual(user.mail, 'newuser6@example.com')
self.assertEqual(roles, ['base', 'role1', 'role2'])
self.assertIsNone(User.query.get('uid=newuser7,ou=users,dc=example,dc=com'))
self.assertIsNone(User.query.get('uid=newuser8,ou=users,dc=example,dc=com'))
self.assertIsNone(User.query.get('uid=newuser9,ou=users,dc=example,dc=com'))
user = User.query.get('uid=newuser10,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser10')
self.assertEqual(user.displayname, 'newuser10')
self.assertEqual(user.mail, 'newuser10@example.com')
self.assertEqual(roles, ['base'])
user = User.query.get('uid=newuser11,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser11')
self.assertEqual(user.displayname, 'newuser11')
self.assertEqual(user.mail, 'newuser11@example.com')
# Currently the csv import is not very robust, imho newuser11 should have role1 and role2!
#self.assertEqual(roles, ['base', 'role1', 'role2'])
self.assertEqual(roles, ['base', 'role2'])
user = User.query.get('uid=newuser12,ou=users,dc=example,dc=com')
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser12')
self.assertEqual(user.displayname, 'newuser12')
self.assertEqual(user.mail, 'newuser12@example.com')
self.assertEqual(roles, ['base', 'role1'])
class TestUserViewsOL(TestUserViews):
use_openldap = True
class TestGroupViews(UffdTestCase):
def setUp(self):
super().setUp()
self.client.post(path=url_for('session.login'),
data={'loginname': 'testadmin', 'password': 'adminpassword'}, follow_redirects=True)
def test_index(self):
r = self.client.get(path=url_for('group.index'), follow_redirects=True)
dump('group_index', r)
self.assertEqual(r.status_code, 200)
def test_show(self):
r = self.client.get(path=url_for('group.show', gid=20001), follow_redirects=True)
dump('group_show', r)
self.assertEqual(r.status_code, 200)
class TestGroupViewsOL(TestGroupViews):
use_openldap = True
from uffd.utils import nopad_b32decode, nopad_b32encode, nopad_urlsafe_b64decode, nopad_urlsafe_b64encode
from tests.utils import UffdTestCase
class TestUtils(UffdTestCase):
def test_nopad_b32(self):
for n in range(0, 32):
self.assertEqual(b'X'*n, nopad_b32decode(nopad_b32encode(b'X'*n)))
def test_nopad_b64(self):
for n in range(0, 32):
self.assertEqual(b'X'*n, nopad_urlsafe_b64decode(nopad_urlsafe_b64encode(b'X'*n)))
import os
import tempfile
import shutil
import unittest
from flask import request
from flask import url_for
import flask_migrate
from uffd import create_app, db
from uffd.models import User, Group, Mail
def dump(basename, resp):
basename = basename.replace('.', '_').replace('/', '_')
......@@ -15,51 +15,153 @@ def dump(basename, resp):
return
os.makedirs(root, exist_ok=True)
path = os.path.join(root, basename+suffix)
with open(path, 'xb') as f:
with open(path, 'wb') as f:
f.write(resp.data)
def db_flush():
db.session = db.create_scoped_session()
if hasattr(request, 'ldap_connection'):
del request.ldap_session
db.session.rollback()
db.session.expire_all()
class UffdTestCase(unittest.TestCase):
use_openldap = False
class AppTestCase(unittest.TestCase):
DISABLE_SQLITE_MEMORY_DB = False
def setUp(self):
self.dir = tempfile.mkdtemp()
# It would be far better to create a minimal app here, but since the
# session module depends on almost everything else, that is not really feasable
config = {
'TESTING': True,
'DEBUG': True,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///%s/db.sqlite'%self.dir,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
'SECRET_KEY': 'DEBUGKEY',
'LDAP_SERVICE_MOCK': True,
'MAIL_SKIP_SEND': True,
'SELF_SIGNUP': True,
}
if self.use_openldap:
if not os.environ.get('UNITTEST_OPENLDAP'):
self.skipTest('OPENLDAP_TESTING not set')
config['LDAP_SERVICE_MOCK'] = False
config['LDAP_SERVICE_URL'] = 'ldap://localhost'
config['LDAP_SERVICE_BIND_DN'] = 'cn=uffd,ou=system,dc=example,dc=com'
config['LDAP_SERVICE_BIND_PASSWORD'] = 'uffd-ldap-password'
os.system("ldapdelete -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_cleanup.ldif > /dev/null 2>&1")
os.system("ldapadd -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_add.ldif")
os.system("ldapmodify -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_modify.ldif")
#os.system("/usr/sbin/slapcat -n 1 -l /dev/stdout")
if self.DISABLE_SQLITE_MEMORY_DB:
try:
os.remove('/tmp/uffd-migration-test-db.sqlite3')
except FileNotFoundError:
pass
config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/uffd-migration-test-db.sqlite3'
if os.environ.get('TEST_WITH_MYSQL'):
import MySQLdb
conn = MySQLdb.connect(user='root', unix_socket='/var/run/mysqld/mysqld.sock')
cur = conn.cursor()
try:
cur.execute('DROP DATABASE uffd_tests')
except:
pass
cur.execute('CREATE DATABASE uffd_tests CHARACTER SET utf8mb4 COLLATE utf8mb4_nopad_bin')
conn.close()
config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb:///uffd_tests?unix_socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4'
self.app = create_app(config)
self.setUpApp()
def setUpApp(self):
pass
def tearDown(self):
if self.DISABLE_SQLITE_MEMORY_DB:
try:
os.remove('/tmp/uffd-migration-test-db.sqlite3')
except FileNotFoundError:
pass
class MigrationTestCase(AppTestCase):
DISABLE_SQLITE_MEMORY_DB = True
REVISION = None
def setUp(self):
super().setUp()
self.request_context = self.app.test_request_context()
self.request_context.__enter__()
if self.REVISION:
flask_migrate.upgrade(revision=self.REVISION + '-1')
def upgrade(self, revision='+1'):
db.session.commit()
flask_migrate.upgrade(revision=revision)
def downgrade(self, revision='-1'):
db.session.commit()
flask_migrate.downgrade(revision=revision)
def tearDown(self):
db.session.rollback()
self.request_context.__exit__(None, None, None)
super().tearDown()
class ModelTestCase(AppTestCase):
def setUp(self):
super().setUp()
self.request_context = self.app.test_request_context()
self.request_context.__enter__()
db.create_all()
db.session.commit()
def tearDown(self):
db.session.rollback()
self.request_context.__exit__(None, None, None)
super().tearDown()
class UffdTestCase(AppTestCase):
def setUp(self):
super().setUp()
self.client = self.app.test_client()
self.client.__enter__()
# Just do some request so that we can use url_for
self.client.get(path='/')
db.create_all()
# This reflects the old LDAP example data
users_group = Group(name='users', unix_gid=20001, description='Base group for all users')
db.session.add(users_group)
access_group = Group(name='uffd_access', unix_gid=20002, description='Access to Single-Sign-On and Selfservice')
db.session.add(access_group)
admin_group = Group(name='uffd_admin', unix_gid=20003, description='Admin access to uffd')
db.session.add(admin_group)
testuser = User(loginname='testuser', unix_uid=10000, password='userpassword', primary_email_address='test@example.com', displayname='Test User', groups=[users_group, access_group])
db.session.add(testuser)
testadmin = User(loginname='testadmin', unix_uid=10001, password='adminpassword', primary_email_address='admin@example.com', displayname='Test Admin', groups=[users_group, access_group, admin_group])
db.session.add(testadmin)
testmail = Mail(uid='test', receivers=['test1@example.com', 'test2@example.com'], destinations=['testuser@mail.example.com'])
db.session.add(testmail)
self.setUpDB()
db.session.commit()
def setUpApp(self):
def setUpDB(self):
pass
def tearDown(self):
self.client.__exit__(None, None, None)
shutil.rmtree(self.dir)
super().tearDown()
def get_user(self):
return User.query.filter_by(loginname='testuser').one_or_none()
def get_admin(self):
return User.query.filter_by(loginname='testadmin').one_or_none()
def get_admin_group(self):
return Group.query.filter_by(name='uffd_admin').one_or_none()
def get_access_group(self):
return Group.query.filter_by(name='uffd_access').one_or_none()
def get_users_group(self):
return Group.query.filter_by(name='users').one_or_none()
def get_mail(self):
return Mail.query.filter_by(uid='test').one_or_none()
def login_as(self, user, ref=None):
# It is currently not possible to login while already logged in as another
# user, so make sure that we are not logged in first
self.client.get(path=url_for('session.logout'), follow_redirects=True)
loginname = None
password = None
if user == 'user':
loginname = 'testuser'
password = 'userpassword'
elif user == 'admin':
loginname = 'testadmin'
password = 'adminpassword'
return self.client.post(path=url_for('session.login', ref=ref),
data={'loginname': loginname, 'password': password}, follow_redirects=True)
import base64
from flask import url_for
from uffd.password_hash import PlaintextPasswordHash
from uffd.remailer import remailer
from uffd.database import db
from uffd.models import APIClient, Service, User, RemailerMode
from uffd.views.api import apikey_required
from tests.utils import UffdTestCase, db_flush
def basic_auth(username, password):
return ('Authorization', 'Basic ' + base64.b64encode(f'{username}:{password}'.encode()).decode())
class TestAPIAuth(UffdTestCase):
def setUpApp(self):
@self.app.route('/test/endpoint1')
@apikey_required()
def testendpoint1():
return 'OK', 200
@self.app.route('/test/endpoint2')
@apikey_required('users')
def testendpoint2():
return 'OK', 200
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test1'), auth_username='test1', auth_password='testsecret1', perm_users=True))
db.session.add(APIClient(service=Service(name='test2'), auth_username='test2', auth_password='testsecret2'))
def test_basic(self):
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test1', 'testsecret1')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.get(path=url_for('testendpoint2'), headers=[basic_auth('test1', 'testsecret1')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test2', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
def test_basic_invalid_credentials(self):
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test-none', 'testsecret-none')], follow_redirects=True)
self.assertEqual(r.status_code, 401)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test1', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 401)
def test_basic_missing_scope(self):
r = self.client.get(path=url_for('testendpoint2'), headers=[basic_auth('test2', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 403)
def test_no_auth(self):
r = self.client.get(path=url_for('testendpoint1'), follow_redirects=True)
self.assertEqual(r.status_code, 401)
def test_auth_password_rehash(self):
db.session.add(APIClient(service=Service(name='test3'), auth_username='test3', auth_password=PlaintextPasswordHash.from_password('testsecret3')))
db.session.commit()
self.assertIsInstance(APIClient.query.filter_by(auth_username='test3').one().auth_password, PlaintextPasswordHash)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test3', 'testsecret3')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
api_client = APIClient.query.filter_by(auth_username='test3').one()
self.assertIsInstance(api_client.auth_password, APIClient.auth_password.method_cls)
self.assertTrue(api_client.auth_password.verify('testsecret3'))
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test3', 'testsecret3')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
class TestAPIGetmails(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_mail_aliases=True))
def test_lookup(self):
r = self.client.get(path=url_for('api.getmails', receive_address='test1@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
r = self.client.get(path=url_for('api.getmails', receive_address='test2@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
def test_lookup_notfound(self):
r = self.client.get(path=url_for('api.getmails', receive_address='test3@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_lookup_case_folding(self):
r = self.client.get(path=url_for('api.getmails', receive_address='Test1@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
class TestAPICheckPassword(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_checkpassword=True))
def test(self):
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json['loginname'], 'testuser')
def test_password_rehash(self):
self.get_user().password = PlaintextPasswordHash.from_password('userpassword')
db.session.commit()
self.assertIsInstance(self.get_user().password, PlaintextPasswordHash)
db_flush()
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json['loginname'], 'testuser')
self.assertIsInstance(self.get_user().password, User.password.method_cls)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_wrong_password(self):
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'wrongpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, None)
def test_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, None)
class TestAPIGetusers(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_users=True))
def fix_result(self, result):
result.sort(key=lambda user: user['id'])
for user in result:
user['groups'].sort()
return result
def test_all(self):
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_id(self):
r = self.client.get(path=url_for('api.getusers', id=10000), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_id_empty(self):
r = self.client.get(path=url_for('api.getusers', id=0), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_with_remailer(self):
service = Service.query.filter_by(name='test').one()
service.remailer_mode = RemailerMode.ENABLED_V1
db.session.commit()
user = self.get_user()
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
r = self.client.get(path=url_for('api.getusers', id=10000), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
service = Service.query.filter_by(name='test').one()
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': remailer.build_v1_address(service.id, user.id), 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_loginname(self):
r = self.client.get(path=url_for('api.getusers', loginname='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_loginname_empty(self):
r = self.client.get(path=url_for('api.getusers', loginname='notauser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_email(self):
r = self.client.get(path=url_for('api.getusers', email='admin@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_email_with_remailer(self):
service = Service.query.filter_by(name='test').one()
service.remailer_mode = RemailerMode.ENABLED_V1
db.session.commit()
user = self.get_admin()
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
r = self.client.get(path=url_for('api.getusers', email=remailer.build_v1_address(service.id, user.id)), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
service = Service.query.filter_by(name='test').one()
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': remailer.build_v1_address(service.id, user.id), 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_email_empty(self):
r = self.client.get(path=url_for('api.getusers', email='foo@bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_group(self):
r = self.client.get(path=url_for('api.getusers', group='uffd_admin'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_group_empty(self):
r = self.client.get(path=url_for('api.getusers', group='notagroup'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
class TestAPIGetgroups(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_users=True))
def fix_result(self, result):
result.sort(key=lambda group: group['id'])
for group in result:
group['members'].sort()
return result
def test_all(self):
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_all_deactivated_members(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_id(self):
r = self.client.get(path=url_for('api.getgroups', id=20002), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
def test_id_empty(self):
r = self.client.get(path=url_for('api.getgroups', id=0), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_name(self):
r = self.client.get(path=url_for('api.getgroups', name='uffd_admin'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_name_empty(self):
r = self.client.get(path=url_for('api.getgroups', name='notagroup'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_member(self):
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
def test_member_empty(self):
r = self.client.get(path=url_for('api.getgroups', member='notauser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_member_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [])
class TestAPIRemailerResolve(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_remailer=True))
db.session.add(Service(name='service1'))
db.session.add(Service(name='service2', remailer_mode=RemailerMode.ENABLED_V1))
def test(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
service = Service.query.filter_by(name='service2').one()
r = self.client.get(path=url_for('api.resolve_remailer', orig_address=remailer.build_v1_address(service.id, self.get_user().id)), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, {'address': self.get_user().primary_email.address})
r = self.client.get(path=url_for('api.resolve_remailer', orig_address='foo@bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, {'address': None})
def test_invalid(self):
r = self.client.get(path=url_for('api.resolve_remailer', orig_address=['foo', 'bar']), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
r = self.client.get(path=url_for('api.resolve_remailer'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
r = self.client.get(path=url_for('api.resolve_remailer', foo='bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
class TestAPIMetricsPrometheus(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_metrics=True))
def test(self):
r = self.client.get(path=url_for('api.prometheus_metrics'), headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertTrue("uffd_version_info" in r.data.decode())
import unittest
import datetime
import time
from flask import url_for, session
from flask import url_for, current_app
# These imports are required, because otherwise we get circular imports?!
from uffd import user
from uffd.ldap import ldap
from uffd.database import db
from uffd.models import Invite, InviteGrant, InviteSignup, Role, RoleGroup
from uffd import create_app, db
from uffd.invite.models import Invite, InviteGrant, InviteSignup
from uffd.user.models import User, Group
from uffd.role.models import Role
from uffd.session.views import get_current_user, is_valid_session, login_get_user
from tests.utils import dump, UffdTestCase, db_flush
from utils import dump, UffdTestCase, db_flush
class TestInviteModel(UffdTestCase):
def test_expire(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertFalse(invite.expired)
self.assertTrue(invite.active)
invite.valid_until = datetime.datetime.now() - datetime.timedelta(seconds=60)
self.assertTrue(invite.expired)
self.assertFalse(invite.active)
def test_void(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False)
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite.used = True
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=True)
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite.used = True
self.assertTrue(invite.voided)
self.assertFalse(invite.active)
def test_disable(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertTrue(invite.active)
invite.disable()
self.assertFalse(invite.active)
def test_reset_disabled(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
invite.disable()
self.assertFalse(invite.active)
invite.reset()
self.assertTrue(invite.active)
def test_reset_expired(self):
invite = Invite(valid_until=datetime.datetime.now() - datetime.timedelta(seconds=60))
self.assertFalse(invite.active)
invite.reset()
self.assertFalse(invite.active)
def test_reset_disabled(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False)
invite.used = True
invite.disable()
self.assertFalse(invite.active)
invite.reset()
self.assertTrue(invite.active)
class TestInviteGrantModel(UffdTestCase):
def test_success(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
group0 = Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com')
role0 = Role(name='baserole', groups=[group0])
db.session.add(role0)
user.roles.add(role0)
user.update_groups()
group1 = Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')
role1 = Role(name='testrole1', groups=[group1])
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2])
self.assertIn(role0, user.roles)
self.assertNotIn(role1, user.roles)
self.assertNotIn(role2, user.roles)
self.assertIn(group0, user.groups)
self.assertNotIn(group1, user.groups)
self.assertFalse(invite.used)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertTrue(success)
self.assertIn(role0, user.roles)
self.assertIn(role1, user.roles)
self.assertIn(role2, user.roles)
self.assertIn(group0, user.groups)
self.assertIn(group1, user.groups)
self.assertTrue(invite.used)
db.session.commit()
ldap.session.commit()
db_flush()
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
self.assertIn('baserole', [role.name for role in user.roles])
self.assertIn('testrole1', [role.name for role in user.roles])
self.assertIn('testrole2', [role.name for role in user.roles])
self.assertIn('cn=uffd_access,ou=groups,dc=example,dc=com', [group.dn for group in user.groups])
self.assertIn('cn=uffd_admin,ou=groups,dc=example,dc=com', [group.dn for group in user.groups])
def test_inactive(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
group = Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')
role = Role(name='testrole1', groups=[group])
db.session.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role], single_use=True, used=True)
self.assertFalse(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
self.assertNotIn(role, user.roles)
self.assertNotIn(group, user.groups)
def test_no_roles(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertTrue(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
def test_no_new_roles(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
role = Role(name='testrole1')
db.session.add(role)
user.roles.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role])
self.assertTrue(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
class TestInviteSignupModel(UffdTestCase):
def create_base_roles(self):
self.app.config['ROLES_BASEROLES'] = ['base']
baserole = Role(name='base')
baserole.groups.add(Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com'))
baserole.groups.add(Group.query.get('cn=users,ou=groups,dc=example,dc=com'))
db.session.add(baserole)
db.session.commit()
def test_success(self):
self.create_base_roles()
base_role = Role.query.filter_by(name='base').one()
base_group1 = Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com')
base_group2 = Group.query.get('cn=users,ou=groups,dc=example,dc=com')
group = Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')
role1 = Role(name='testrole1', groups=[group])
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(invite.used)
valid, msg = signup.validate()
self.assertTrue(valid)
self.assertFalse(invite.used)
user, msg = signup.finish('notsecret')
self.assertIsInstance(user, User)
self.assertTrue(invite.used)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'test@example.com')
self.assertEqual(signup.user.dn, user.dn)
self.assertIn(base_role, user.roles)
self.assertIn(role1, user.roles)
self.assertIn(role2, user.roles)
self.assertIn(base_group1, user.groups)
self.assertIn(base_group2, user.groups)
self.assertIn(group, user.groups)
db.session.commit()
ldap.session.commit()
db_flush()
self.assertEqual(len(User.query.filter_by(loginname='newuser').all()), 1)
def test_success_no_roles(self):
self.create_base_roles()
base_role = Role.query.filter_by(name='base').one()
base_group1 = Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com')
base_group2 = Group.query.get('cn=users,ou=groups,dc=example,dc=com')
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(invite.used)
valid, msg = signup.validate()
self.assertTrue(valid)
self.assertFalse(invite.used)
user, msg = signup.finish('notsecret')
self.assertIsInstance(user, User)
self.assertTrue(invite.used)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'test@example.com')
self.assertEqual(signup.user.dn, user.dn)
self.assertIn(base_role, user.roles)
self.assertEqual(len(user.roles), 1)
self.assertIn(base_group1, user.groups)
self.assertIn(base_group2, user.groups)
self.assertEqual(len(user.groups), 2)
db.session.commit()
ldap.session.commit()
db_flush()
self.assertEqual(len(User.query.filter_by(loginname='newuser').all()), 1)
def test_inactive(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, single_use=True, used=True)
self.assertFalse(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
user, msg = signup.finish('notsecret')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
def test_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
def test_invalid2(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
user, msg = signup.finish('wrongpassword')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
def test_no_signup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
user, msg = signup.finish('notsecret')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
class TestInviteViews(UffdTestCase):
class TestInviteAdminViews(UffdTestCase):
def setUpApp(self):
self.app.config['SELF_SIGNUP'] = False
self.app.last_mail = None
def login_admin(self):
self.client.post(path=url_for('session.login'),
data={'loginname': 'testadmin', 'password': 'adminpassword'}, follow_redirects=True)
def login_user(self):
self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
def test_index(self):
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
valid_until_expired = datetime.datetime.now() - datetime.timedelta(seconds=60)
user1 = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
user2 = User.query.get('uid=testadmin,ou=users,dc=example,dc=com')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
valid_until_expired = datetime.datetime.utcnow() - datetime.timedelta(seconds=60)
user1 = self.get_user()
user2 = self.get_admin()
role1 = Role(name='testrole1')
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
# All possible states
db.session.add(Invite(valid_until=valid_until, single_use=False))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=False))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=True, signups=[InviteSignup(user=user1)]))
db.session.add(Invite(valid_until=valid_until_expired))
db.session.add(Invite(valid_until=valid_until, disabled=True))
db.session.add(Invite(valid_until=valid_until, single_use=False, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=False, creator=self.get_admin()))
invite = Invite(valid_until=valid_until, single_use=True, used=True, creator=self.get_admin())
invite.signups = [InviteSignup(user=user1)]
db.session.add(invite)
db.session.add(Invite(valid_until=valid_until_expired, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, disabled=True, creator=self.get_admin()))
# Different permissions
db.session.add(Invite(valid_until=valid_until, allow_signup=True))
db.session.add(Invite(valid_until=valid_until, allow_signup=False))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, roles=[role1], grants=[InviteGrant(user=user2)]))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, roles=[role1, role2]))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, roles=[role1], grants=[InviteGrant(user=user2)], creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, roles=[role1, role2], creator=self.get_admin()))
db.session.commit()
self.login_admin()
self.login_as('admin')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
dump('invite_index', r)
self.assertEqual(r.status_code, 200)
def test_index_empty(self):
self.login_admin()
self.login_as('admin')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
dump('invite_index_empty', r)
self.assertEqual(r.status_code, 200)
......@@ -300,13 +52,48 @@ class TestInviteViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_index_noaccess(self):
self.login_user()
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
dump('invite_index_noaccess', r)
self.assertEqual(r.status_code, 403)
def test_index_signupperm(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite1 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_admin())
db.session.add(invite1)
invite2 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_user())
db.session.add(invite2)
invite3 = Invite(valid_until=valid_until, allow_signup=True)
db.session.add(invite3)
db.session.commit()
token1 = invite1.short_token
token2 = invite2.short_token
token3 = invite3.short_token
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertNotIn(token1.encode(), r.data)
self.assertIn(token2.encode(), r.data)
self.assertNotIn(token3.encode(), r.data)
def test_index_rolemod(self):
role1 = Role(name='testrole1')
db.session.add(role1)
role2 = Role(name='testrole2', moderator_group=self.get_access_group())
db.session.add(role2)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
db.session.add(Invite(valid_until=valid_until, roles=[role1]))
db.session.add(Invite(valid_until=valid_until, roles=[role2]))
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertNotIn('testrole1'.encode(), r.data)
self.assertIn('testrole2'.encode(), r.data)
def test_new(self):
self.login_admin()
self.login_as('admin')
role = Role(name='testrole1')
db.session.add(role)
db.session.commit()
......@@ -314,10 +101,10 @@ class TestInviteViews(UffdTestCase):
r = self.client.get(path=url_for('invite.new'), follow_redirects=True)
dump('invite_new', r)
self.assertEqual(r.status_code, 200)
valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat()
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
self.assertListEqual(Invite.query.all(), [])
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until,
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
dump('invite_new_submit', r)
invite = Invite.query.one()
......@@ -327,88 +114,183 @@ class TestInviteViews(UffdTestCase):
self.assertTrue(invite.allow_signup)
self.assertListEqual(invite.roles, [role])
def test_new_noperm(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
role = Role(name='testrole1')
db.session.add(role)
db.session.commit()
role_id = role.id
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
dump('invite_new_noperm', r)
self.assertIsNone(Invite.query.first())
def test_new_empty(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '0'}, follow_redirects=True)
dump('invite_new_empty', r)
self.assertIsNone(Invite.query.first())
def test_disable(self):
self.login_admin()
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until)
self.login_as('admin')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
token = invite.token
self.assertTrue(Invite.query.get(token).active)
r = self.client.post(path=url_for('invite.disable', token=token), follow_redirects=True)
id = invite.id
self.assertTrue(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.disable', invite_id=id), follow_redirects=True)
dump('invite_disable', r)
self.assertTrue(Invite.query.get(token).disabled)
self.assertTrue(Invite.query.get(id).disabled)
def test_disable_own(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, creator=self.get_user())
db.session.add(invite)
db.session.commit()
id = invite.id
self.assertTrue(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.disable', invite_id=id), follow_redirects=True)
dump('invite_disable', r)
self.assertTrue(Invite.query.get(id).disabled)
def test_disable_rolemod(self):
self.login_as('user')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group=self.get_access_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
self.assertTrue(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.disable', invite_id=id), follow_redirects=True)
self.assertTrue(Invite.query.get(id).disabled)
def test_disable_noperm(self):
self.login_as('user')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
db.session.add(Role(name='testrole1', moderator_group=self.get_access_group()))
role = Role(name='testrole2', moderator_group=self.get_admin_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
r = self.client.post(path=url_for('invite.disable', invite_id=id), follow_redirects=True)
self.assertFalse(Invite.query.get(id).disabled)
self.assertTrue(Invite.query.get(id).active)
def test_reset_disabled(self):
self.login_admin()
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True)
self.login_as('admin')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
token = invite.token
self.assertFalse(Invite.query.get(token).active)
r = self.client.post(path=url_for('invite.reset', token=token), follow_redirects=True)
id = invite.id
self.assertFalse(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.reset', invite_id=id), follow_redirects=True)
dump('invite_reset_disabled', r)
self.assertTrue(Invite.query.get(token).active)
self.assertTrue(Invite.query.get(id).active)
def test_reset_voided(self):
self.login_admin()
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, single_use=True, used=True)
self.login_as('admin')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, single_use=True, used=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
token = invite.token
self.assertFalse(Invite.query.get(token).active)
r = self.client.post(path=url_for('invite.reset', token=token), follow_redirects=True)
id = invite.id
self.assertFalse(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.reset', invite_id=id), follow_redirects=True)
dump('invite_reset_voided', r)
self.assertTrue(Invite.query.get(token).active)
self.assertTrue(Invite.query.get(id).active)
def test_reset_own(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_user())
db.session.add(invite)
db.session.commit()
id = invite.id
self.assertFalse(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.reset', invite_id=id), follow_redirects=True)
dump('invite_reset_own', r)
self.assertTrue(Invite.query.get(id).active)
def test_reset_foreign(self):
self.login_as('user')
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group=self.get_access_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
self.assertFalse(Invite.query.get(id).active)
r = self.client.post(path=url_for('invite.reset', invite_id=id), follow_redirects=True)
self.assertFalse(Invite.query.get(id).active)
class TestInviteUseViews(UffdTestCase):
def setUpApp(self):
self.app.config['SELF_SIGNUP'] = False
self.app.last_mail = None
def test_use(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use', r)
self.assertEqual(r.status_code, 200)
def test_use_loggedin(self):
self.login_user()
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
self.login_as('user')
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use_loggedin', r)
self.assertEqual(r.status_code, 200)
def test_use_inactive(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use_inactive', r)
self.assertEqual(r.status_code, 200)
# TODO: test cases for {logged in, not logged in} x (signup-only, grant-only, both, none?}
def test_grant(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
group0 = Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com')
role0 = Role(name='baserole', groups=[group0])
user = self.get_user()
group0 = self.get_access_group()
role0 = Role(name='baserole', groups={group0: RoleGroup(group=group0)})
db.session.add(role0)
user.roles.add(role0)
user.roles.append(role0)
user.update_groups()
group1 = Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')
role1 = Role(name='testrole1', groups=[group1])
group1 = self.get_admin_group()
role1 = Role(name='testrole1', groups={group1: RoleGroup(group=group1)})
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2])
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
db.session.add(invite)
db.session.commit()
ldap.session.commit()
invite_id = invite.id
token = invite.token
self.assertIn(role0, user.roles)
self.assertNotIn(role1, user.roles)
......@@ -416,73 +298,76 @@ class TestInviteViews(UffdTestCase):
self.assertIn(group0, user.groups)
self.assertNotIn(group1, user.groups)
self.assertFalse(invite.used)
self.login_user()
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant', r)
self.assertEqual(r.status_code, 200)
db_flush()
invite = Invite.query.get(token)
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
invite = Invite.query.filter_by(token=token).first()
user = self.get_user()
self.assertTrue(invite.used)
self.assertIn('baserole', [role.name for role in user.roles])
self.assertIn('testrole1', [role.name for role in user.roles])
self.assertIn('testrole2', [role.name for role in user.roles])
self.assertIn('cn=uffd_access,ou=groups,dc=example,dc=com', [group.dn for group in user.groups])
self.assertIn('cn=uffd_admin,ou=groups,dc=example,dc=com', [group.dn for group in user.groups])
self.assertIn(self.get_access_group(), user.groups)
self.assertIn(self.get_admin_group(), user.groups)
def test_grant_invalid_invite(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_user()
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_invalid_invite', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.get(token).used)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
def test_grant_no_roles(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60))
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_user()
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_no_roles', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.get(token).used)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
def test_grant_no_new_roles(self):
user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
user = self.get_user()
role = Role(name='testrole')
db.session.add(role)
user.roles.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role])
user.roles.append(role)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role])
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_user()
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_no_new_roles', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.get(token).used)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
def test_signup(self):
self.app.config['ROLES_BASEROLES'] = ['base']
baserole = Role(name='base')
baserole.groups.add(Group.query.get('cn=uffd_access,ou=groups,dc=example,dc=com'))
baserole.groups.add(Group.query.get('cn=users,ou=groups,dc=example,dc=com'))
baserole = Role(name='base', is_default=True)
baserole.groups[self.get_access_group()] = RoleGroup()
baserole.groups[self.get_users_group()] = RoleGroup()
db.session.add(baserole)
group = Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')
role1 = Role(name='testrole1', groups=[group])
group = self.get_admin_group()
role1 = Role(name='testrole1', groups={group: RoleGroup(group=group)})
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.get(path=url_for('invite.signup_start', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_signup_start', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('invite.signup_submit', token=token),
......@@ -495,40 +380,40 @@ class TestInviteViews(UffdTestCase):
self.assertEqual(signup.displayname, 'New User')
self.assertEqual(signup.mail, 'test@example.com')
self.assertIn(signup.token, str(self.app.last_mail.get_content()))
self.assertTrue(signup.check_password('notsecret'))
self.assertTrue(signup.password.verify('notsecret'))
self.assertTrue(signup.validate()[0])
def test_signup_invalid_invite(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.get(path=url_for('invite.signup_start', token=invite.token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
dump('invite_signup_invalid_invite', r)
self.assertEqual(r.status_code, 200)
def test_signup_nosignup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.get(path=url_for('invite.signup_start', token=invite.token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
dump('invite_signup_nosignup', r)
self.assertEqual(r.status_code, 200)
def test_signup_wrongpassword(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notthesame'}, follow_redirects=True)
dump('invite_signup_wrongpassword', r)
self.assertEqual(r.status_code, 200)
def test_signup_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': '', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_invalid', r)
......@@ -536,27 +421,28 @@ class TestInviteViews(UffdTestCase):
def test_signup_mailerror(self):
self.app.config['MAIL_SKIP_SEND'] = 'fail'
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_mailerror', r)
self.assertEqual(r.status_code, 200)
def test_signup_hostlimit(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(20):
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'test%d@example.com'%i,
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_hostlimit', r)
......@@ -565,17 +451,18 @@ class TestInviteViews(UffdTestCase):
self.assertIsNone(self.app.last_mail)
def test_signup_mailimit(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(3):
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_maillimit', r)
......@@ -584,75 +471,79 @@ class TestInviteViews(UffdTestCase):
self.assertIsNone(self.app.last_mail)
def test_signup_check(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'newuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'ok')
def test_signup_check_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': ''})
print(r.data)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'invalid')
def test_signup_check_exists(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'exists')
def test_signup_check_nosignup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'error')
def test_signup_check_error(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'error')
def test_signup_check_ratelimited(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(20):
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'ratelimited')
class TestInviteViewsOL(TestInviteViews):
use_openldap = True
import datetime
import time
import unittest
from flask import url_for, session
from flask import url_for
# These imports are required, because otherwise we get circular imports?!
from uffd.ldap import ldap
from uffd import user
from uffd.database import db
from uffd.models import Mail
from uffd.mail.models import Mail
from uffd import create_app, db
from utils import dump, UffdTestCase
def get_mail():
return Mail.query.get('uid=test,ou=postfix,dc=example,dc=com')
from tests.utils import dump, UffdTestCase
class TestMailViews(UffdTestCase):
def setUp(self):
super().setUp()
self.client.post(path=url_for('session.login'),
data={'loginname': 'testadmin', 'password': 'adminpassword'}, follow_redirects=True)
self.login_as('admin')
def test_index(self):
r = self.client.get(path=url_for('mail.index'), follow_redirects=True)
......@@ -28,15 +18,15 @@ class TestMailViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_index_empty(self):
ldap.session.delete(get_mail())
ldap.session.commit()
self.assertIsNone(get_mail())
db.session.delete(self.get_mail())
db.session.commit()
self.assertIsNone(self.get_mail())
r = self.client.get(path=url_for('mail.index'), follow_redirects=True)
dump('mail_index_empty', r)
self.assertEqual(r.status_code, 200)
def test_show(self):
r = self.client.get(path=url_for('mail.show', uid=get_mail().uid), follow_redirects=True)
r = self.client.get(path=url_for('mail.show', mai_id=self.get_mail().id), follow_redirects=True)
dump('mail_show', r)
self.assertEqual(r.status_code, 200)
......@@ -46,17 +36,17 @@ class TestMailViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_update(self):
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['test1@example.com', 'test2@example.com'])
self.assertEqual(sorted(m.destinations), ['testuser@mail.example.com'])
r = self.client.post(path=url_for('mail.update', uid=m.uid),
r = self.client.post(path=url_for('mail.update', mail_id=m.id),
data={'mail-uid': 'test1', 'mail-receivers': 'foo@bar.com\ntest@bar.com',
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_update', r)
self.assertEqual(r.status_code, 200)
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['foo@bar.com', 'test@bar.com'])
......@@ -68,30 +58,27 @@ class TestMailViews(UffdTestCase):
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_create', r)
self.assertEqual(r.status_code, 200)
m = Mail.query.get('uid=test1,ou=postfix,dc=example,dc=com')
m = Mail.query.filter_by(uid='test1').one()
self.assertEqual(m.uid, 'test1')
self.assertEqual(sorted(m.receivers), ['foo@bar.com', 'test@bar.com'])
self.assertEqual(sorted(m.destinations), ['testadmin@mail.example.com', 'testuser@mail.example.com'])
@unittest.skip('We do not catch LDAP errors at the moment!') # TODO: Not sure if necessary
@unittest.skip('We do not catch DB errors at the moment!') # TODO
def test_create_error(self):
r = self.client.post(path=url_for('mail.update'),
data={'mail-uid': 'test', 'mail-receivers': 'foo@bar.com\ntest@bar.com',
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_create_error', r)
self.assertEqual(r.status_code, 200)
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['test1@example.com', 'test2@example.com'])
self.assertEqual(sorted(m.destinations), ['testuser@mail.example.com'])
def test_delete(self):
self.assertIsNotNone(get_mail())
r = self.client.get(path=url_for('mail.delete', uid=get_mail().uid), follow_redirects=True)
self.assertIsNotNone(self.get_mail())
r = self.client.get(path=url_for('mail.delete', mail_id=self.get_mail().id), follow_redirects=True)
dump('mail_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(get_mail())
class TestMailViewsOL(TestMailViews):
use_openldap = True
self.assertIsNone(self.get_mail())
import unittest
from urllib.parse import urlparse, parse_qs
import jwt
from flask import url_for, session
from uffd.database import db
from uffd.password_hash import PlaintextPasswordHash
from uffd.remailer import remailer
from uffd.models import DeviceLoginConfirmation, Service, OAuth2Client, OAuth2DeviceLoginInitiation, RemailerMode, OAuth2Key, Session
from tests.utils import dump, UffdTestCase
from tests.models.test_oauth2 import TEST_JWK
class TestViews(UffdTestCase):
def setUpDB(self):
db.session.add(OAuth2Client(service=Service(name='test', limit_access=False), client_id='test', client_secret='testsecret', redirect_uris=['http://localhost:5009/callback', 'http://localhost:5009/callback2']))
db.session.add(OAuth2Client(service=Service(name='test1', access_group=self.get_admin_group()), client_id='test1', client_secret='testsecret1', redirect_uris=['http://localhost:5008/callback']))
def assert_authorization(self, r, mail=None):
while True:
if r.status_code != 302 or r.location.startswith('http://localhost:5009/callback'):
break
r = self.client.get(r.location, follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.startswith('http://localhost:5009/callback'))
args = parse_qs(urlparse(r.location).query)
self.assertEqual(args['state'], ['teststate'])
code = args['code'][0]
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['token_type'], 'Bearer')
self.assertEqual(r.json['scope'], 'profile')
token = r.json['access_token']
r = self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%token)], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
user = self.get_user()
self.assertEqual(r.json['id'], user.unix_uid)
self.assertEqual(r.json['name'], user.displayname)
self.assertEqual(r.json['nickname'], user.loginname)
self.assertEqual(r.json['email'], mail or user.primary_email.address)
self.assertTrue(r.json.get('groups'))
def test_authorization(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assert_authorization(r)
def test_authorization_with_remailer(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
service = Service.query.filter_by(name='test').one()
service.remailer_mode = RemailerMode.ENABLED_V1
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
service = Service.query.filter_by(name='test').one()
self.assert_authorization(r, mail=remailer.build_v1_address(service.id, self.get_user().id))
def test_authorization_client_secret_rehash(self):
OAuth2Client.query.delete()
db.session.add(OAuth2Client(service=Service(name='rehash_test', limit_access=False), client_id='test', client_secret=PlaintextPasswordHash.from_password('testsecret'), redirect_uris=['http://localhost:5009/callback', 'http://localhost:5009/callback2']))
db.session.commit()
self.assertIsInstance(OAuth2Client.query.filter_by(client_id='test').one().client_secret, PlaintextPasswordHash)
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assert_authorization(r)
oauth2_client = OAuth2Client.query.filter_by(client_id='test').one()
self.assertIsInstance(oauth2_client.client_secret, OAuth2Client.client_secret.method_cls)
self.assertTrue(oauth2_client.client_secret.verify('testsecret'))
def test_authorization_without_redirect_uri(self):
client = OAuth2Client.query.filter_by(client_id='test').one()
client.redirect_uris.remove('http://localhost:5009/callback2')
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', scope='profile'), follow_redirects=False)
self.assert_authorization(r)
def test_authorization_without_scope(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback'), follow_redirects=False)
self.assert_authorization(r)
# Regression test for #115 (OAuth2 authorize endpoint rejects empty scope parameter)
def test_authorization_empty_scope(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', scope='', redirect_uri='http://localhost:5009/callback'), follow_redirects=False)
self.assert_authorization(r)
def test_authorization_access_denied(self):
client = OAuth2Client.query.filter_by(client_id='test').one()
client.service.limit_access = True
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assertEqual(r.status_code, 403)
dump('oauth2_authorization_access_denied', r)
def test_authorization_invalid_scope(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='invalid'), follow_redirects=False)
self.assertEqual(r.status_code, 400)
dump('oauth2_authorization_invalid_scope', r)
def test_authorization_missing_client_id(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assertEqual(r.status_code, 400)
dump('oauth2_authorization_missing_client_id', r)
def test_authorization_invalid_client_id(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='invalid_client_id', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assertEqual(r.status_code, 400)
dump('oauth2_authorization_invalid_client_id', r)
def test_authorization_missing_response_type(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assertEqual(r.status_code, 400)
dump('oauth2_authorization_missing_response_type', r)
def test_authorization_invalid_response_type(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='token', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
self.assertEqual(r.status_code, 400)
dump('oauth2_authorization_invalid_response_type', r)
def test_authorization_devicelogin_start(self):
ref = url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback')
r = self.client.get(path=url_for('session.devicelogin_start', ref=ref), follow_redirects=True)
# check response
initiation = OAuth2DeviceLoginInitiation.query.filter_by(id=session['devicelogin_id'], secret=session['devicelogin_secret']).one()
self.assertEqual(r.status_code, 200)
self.assertFalse(initiation.expired)
self.assertEqual(initiation.client.client_id, 'test')
self.assertIsNotNone(initiation.description)
def test_authorization_devicelogin_auth(self):
with self.client.session_transaction() as _session:
initiation = OAuth2DeviceLoginInitiation(client=OAuth2Client.query.filter_by(client_id='test').one())
db.session.add(initiation)
confirmation = DeviceLoginConfirmation(initiation=initiation, session=Session(user=self.get_user()))
db.session.add(confirmation)
db.session.commit()
_session['devicelogin_id'] = initiation.id
_session['devicelogin_secret'] = initiation.secret
code = confirmation.code
self.client.get(path='/')
ref = url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback')
r = self.client.post(path=url_for('session.devicelogin_submit', ref=ref), data={'confirmation-code': code}, follow_redirects=False)
self.assert_authorization(r)
def test_authorization_devicelogin_auth_deactivated(self):
with self.client.session_transaction() as _session:
initiation = OAuth2DeviceLoginInitiation(client=OAuth2Client.query.filter_by(client_id='test').one())
db.session.add(initiation)
confirmation = DeviceLoginConfirmation(initiation=initiation, session=Session(user=self.get_user()))
db.session.add(confirmation)
db.session.commit()
_session['devicelogin_id'] = initiation.id
_session['devicelogin_secret'] = initiation.secret
code = confirmation.code
self.client.get(path='/')
self.get_user().is_deactivated = True
db.session.commit()
ref = url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback')
r = self.client.post(path=url_for('session.devicelogin_submit', ref=ref), data={'confirmation-code': code}, follow_redirects=True)
self.assertIn(b'Device login failed', r.data)
def get_auth_code(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback', scope='profile'), follow_redirects=False)
while True:
if r.status_code != 302 or r.location.startswith('http://localhost:5009/callback'):
break
r = self.client.get(r.location, follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.startswith('http://localhost:5009/callback'))
args = parse_qs(urlparse(r.location).query)
self.assertEqual(args['state'], ['teststate'])
return args['code'][0]
def test_token_urlsecret(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['token_type'], 'Bearer')
self.assertEqual(r.json['scope'], 'profile')
# Regression test for #114 (OAuth2 token endpoint does not support Basic-Auth)
def test_token_basicauth(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback'},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['token_type'], 'Bearer')
self.assertEqual(r.json['scope'], 'profile')
def test_token_invalid_code(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': 'abcdef', 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 400)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['error'], 'invalid_grant')
def test_token_code_invalidation(self):
code = self.get_auth_code()
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'http://localhost:5009/callback'},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'http://localhost:5009/callback'},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'}, follow_redirects=True)
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json['error'], 'invalid_grant')
def test_token_invalid_client(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'invalid_client', 'client_secret': 'invalid_client_secret'}, follow_redirects=True)
self.assertEqual(r.status_code, 401)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['error'], 'invalid_client')
def test_token_unauthorized_client(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test'}, follow_redirects=True)
self.assertEqual(r.status_code, 401)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['error'], 'invalid_client')
def test_token_unsupported_grant_type(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'password', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 400)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['error'], 'unsupported_grant_type')
def test_token_deactivated_user(self):
code = self.get_auth_code()
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 400)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['error'], 'invalid_grant')
def test_userinfo_invalid_access_token(self):
token = 'invalidtoken'
r = self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%token)], follow_redirects=True)
self.assertEqual(r.status_code, 401)
def test_userinfo_deactivated_user(self):
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': self.get_auth_code(), 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
token = r.json['access_token']
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%token)], follow_redirects=True)
self.assertEqual(r.status_code, 401)
class TestOIDCConfigurationProfile(UffdTestCase):
def setUpDB(self):
db.session.add(OAuth2Key(**TEST_JWK))
def test_discover_spec(self):
ISSUER = 'https://sso.example.com'
r = self.client.get(base_url=ISSUER, path='/.well-known/openid-configuration')
# OIDC Discovery 1.0 section 4.2:
# > A successful response MUST use the 200 OK HTTP status code and return a
# > JSON object using the application/json content type that contains a set
# > of Claims as its members that are a subset of the Metadata values defined
# > in Section 3.
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertIsInstance(r.json, dict) # also validates JSON syntax
# OIDC Discovery 1.0 section 4.2:
# > Claims that return multiple values are represented as JSON arrays.
# > Claims with zero elements MUST be omitted from the response.
for key, value in r.json.items():
if isinstance(value, list):
self.assertNotEqual(len(value), 0)
# OIDC Discovery 1.0 section 3 (REQUIRED metadata values)
required_fields = {'issuer', 'authorization_endpoint', 'jwks_uri', 'response_types_supported', 'subject_types_supported', 'id_token_signing_alg_values_supported'}
if 'code' in r.json.get('response_types_supported', []):
required_fields.add('token_endpoint')
if 'authorization_code' in r.json.get('grant_types_supported', ['authorization_code', 'implicit']):
required_fields.add('token_endpoint')
for field in required_fields:
self.assertIn(field, r.json)
# OIDC Discovery 1.0 section 3 (metadata value types)
bool_fields = ('claims_parameter_supported', 'request_parameter_supported', 'request_uri_parameter_supported', 'require_request_uri_registration')
list_fields = ('scopes_supported', 'response_types_supported', 'response_modes_supported', 'grant_types_supported', 'acr_values_supported', 'subject_types_supported', 'id_token_signing_alg_values_supported', 'id_token_encryption_alg_values_supported', 'id_token_encryption_enc_values_supported', 'userinfo_signing_alg_values_supported', 'userinfo_encryption_alg_values_supported', 'userinfo_encryption_enc_values_supported', 'request_object_signing_alg_values_supported', 'request_object_encryption_alg_values_supported', 'request_object_encryption_enc_values_supported', 'token_endpoint_auth_methods_supported', 'token_endpoint_auth_signing_alg_values_supported', 'display_values_supported', 'claim_types_supported', 'claims_supported', 'claims_locales_supported', 'ui_locales_supported')
https_url_fields = ('issuer', 'authorization_endpoint', 'token_endpoint', 'userinfo_endpoint', 'jwks_uri', 'registration_endpoint')
url_fields = ('service_documentation', 'op_policy_uri', 'op_tos_uri')
for field in bool_fields:
if field in r.json:
self.assertIsInstance(r.json[field], bool)
for field in list_fields:
if field in r.json:
self.assertIsInstance(r.json[field], list)
for field in https_url_fields:
if field in r.json:
self.assertIsInstance(r.json[field], str)
self.assertTrue(r.json[field].lower().startswith('https://'))
for field in url_fields:
if field in r.json:
self.assertIsInstance(r.json[field], str)
self.assertTrue(r.json[field].lower().startswith('http'))
# OIDC Discovery 1.0 section 3 (MUSTs on metadata values except https scheme and jwks_uri)
self.assertEqual(r.json['issuer'], ISSUER)
if 'scopes_supported' in r.json:
self.assertIsInstance(r.json['scopes_supported'], list)
for item in r.json['scopes_supported']:
self.assertIsInstance(item, str)
self.assertRegex(item, r'^[!#-\[\]-~]+$') # 1*( %x21 / %x23-5B / %x5D-7E )
self.assertIn('openid', r.json['scopes_supported'])
self.assertIn('RS256', r.json['id_token_signing_alg_values_supported'])
if 'token_endpoint_auth_signing_alg_values_supported' in r.json:
self.assertNotIn('none', r.json['token_endpoint_auth_signing_alg_values_supported'])
# OIDC Discovery 1.0 section 3 (jwks_uri) and RFC7517
self.assertTrue(r.json['jwks_uri'].startswith(ISSUER)) # Not a requirement by spec, but technically neccessary for this test
r_jwks = self.client.get(base_url=ISSUER, path=r.json['jwks_uri'][len(ISSUER):])
self.assertEqual(r_jwks.status_code, 200)
# The jwks_uri SHOULD include a Cache-Control header in the response that contains a max-age directive ...
self.assertIn('Cache-Control', r_jwks.headers)
self.assertIsInstance(r_jwks.json, dict) # also validates JSON syntax
self.assertIn('keys', r_jwks.json)
self.assertIsInstance(r_jwks.json['keys'], list)
has_sign_keys = False
has_encrypt_keys = False
kids = set()
for key in r_jwks.json['keys']:
self.assertIn('kty', key)
self.assertIsInstance(key['kty'], str)
if 'use' in key:
self.assertIsInstance(key['use'], str)
if key['use'] == 'sig':
has_sign_keys = True
if key['use'] == 'enc':
has_enc_keys = True
if 'key_ops' in key:
self.assertIsInstance(key['key_ops'], list)
self.assertNotIn('use', key) # SHOULD
for value in key['key_ops']:
self.assertIsInstance(value, str)
self.assertEqual(key['key_ops'].count(value), 1)
# OIDC: "The JWK Set MUST NOT contain private or symmetric key values."
self.assertNotIn('decrypt', key['key_ops'])
self.assertNotIn('sign', key['key_ops'])
if 'verify' in key['key_ops']:
has_sign_keys = True
if 'encrypt' in key['key_ops']:
has_enc_keys = True
if 'alg' in key:
self.assertIsInstance(key['alg'], str)
if 'kid' in key:
self.assertIsInstance(key['kid'], str)
self.assertNotIn(key['kid'], kids) # SHOULD
kids.add(key['kid'])
# ignoring everything X.509 related
# TODO: Validate algorithm-specific part of JWK
if has_sign_keys and has_encrypt_keys:
for key in r_jwks.json['keys']:
self.assertIn('use', key)
class TestOIDCBasicProfile(UffdTestCase):
def setUpDB(self):
db.session.add(OAuth2Key(**TEST_JWK))
db.session.add(OAuth2Client(service=Service(name='test', limit_access=False), client_id='test', client_secret='testsecret', redirect_uris=['https://service/callback']))
# Helper
def validate_claim_syntax(self, name, value):
# Strip language tag
if '#' in name:
name = name.split('#')[0]
str_claims = ('sub', 'name', 'given_name', 'family_name', 'middle_name', 'nickname', 'preferred_username', 'profile', 'picture', 'website', 'email', 'gender', 'birthdate', 'zoneinfo', 'locale', 'phone_number', 'acr')
if name in str_claims:
self.assertIsInstance(value, str)
if name in ('profile', 'picture', 'website'):
self.assertTrue(value.lower().startswith('http'))
if name in ('email_verified', 'phone_number_verified'):
self.assertIsInstance(value, bool)
if name in ('updated_at', 'auth_time'):
self.assertTrue(isinstance(value, int) or isinstance(value, float))
if name == 'address':
self.assertIsInstance(value, dict)
if name == 'amr':
self.assertIsInstance(value, list)
for item in value:
self.assertIsInstance(item, str)
def validate_id_token(self, id_token, nonce='testnonce', client_id='test'):
headers = jwt.get_unverified_header(id_token)
self.assertIn('kid', headers)
self.assertIsInstance(headers['kid'], str)
# This checks signature and exp
data = OAuth2Key.decode_jwt(id_token, options={'verify_aud': False})
self.assertIn('iss', data)
self.assertIsInstance(data['iss'], str)
self.assertIn('sub', data)
self.assertIn('aud', data)
self.assertIsInstance(data['aud'], str)
if client_id is not None:
self.assertEqual(data['aud'], client_id)
self.assertIn('iat', data)
self.assertTrue(isinstance(data['iat'], int) or isinstance(data['iat'], float))
if 'nonce' in data:
self.assertIsInstance(data['nonce'], str)
self.assertEqual(data.get('nonce'), nonce)
if 'azp' in data:
self.assertIsInstance(data['azp'], str)
for name, value in data.items():
self.validate_claim_syntax(name, value)
return data
def is_login_page(self, location):
url = urlparse(location)
return url.netloc in ('localhost', '') and url.path == url_for('session.login')
def is_callback(self, location):
return location.startswith('https://service/callback')
def do_auth_request(self, client_id='test', state='teststate', nonce='testnonce', redirect_uri='https://service/callback', scope='openid', follow_redirects=True, **kwargs):
r = self.client.get(path=url_for('oauth2.authorize', client_id=client_id, state=state, nonce=nonce, redirect_uri=redirect_uri, scope=scope, **kwargs), follow_redirects=False)
while follow_redirects and r.status_code == 302 and not self.is_login_page(r.location) and not self.is_callback(r.location):
r = self.client.get(path=r.location, follow_redirects=False)
return r
def do_login(self, r, loginname='testuser', password='userpassword'):
self.assertEqual(r.status_code, 302)
self.assertTrue(self.is_login_page(r.location))
self.client.get(path=url_for('session.logout'), follow_redirects=True)
args = parse_qs(urlparse(r.location).query)
r = self.client.post(path=url_for('session.login', ref=args['ref'][0]), data={'loginname': loginname, 'password': password}, follow_redirects=False)
while r.status_code == 302 and not self.is_login_page(r.location) and not self.is_callback(r.location):
r = self.client.get(path=r.location, follow_redirects=False)
return r
def validate_auth_response(self, r, state='teststate'):
self.assertEqual(r.status_code, 302)
self.assertTrue(self.is_callback(r.location))
args = parse_qs(urlparse(r.location).query)
for key in args:
self.assertNotIn(key, ('error', 'error_description', 'error_uri'))
self.assertEqual(len(args[key]), 1) # Not generally specified, but still a good check
if state is not None:
self.assertIn('state', args)
self.assertEqual(args['state'], [state])
else:
self.assertNotIn('state', args)
return {key: values[0] for key, values in args.items()}
def assert_auth_error(self, r, *errors, state='teststate'):
self.assertEqual(r.status_code, 302)
self.assertTrue(self.is_callback(r.location))
args = parse_qs(urlparse(r.location).query)
for key in args:
self.assertIn(key, ('error', 'error_description', 'error_uri', 'state'))
self.assertEqual(len(args[key]), 1)
self.assertIn('error', args)
if state is not None:
self.assertIn('state', args)
self.assertEqual(args['state'][0], state)
else:
self.assertNotIn('state', args)
if errors:
self.assertIn(args['error'][0], errors)
self.assertRegex(args['error'][0], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error_description' in args:
self.assertRegex(args['error_description'][0], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error_uri' in args:
self.assertRegex(args['error_uri'][0], r'^[!#-\[\]-~]+$') # 1*( %x21 / %x23-5B / %x5D-7E )
def do_token_request(self, client_id='test', client_secret='testsecret', redirect_uri='https://service/callback', **kwargs):
data = {'redirect_uri': redirect_uri, 'client_id': client_id, 'client_secret': client_secret}
data.update(kwargs)
return self.client.post(path=url_for('oauth2.token'), data=data, follow_redirects=True)
def validate_token_response(self, r, nonce='testnonce', client_id='test'):
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertIn('Cache-Control', r.headers)
self.assertEqual(r.headers['Cache-Control'].lower(), 'no-store')
for key in r.json:
self.assertNotIn(key, ('error', 'error_description', 'error_uri'))
self.assertIn('access_token', r.json)
self.assertIsInstance(r.json['access_token'], str)
self.assertIn('token_type', r.json)
self.assertIsInstance(r.json['token_type'], str)
# OIDC Core 1.0 section 3.1.3.3:
# > The OAuth 2.0 token_type response parameter value MUST be Bearer,
# > [...] unless another Token Type has been negotiated with the Client.
self.assertEqual(r.json['token_type'].lower(), 'bearer')
if 'expires_in' in r.json:
self.assertTrue(isinstance(r.json['expires_in'], int) or isinstance(data['expires_in'], float))
if 'refresh_token' in r.json:
self.assertIsInstance(r.json['refresh_token'], str)
if 'scope' in r.json:
self.assertIsInstance(r.json['scope'], str)
# scope = scope-token *( SP scope-token )
# scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
self.assertRegex(r.json['scope'], r'^[!#-\[\]-~]+( [!#-\[\]-~]+)*$')
# OIDC Core 1.0 section 3.1.3.3:
# > All Token Responses that contain tokens, secrets, or other sensitive
# > information MUST include the following HTTP response header fields and values:
# > Cache-Control: no-store
self.assertIn('id_token', r.json)
return self.validate_id_token(r.json['id_token'], nonce=nonce, client_id=client_id)
def assert_token_error(self, r, *errors):
self.assertEqual(r.content_type, 'application/json')
if r.json.get('error', '') == 'invalid_client':
self.assertEqual(r.status_code, 401)
else:
self.assertEqual(r.status_code, 400)
for key in r.json:
self.assertIn(key, ('error', 'error_description', 'error_uri'))
self.assertIn('error', r.json)
if errors:
self.assertIn(r.json['error'], errors)
self.assertRegex(r.json['error'], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error_description' in r.json:
self.assertRegex(r.json['error_description'], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error_uri' in r.json:
self.assertRegex(r.json['error_uri'], r'^[!#-\[\]-~]+$') # 1*( %x21 / %x23-5B / %x5D-7E )
def do_userinfo_request(self, access_token):
return self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%access_token)], follow_redirects=True)
def validate_userinfo_response(self, r):
self.assertEqual(r.status_code, 200)
# We ignore everything related to encrypted/signed JWT userinfo here
self.assertEqual(r.content_type, 'application/json')
self.assertIn('sub', r.json)
for name, value in r.json.items():
self.validate_claim_syntax(name, value)
def assert_userinfo_error(self, r):
self.assertEqual(r.status_code, 401)
self.assertEqual(len(r.headers.getlist('WWW-Authenticate')), 1)
method, args = (r.headers['WWW-Authenticate'].split(' ', 1) + [''])[:2]
args = {item.split('=', 1)[0]: item.split('=', 1)[1].strip(' \n"') for item in args.split(',') if item.strip()}
if 'scope' in args:
self.assertRegex(args['scope'], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error' in args:
self.assertRegex(args['error'], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
if 'error_description' in args:
self.assertRegex(args['error_description'], r'^[ -!#-\[\]-~]+$') # 1*( %x20-x21 / %x23-5B / %x5D-7E )
def test(self):
self.login_as('user')
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
r = self.do_userinfo_request(r.json['access_token'])
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
def test_notloggedin(self):
r = self.do_auth_request(response_type='code')
r = self.do_login(r)
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
def test_no_state(self):
self.login_as('user')
r = self.do_auth_request(response_type='code', state=None)
args = self.validate_auth_response(r, state=None)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
def test_no_nonce(self):
self.login_as('user')
r = self.do_auth_request(response_type='code', nonce=None)
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r, nonce=None)
self.assertEqual(id_token['sub'], '10000')
def test_redirect_uri(self):
self.login_as('user')
# No redirect_uri in auth request is fine if there is only one uri registered
r = self.do_auth_request(response_type='code', redirect_uri=None)
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], redirect_uri=None)
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
# If auth request has redirect_uri, it is required in token request
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], redirect_uri=None)
self.assert_token_error(r)
# If auth request has redirect_uri, it the redirect_uri in the token request must be the same
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], redirect_uri='https://foobar/callback')
self.assert_token_error(r)
# Invalid redirect_uri
r = self.do_auth_request(response_type='code', redirect_uri='http://foobar/callback')
self.assertEqual(r.status_code, 400) # No redirect!
# redirect_uri is required in auth request if there is more than a single uri registered
client = OAuth2Client.query.one()
client.redirect_uris.append('https://service/callback2')
db.session.commit()
r = self.do_auth_request(response_type='code', redirect_uri=None)
self.assertEqual(r.status_code, 400) # No redirect!
def test_auth_errors(self):
# Missing response_type
r = self.do_auth_request()
self.assert_auth_error(r, 'invalid_request')
# Invalid response_type
r = self.do_auth_request(response_type='foobar')
self.assert_auth_error(r, 'unsupported_response_type')
# Missing client_id
r = self.do_auth_request(response_type='code', client_id=None)
self.assertEqual(r.status_code, 400) # No redirect!
# Invalid client_id
r = self.do_auth_request(response_type='code', client_id='foobar')
self.assertEqual(r.status_code, 400) # No redirect!
# Duplicate parameter
r = self.do_auth_request(response_type='code', client_id=['test', 'foobar'])
self.assertEqual(r.status_code, 400) # No redirect!
def test_access_denied(self):
Service.query.one().limit_access = True
db.session.commit()
self.login_as('user')
r = self.do_auth_request(response_type='code')
self.assert_auth_error(r, 'access_denied')
def test_auth_request_uri(self):
self.login_as('user')
r = self.do_auth_request(response_type='code', request_uri='https://localhost/myrequest_uri')
self.assert_auth_error(r, 'request_uri_not_supported')
def test_auth_request_unsigned(self):
self.login_as('user')
request_params = {
'response_type': 'code',
'client_id': 'test',
'redirect_uri': 'http://service/callback',
'scope': 'openid',
'state': 'teststate',
'nonce': 'testnonce',
'claims': {
'userinfo': {
'name': None,
'email': {'essential': True},
'email_verified': {'essential': True},
},
'id_token': {
'email': None,
}
}
}
r = self.do_auth_request(response_type='code', request=jwt.encode(request_params, algorithm='none', key=None))
self.assert_auth_error(r, 'request_not_supported')
def test_token_client_auth(self):
self.login_as('user')
# Auth via body -> ACCEPT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.validate_token_response(r)
# Auth via header -> ACCEPT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.client.post(
path=url_for('oauth2.token'),
data={'redirect_uri': 'https://service/callback', 'grant_type': 'authorization_code', 'code': args['code']},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'},
follow_redirects=True,
)
self.validate_token_response(r)
# Auth via header, but same client id also in body -> ACCEPT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.client.post(
path=url_for('oauth2.token'),
data={'redirect_uri': 'https://service/callback', 'grant_type': 'authorization_code', 'client_id': 'test', 'code': args['code']},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'},
follow_redirects=True,
)
self.validate_token_response(r)
# Different client id in body and header -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.client.post(
path=url_for('oauth2.token'),
data={'redirect_uri': 'https://service/callback', 'grant_type': 'authorization_code', 'client_id': 'XXXX', 'code': args['code']},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'},
follow_redirects=True,
)
self.assert_token_error(r, 'invalid_request')
# Duplicate client id in body -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_id=['test', 'XXXX'])
self.assert_token_error(r, 'invalid_request')
# Duplicate client secret in body -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_secret=['testsecret', 'XXXXX'])
self.assert_token_error(r, 'invalid_request')
# Client secret in body and header -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.client.post(
path=url_for('oauth2.token'),
data={'redirect_uri': 'https://service/callback', 'grant_type': 'authorization_code', 'client_id': 'test', 'client_secret': 'testsecret', 'code': args['code']},
headers={'Authorization': f'Basic dGVzdDp0ZXN0c2VjcmV0'},
follow_redirects=True,
)
self.assert_token_error(r, 'invalid_request')
# No secret -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_secret=None)
self.assert_token_error(r, 'invalid_client')
# No client id but secret -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_id=None)
self.assert_token_error(r, 'invalid_client')
# No client id and no secret -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_id=None, client_secret=None)
self.assert_token_error(r, 'invalid_client')
# Unknown client id -> REJECT
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_id='XXXX')
self.assert_token_error(r, 'invalid_client')
def test_token_errors(self):
self.login_as('user')
# Missing grant_type parameter
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(code=args['code'])
self.assert_token_error(r, 'invalid_request')
# Missing code parameter
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code')
self.assert_token_error(r, 'invalid_request')
# redirect_uri behaviour is already tested in test_redirect_uri
# Invalid grant type
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='foobar', code=args['code'])
self.assert_token_error(r, 'unsupported_grant_type')
# Duplicate code parameter
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=[args['code'], 'XXXXX'])
self.assert_token_error(r, 'invalid_request')
# Invalid code parameter
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code='XXXXX')
self.assert_token_error(r, 'invalid_grant')
# Invalid code parameter
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'].split('-')[0]+'-XXXXX')
self.assert_token_error(r, 'invalid_grant')
# Code was issued to different client
db.session.add(OAuth2Client(service=Service(name='test2', limit_access=False), client_id='test2', client_secret='testsecret2', redirect_uris=['https://service2/callback']))
db.session.commit()
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'], client_id='test2', client_secret='testsecret2')
self.assert_token_error(r, 'invalid_grant')
def test_userinfo_auth(self):
self.login_as('user')
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.validate_token_response(r)
access_token = r.json['access_token']
# GET + Bearer
r = self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%access_token)], follow_redirects=True)
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
# POST + Bearer
r = self.client.post(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%access_token)], follow_redirects=True)
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
# "Bearer" is case-insensitive
r = self.client.post(path=url_for('oauth2.userinfo'), headers=[('authorization', 'bearer %s'%access_token)], follow_redirects=True)
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
# Invalid auth scheme
r = self.client.post(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Basic dGVzdDp0ZXN0c2VjcmV0')], follow_redirects=True)
self.assert_userinfo_error(r)
# Invalid bearer token
r = self.client.post(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s-XXXXX'%access_token.split('-')[0])], follow_redirects=True)
self.assert_userinfo_error(r)
r = self.client.post(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer XXXXX')], follow_redirects=True)
self.assert_userinfo_error(r)
# POST + body
r = self.client.post(path=url_for('oauth2.userinfo'), data={'access_token': access_token}, follow_redirects=True)
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
# GET + query
r = self.client.get(path=url_for('oauth2.userinfo', access_token=access_token), follow_redirects=True)
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
# POST + Bearer + body -> REJECT
r = self.client.post(path=url_for('oauth2.userinfo'), data={'access_token': access_token}, headers=[('Authorization', 'Bearer %s'%access_token)], follow_redirects=True)
self.assert_userinfo_error(r)
# No auth -> REJECT
r = self.client.post(path=url_for('oauth2.userinfo'), follow_redirects=True)
self.assert_userinfo_error(r)
def test_scope(self):
self.login_as('user')
# Scope values used that are not understood by an implementation SHOULD be ignored.
r = self.do_auth_request(response_type='code', scope='openid profile email address phone groups foobar')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
r = self.do_userinfo_request(r.json['access_token'])
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
self.assertEqual(r.json['name'], 'Test User')
self.assertEqual(r.json['preferred_username'], 'testuser')
self.assertEqual(r.json['email'], 'test@example.com')
self.assertEqual(sorted(r.json['groups']), sorted(['users', 'uffd_access']))
def test_claims(self):
self.login_as('user')
# Scope values used that are not understood by an implementation SHOULD be ignored.
r = self.do_auth_request(response_type='code', claims='{"userinfo": {"name": {"essential": true}}, "id_token": {"preferred_username": {"essential": true}, "email": null}}')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
id_token = self.validate_token_response(r)
self.assertEqual(id_token['sub'], '10000')
self.assertEqual(id_token['preferred_username'], 'testuser')
self.assertEqual(id_token['email'], 'test@example.com')
self.assertNotIn('name', r.json)
r = self.do_userinfo_request(r.json['access_token'])
self.validate_userinfo_response(r)
self.assertEqual(r.json['sub'], '10000')
self.assertEqual(r.json['name'], 'Test User')
self.assertNotIn('email', r.json)
def test_prompt_none(self):
r = self.do_auth_request(response_type='code', prompt='none')
self.assert_auth_error(r, 'login_required')
self.login_as('user')
r = self.do_auth_request(response_type='code', prompt='none')
args = self.validate_auth_response(r)
self.assertIn('code', args)
# OIDC Core 1.0 section 3.1.2.1.:
# > If this parameter contains none with any other value, an error is returned.
r = self.do_auth_request(response_type='code', prompt='none login')
self.assert_auth_error(r)
@unittest.skip('prompt=login is not implemented') # MUST
def test_prompt_login(self):
self.login_as('user')
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
self.assertIn('code', args)
r = self.do_auth_request(response_type='code', prompt='login')
self.assertEqual(r.status_code, 302)
self.assertTrue(self.is_login_page(r.location))
# TODO: max_age
def test_sub_value(self):
# Via id_token_hint or claims.id_token.sub.value
self.login_as('user')
r = self.do_auth_request(response_type='code', prompt='none')
args = self.validate_auth_response(r)
r = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.validate_token_response(r)
id_token = r.json['id_token']
r = self.do_auth_request(response_type='code', prompt='none', id_token_hint=id_token)
args = self.validate_auth_response(r)
self.assertIn('code', args)
r = self.do_auth_request(response_type='code', prompt='none', id_token_hint='XXXXX')
self.assert_auth_error(r, 'invalid_request')
r = self.do_auth_request(response_type='code', prompt='none', claims='{"id_token": {"sub": {"value": "10000"}}}')
args = self.validate_auth_response(r)
r = self.do_auth_request(response_type='code', prompt='none', claims='{"id_token": {"sub": {"value": "10001"}}}')
self.assert_auth_error(r, 'login_required')
# sub value in id_token_hint and claims is the same -> Not ambiguous
r = self.do_auth_request(response_type='code', prompt='none', id_token_hint=id_token, claims='{"id_token": {"sub": {"value": "10000"}}}')
args = self.validate_auth_response(r)
self.assertIn('code', args)
# sub value in id_token_hint and claims differs -> Ambiguous
r = self.do_auth_request(response_type='code', prompt='none', id_token_hint=id_token, claims='{"id_token": {"sub": {"value": "10001"}}}')
self.assert_auth_error(r, 'invalid_request')
self.login_as('admin')
r = self.do_auth_request(response_type='code', prompt='none', id_token_hint=id_token)
self.assert_auth_error(r, 'login_required')
def test_code_reuse(self):
self.login_as('user')
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r1 = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.validate_token_response(r1)
r2 = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.assert_token_error(r2, 'invalid_grant')
@unittest.skip('Token revoking on reuse is not implemented') # SHOULD
def test_code_reuse_revoke(self):
self.login_as('user')
r = self.do_auth_request(response_type='code')
args = self.validate_auth_response(r)
r1 = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.validate_token_response(r1)
r2 = self.do_token_request(grant_type='authorization_code', code=args['code'])
self.assert_token_error(r2, 'invalid_grant')
r = self.do_userinfo_request(r1.json['access_token'])
self.assert_userinfo_error(r)
import datetime
import time
from flask import url_for
from flask import url_for, session
from uffd.database import db
from uffd.models import User, Role, RoleGroup
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.user.models import Group
from uffd.role.models import Role
from uffd import create_app, db
from utils import dump, UffdTestCase
from tests.utils import dump, UffdTestCase
class TestRoleViews(UffdTestCase):
def setUp(self):
super().setUp()
self.client.post(path=url_for('session.login'),
data={'loginname': 'testadmin', 'password': 'adminpassword'}, follow_redirects=True)
self.login_as('admin')
def test_index(self):
db.session.add(Role(name='base', description='Base role description'))
......@@ -40,7 +32,7 @@ class TestRoleViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_new(self):
r = self.client.get(path=url_for('role.show'), follow_redirects=True)
r = self.client.get(path=url_for('role.new'), follow_redirects=True)
dump('role_new', r)
self.assertEqual(r.status_code, 200)
......@@ -48,27 +40,26 @@ class TestRoleViews(UffdTestCase):
role = Role(name='base', description='Base role description')
db.session.add(role)
db.session.commit()
role.groups.add(Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com'))
role.groups[self.get_admin_group()] = RoleGroup()
db.session.commit()
self.assertEqual(role.name, 'base')
self.assertEqual(role.description, 'Base role description')
self.assertEqual([group.dn for group in role.groups], ['cn=uffd_admin,ou=groups,dc=example,dc=com'])
self.assertSetEqual(set(role.groups), {self.get_admin_group()})
r = self.client.post(path=url_for('role.update', roleid=role.id),
data={'name': 'base1', 'description': 'Base role description1', 'group-20001': '1', 'group-20002': '1'},
data={'name': 'base1', 'description': 'Base role description1', 'moderator-group': '', 'group-%d'%self.get_users_group().id: '1', 'group-%d'%self.get_access_group().id: '1'},
follow_redirects=True)
dump('role_update', r)
self.assertEqual(r.status_code, 200)
role = Role.query.get(role.id)
self.assertEqual(role.name, 'base1')
self.assertEqual(role.description, 'Base role description1')
self.assertEqual(sorted([group.dn for group in role.groups]), ['cn=uffd_access,ou=groups,dc=example,dc=com',
'cn=users,ou=groups,dc=example,dc=com'])
# TODO: verify that group memberships are updated (currently not possible with ldap mock!)
self.assertSetEqual(set(role.groups), {self.get_access_group(), self.get_users_group()})
# TODO: verify that group memberships are updated
def test_create(self):
self.assertIsNone(Role.query.filter_by(name='base').first())
r = self.client.post(path=url_for('role.update'),
data={'name': 'base', 'description': 'Base role description', 'group-20001': '1', 'group-20002': '1'},
data={'name': 'base', 'description': 'Base role description', 'moderator-group': '', 'group-%d'%self.get_users_group().id: '1', 'group-%d'%self.get_access_group().id: '1'},
follow_redirects=True)
dump('role_create', r)
self.assertEqual(r.status_code, 200)
......@@ -76,8 +67,21 @@ class TestRoleViews(UffdTestCase):
self.assertIsNotNone(role)
self.assertEqual(role.name, 'base')
self.assertEqual(role.description, 'Base role description')
self.assertEqual(sorted([group.dn for group in role.groups]), ['cn=uffd_access,ou=groups,dc=example,dc=com',
'cn=users,ou=groups,dc=example,dc=com'])
self.assertSetEqual(set(role.groups), {self.get_access_group(), self.get_users_group()})
# TODO: verify that group memberships are updated (currently not possible with ldap mock!)
def test_create_with_moderator_group(self):
self.assertIsNone(Role.query.filter_by(name='base').first())
r = self.client.post(path=url_for('role.update'),
data={'name': 'base', 'description': 'Base role description', 'moderator-group': self.get_admin_group().id, 'group-%d'%self.get_users_group().id: '1', 'group-%d'%self.get_access_group().id: '1'},
follow_redirects=True)
self.assertEqual(r.status_code, 200)
role = Role.query.filter_by(name='base').first()
self.assertIsNotNone(role)
self.assertEqual(role.name, 'base')
self.assertEqual(role.description, 'Base role description')
self.assertEqual(role.moderator_group.name, 'uffd_admin')
self.assertSetEqual(set(role.groups), {self.get_access_group(), self.get_users_group()})
# TODO: verify that group memberships are updated (currently not possible with ldap mock!)
def test_delete(self):
......@@ -92,5 +96,58 @@ class TestRoleViews(UffdTestCase):
self.assertIsNone(Role.query.get(role_id))
# TODO: verify that group memberships are updated (currently not possible with ldap mock!)
class TestRoleViewsOL(TestRoleViews):
use_openldap = True
def test_set_default(self):
db.session.add(User(loginname='service', is_service_user=True, primary_email_address='service@example.com', displayname='Service'))
db.session.commit()
role = Role(name='test')
db.session.add(role)
role.groups[self.get_admin_group()] = RoleGroup()
user1 = self.get_user()
user2 = self.get_admin()
service_user = User.query.filter_by(loginname='service').one_or_none()
self.assertSetEqual(set(self.get_user().roles_effective), set())
self.assertSetEqual(set(self.get_admin().roles_effective), set())
self.assertSetEqual(set(service_user.roles_effective), set())
role.members.append(self.get_user())
role.members.append(service_user)
self.assertSetEqual(set(self.get_user().roles_effective), {role})
self.assertSetEqual(set(self.get_admin().roles_effective), set())
self.assertSetEqual(set(service_user.roles_effective), {role})
db.session.commit()
role_id = role.id
self.assertSetEqual(set(role.members), {self.get_user(), service_user})
r = self.client.get(path=url_for('role.set_default', roleid=role.id), follow_redirects=True)
dump('role_set_default', r)
self.assertEqual(r.status_code, 200)
role = Role.query.get(role_id)
service_user = User.query.filter_by(loginname='service').one_or_none()
self.assertSetEqual(set(role.members), {service_user})
self.assertSetEqual(set(self.get_user().roles_effective), {role})
self.assertSetEqual(set(self.get_admin().roles_effective), {role})
def test_unset_default(self):
admin_role = Role(name='admin', is_default=True)
db.session.add(admin_role)
admin_role.groups[self.get_admin_group()] = RoleGroup()
db.session.add(User(loginname='service', is_service_user=True, primary_email_address='service@example.com', displayname='Service'))
db.session.commit()
role = Role(name='test', is_default=True)
db.session.add(role)
service_user = User.query.filter_by(loginname='service').one_or_none()
role.members.append(service_user)
self.assertSetEqual(set(self.get_user().roles_effective), {role, admin_role})
self.assertSetEqual(set(self.get_admin().roles_effective), {role, admin_role})
self.assertSetEqual(set(service_user.roles_effective), {role})
db.session.commit()
role_id = role.id
admin_role_id = admin_role.id
self.assertSetEqual(set(role.members), {service_user})
r = self.client.get(path=url_for('role.unset_default', roleid=role.id), follow_redirects=True)
dump('role_unset_default', r)
self.assertEqual(r.status_code, 200)
role = Role.query.get(role_id)
admin_role = Role.query.get(admin_role_id)
service_user = User.query.filter_by(loginname='service').one_or_none()
self.assertSetEqual(set(role.members), {service_user})
self.assertSetEqual(set(self.get_user().roles_effective), {admin_role})
self.assertSetEqual(set(self.get_admin().roles_effective), {admin_role})
from flask import url_for
from uffd.database import db
from uffd.models import Role, RoleGroup
from tests.utils import dump, UffdTestCase
class TestRolemodViewsLoggedOut(UffdTestCase):
def test_acl_nologin(self):
r = self.client.get(path=url_for('rolemod.index'), follow_redirects=True)
dump('rolemod_acl_nologin', r)
self.assertEqual(r.status_code, 200)
def test_index(self):
db.session.add(Role(name='test_role_1', moderator_group=self.get_access_group()))
db.session.add(Role(name='test_role_2', moderator_group=self.get_admin_group()))
db.session.add(Role(name='test_role_3'))
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('rolemod.index'), follow_redirects=True)
dump('rolemod_index', r)
self.assertEqual(r.status_code, 200)
self.assertIn('test_role_1'.encode(), r.data)
self.assertNotIn('test_role_2'.encode(), r.data)
self.assertNotIn('test_role_3'.encode(), r.data)
class TestRolemodViews(UffdTestCase):
def setUp(self):
super().setUp()
self.login_as('user')
def test_acl_notmod(self):
db.session.add(Role(name='test', moderator_group=self.get_admin_group()))
db.session.commit()
r = self.client.get(path=url_for('rolemod.index'), follow_redirects=True)
dump('rolemod_acl_notmod', r)
self.assertEqual(r.status_code, 403)
def test_show(self):
role = Role(name='test', moderator_group=self.get_access_group())
db.session.add(role)
role.members.append(self.get_admin())
db.session.commit()
r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True)
dump('rolemod_show', r)
self.assertEqual(r.status_code, 200)
def test_show_empty(self):
role = Role(name='test', moderator_group=self.get_access_group())
db.session.add(role)
db.session.commit()
r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True)
dump('rolemod_show_empty', r)
self.assertEqual(r.status_code, 200)
def test_show_noperm(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test', moderator_group=self.get_admin_group())
db.session.add(role)
db.session.commit()
r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True)
dump('rolemod_show_noperm', r)
self.assertEqual(r.status_code, 403)
def test_show_nomod(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test')
db.session.add(role)
db.session.commit()
r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True)
dump('rolemod_show_nomod', r)
self.assertEqual(r.status_code, 403)
def test_update(self):
role = Role(name='test', description='old_description', moderator_group=self.get_access_group())
db.session.add(role)
db.session.commit()
r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'new_description'}, follow_redirects=True)
dump('rolemod_update', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(Role.query.get(role.id).description, 'new_description')
def test_update_descr_too_long(self):
role = Role(name='test', description='old_description', moderator_group=self.get_access_group())
db.session.add(role)
db.session.commit()
r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'long_description'*300}, follow_redirects=True)
dump('rolemod_update_descr_too_long', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(Role.query.get(role.id).description, 'old_description')
def test_update_noperm(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test', description='old_description', moderator_group=self.get_admin_group())
db.session.add(role)
db.session.commit()
r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'new_description'}, follow_redirects=True)
dump('rolemod_update_noperm', r)
self.assertEqual(r.status_code, 403)
self.assertEqual(Role.query.get(role.id).description, 'old_description')
def test_update_nomod(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test', description='old_description')
db.session.add(role)
db.session.commit()
r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'new_description'}, follow_redirects=True)
dump('rolemod_update_nomod', r)
self.assertEqual(r.status_code, 403)
self.assertEqual(Role.query.get(role.id).description, 'old_description')
def test_delete_member(self):
role = Role(name='test', moderator_group=self.get_access_group())
role.groups[self.get_admin_group()] = RoleGroup()
db.session.add(role)
role.members.append(self.get_admin())
db.session.commit()
role.update_member_groups()
db.session.commit()
user = self.get_admin()
group = self.get_admin_group()
self.assertTrue(user in group.members)
role = Role.query.get(role.id)
self.assertTrue(user in role.members)
r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_id=user.id), follow_redirects=True)
dump('rolemod_delete_member', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_admin()
group = self.get_admin_group()
self.assertFalse(user_updated in group.members)
role = Role.query.get(role.id)
self.assertFalse(user_updated in role.members)
def test_delete_member_nomember(self):
role = Role(name='test', moderator_group=self.get_access_group())
role.groups[self.get_admin_group()] = RoleGroup()
db.session.add(role)
db.session.commit()
user = self.get_admin()
r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_id=user.id), follow_redirects=True)
dump('rolemod_delete_member_nomember', r)
self.assertEqual(r.status_code, 200)
def test_delete_member_noperm(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test', moderator_group=self.get_admin_group())
db.session.add(role)
role.members.append(self.get_admin())
db.session.commit()
user = self.get_admin()
role = Role.query.get(role.id)
self.assertTrue(user in role.members)
r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_id=user.id), follow_redirects=True)
dump('rolemod_delete_member_noperm', r)
self.assertEqual(r.status_code, 403)
user_updated = self.get_admin()
role = Role.query.get(role.id)
self.assertTrue(user_updated in role.members)
def test_delete_member_nomod(self):
# Make sure we pass the blueprint-wide acl check
db.session.add(Role(name='other_role', moderator_group=self.get_access_group()))
role = Role(name='test')
db.session.add(role)
role.members.append(self.get_admin())
db.session.commit()
user = self.get_admin()
role = Role.query.get(role.id)
self.assertTrue(user in role.members)
r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_id=user.id), follow_redirects=True)
dump('rolemod_delete_member_nomod', r)
self.assertEqual(r.status_code, 403)
user_updated = self.get_admin()
role = Role.query.get(role.id)
self.assertTrue(user_updated in role.members)
import datetime
import re
import time
from flask import url_for, request, session
from uffd.database import db
from uffd.models import PasswordToken, UserEmail, Role, RoleGroup, Service, ServiceUser, FeatureFlag, MFAMethod, RecoveryCodeMethod, TOTPMethod, WebauthnMethod
from uffd.models.mfa import _hotp
from tests.utils import dump, UffdTestCase, db_flush
class TestSelfservice(UffdTestCase):
def test_index(self):
self.login_as('user')
r = self.client.get(path=url_for('selfservice.index'))
dump('selfservice_index', r)
self.assertEqual(r.status_code, 200)
user = request.user
self.assertIn(user.displayname.encode(), r.data)
self.assertIn(user.loginname.encode(), r.data)
self.assertIn(user.primary_email.address.encode(), r.data)
def test_update_displayname(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': 'New Display Name'},
follow_redirects=True)
dump('update_displayname', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertEqual(user.displayname, 'New Display Name')
def test_update_displayname_invalid(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': ''},
follow_redirects=True)
dump('update_displayname_invalid', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertNotEqual(user.displayname, '')
def test_add_email(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.add_email'),
data={'address': 'new@example.com'},
follow_redirects=True)
dump('selfservice_add_email', r)
self.assertEqual(r.status_code, 200)
self.assertIn('new@example.com', self.app.last_mail['To'])
m = re.search(r'/email/([0-9]+)/verify/(.*)', str(self.app.last_mail.get_content()))
email_id, secret = m.groups()
email = UserEmail.query.get(email_id)
self.assertEqual(email.user.id, request.user.id)
self.assertEqual(email.address, 'new@example.com')
self.assertFalse(email.verified)
self.assertFalse(email.verification_expired)
self.assertTrue(email.verification_secret.verify(secret))
def test_add_email_duplicate(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.add_email'),
data={'address': 'test@example.com'},
follow_redirects=True)
dump('selfservice_add_email_duplicate', r)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(self.get_user().all_emails), 1)
self.assertEqual(UserEmail.query.filter_by(user=None).all(), [])
def test_verify_email(self):
self.login_as('user')
email = UserEmail(user=self.get_user(), address='new@example.com')
secret = email.start_verification()
db.session.add(email)
db.session.commit()
email_id = email.id
r = self.client.get(path=url_for('selfservice.verify_email', email_id=email_id, secret=secret), follow_redirects=True)
dump('selfservice_verify_email', r)
self.assertEqual(r.status_code, 200)
email = UserEmail.query.get(email_id)
self.assertTrue(email.verified)
self.assertEqual(self.get_user().primary_email.address, 'test@example.com')
def test_verify_email_notfound(self):
self.login_as('user')
r = self.client.get(path=url_for('selfservice.verify_email', email_id=2342, secret='invalidsecret'), follow_redirects=True)
dump('selfservice_verify_email_notfound', r)
def test_verify_email_wrong_user(self):
self.login_as('user')
email = UserEmail(user=self.get_admin(), address='new@example.com')
secret = email.start_verification()
db.session.add(email)
db.session.commit()
r = self.client.get(path=url_for('selfservice.verify_email', email_id=email.id, secret=secret), follow_redirects=True)
dump('selfservice_verify_email_wrong_user', r)
self.assertFalse(email.verified)
def test_verify_email_wrong_secret(self):
self.login_as('user')
email = UserEmail(user=self.get_user(), address='new@example.com')
secret = email.start_verification()
db.session.add(email)
db.session.commit()
r = self.client.get(path=url_for('selfservice.verify_email', email_id=email.id, secret='invalidsecret'), follow_redirects=True)
dump('selfservice_verify_email_wrong_secret', r)
self.assertFalse(email.verified)
def test_verify_email_expired(self):
self.login_as('user')
email = UserEmail(user=self.get_user(), address='new@example.com')
secret = email.start_verification()
email.verification_expires = datetime.datetime.utcnow() - datetime.timedelta(days=1)
db.session.add(email)
db.session.commit()
r = self.client.get(path=url_for('selfservice.verify_email', email_id=email.id, secret=secret), follow_redirects=True)
dump('selfservice_verify_email_expired', r)
self.assertFalse(email.verified)
def test_verify_email_legacy(self):
self.login_as('user')
email = UserEmail(
user=self.get_user(),
address='new@example.com',
verification_legacy_id=1337,
_verification_secret='{PLAIN}ZgvsUs2bZjr9Whpy1la7Q0PHbhjmpXtNdH1mCmDbQP7',
verification_expires=datetime.datetime.utcnow()+datetime.timedelta(days=1)
)
db.session.add(email)
db.session.commit()
email_id = email.id
r = self.client.get(path=f'/self/token/mail_verification/1337/ZgvsUs2bZjr9Whpy1la7Q0PHbhjmpXtNdH1mCmDbQP7', follow_redirects=True)
dump('selfservice_verify_email_legacy', r)
self.assertEqual(r.status_code, 200)
email = UserEmail.query.get(email_id)
self.assertTrue(email.verified)
self.assertEqual(self.get_user().primary_email, email)
def test_verify_email_duplicate_strict_uniqueness(self):
FeatureFlag.unique_email_addresses.enable()
db.session.commit()
self.login_as('user')
email = UserEmail(user=self.get_user(), address='admin@example.com')
secret = email.start_verification()
db.session.add(email)
db.session.commit()
email_id = email.id
r = self.client.get(path=url_for('selfservice.verify_email', email_id=email.id, secret=secret), follow_redirects=True)
dump('selfservice_verify_email_duplicate_strict_uniqueness', r)
email = UserEmail.query.get(email_id)
self.assertFalse(email.verified)
def test_retry_email_verification(self):
self.login_as('user')
email = UserEmail(user=self.get_user(), address='new@example.com')
old_secret = email.start_verification()
db.session.add(email)
db.session.commit()
r = self.client.get(path=url_for('selfservice.retry_email_verification', email_id=email.id), follow_redirects=True)
dump('selfservice_retry_email_verification', r)
self.assertEqual(r.status_code, 200)
self.assertIn('new@example.com', self.app.last_mail['To'])
m = re.search(r'/email/([0-9]+)/verify/(.*)', str(self.app.last_mail.get_content()))
email_id, secret = m.groups()
email = UserEmail.query.get(email_id)
self.assertEqual(email.user.id, request.user.id)
self.assertEqual(email.address, 'new@example.com')
self.assertFalse(email.verified)
self.assertFalse(email.verification_expired)
self.assertTrue(email.verification_secret.verify(secret))
self.assertFalse(email.verification_secret.verify(old_secret))
def test_delete_email(self):
self.login_as('user')
email = UserEmail(user=self.get_user(), address='new@example.com', verified=True)
db.session.add(email)
self.get_user().recovery_email = email
db.session.commit()
r = self.client.post(path=url_for('selfservice.delete_email', email_id=email.id), follow_redirects=True)
dump('selfservice_delete_email', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(UserEmail.query.filter_by(address='new@example.com').first())
self.assertIsNone(self.get_user().recovery_email)
self.assertEqual(self.get_user().primary_email.address, 'test@example.com')
def test_delete_email_invalid(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.delete_email', email_id=2324), follow_redirects=True)
self.assertEqual(r.status_code, 404)
def test_delete_email_primary(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.delete_email', email_id=request.user.primary_email.id), follow_redirects=True)
dump('selfservice_delete_email_primary', r)
self.assertEqual(self.get_user().primary_email.address, 'test@example.com')
def test_update_email_preferences(self):
self.login_as('user')
user_id = self.get_user().id
email = UserEmail(user=self.get_user(), address='new@example.com', verified=True)
db.session.add(email)
service = Service(name='service', enable_email_preferences=True, limit_access=False)
db.session.add(service)
db.session.commit()
email_id = email.id
service_id = service.id
old_email_id = self.get_user().primary_email.id
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(email_id), 'recovery_email': 'primary'},
follow_redirects=True)
dump('selfservice_update_email_preferences', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.get_user().primary_email.id, email.id)
self.assertIsNone(self.get_user().recovery_email)
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(old_email_id), 'recovery_email': str(email_id)},
follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.get_user().primary_email.id, old_email_id)
self.assertEqual(self.get_user().recovery_email.id, email_id)
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(old_email_id), 'recovery_email': 'primary', f'service_{service_id}_email': 'primary'},
follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertIsNone(ServiceUser.query.get((service_id, user_id)).service_email)
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(old_email_id), 'recovery_email': 'primary', f'service_{service_id}_email': str(email_id)},
follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(ServiceUser.query.get((service_id, user_id)).service_email.id, email_id)
def test_update_email_preferences_unverified(self):
self.login_as('user')
user_id = self.get_user().id
email = UserEmail(user=self.get_user(), address='new@example.com')
db.session.add(email)
service = Service(name='service', enable_email_preferences=True, limit_access=False)
db.session.add(service)
db.session.commit()
email_id = email.id
service_id = service.id
old_email_id = self.get_user().primary_email.id
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(email_id), 'recovery_email': 'primary'},
follow_redirects=True)
with self.app.test_request_context():
self.assertEqual(self.get_user().primary_email.address, 'test@example.com')
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(old_email_id), 'recovery_email': str(email_id)},
follow_redirects=True)
with self.app.test_request_context():
self.assertIsNone(self.get_user().recovery_email)
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(old_email_id), 'recovery_email': 'primary', f'service_{service_id}_email': str(email_id)},
follow_redirects=True)
with self.app.test_request_context():
self.assertIsNone(ServiceUser.query.get((service_id, user_id)).service_email)
def test_update_email_preferences_invalid(self):
self.login_as('user')
user_id = self.get_user().id
email = UserEmail(user=self.get_user(), address='new@example.com', verified=True)
db.session.add(email)
service = Service(name='service', enable_email_preferences=True, limit_access=False)
db.session.add(service)
db.session.commit()
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(email.id), 'recovery_email': '2342'},
follow_redirects=True)
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': str(email.id), 'recovery_email': 'primary', f'service_{service_id}_email': '2342'},
follow_redirects=True)
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': 'primary', 'recovery_email': 'primary'},
follow_redirects=True)
with self.assertRaises(Exception):
r = self.client.post(path=url_for('selfservice.update_email_preferences'),
data={'primary_email': '2342', 'recovery_email': 'primary'},
follow_redirects=True)
def test_change_password(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'newpassword', 'password2': 'newpassword'},
follow_redirects=True)
dump('change_password', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(self.get_user().password.verify('newpassword'))
def test_change_password_invalid(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'shortpw', 'password2': 'shortpw'},
follow_redirects=True)
dump('change_password_invalid', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertFalse(user.password.verify('shortpw'))
self.assertTrue(user.password.verify('userpassword'))
# Regression test for #100 (login not possible if password contains character disallowed by SASLprep)
def test_change_password_samlprep_invalid(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'shortpw\n', 'password2': 'shortpw\n'},
follow_redirects=True)
dump('change_password_samlprep_invalid', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertFalse(user.password.verify('shortpw\n'))
self.assertTrue(user.password.verify('userpassword'))
def test_change_password_mismatch(self):
self.login_as('user')
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'newpassword1', 'password2': 'newpassword2'},
follow_redirects=True)
dump('change_password_mismatch', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertFalse(user.password.verify('newpassword1'))
self.assertFalse(user.password.verify('newpassword2'))
self.assertTrue(user.password.verify('userpassword'))
def test_leave_role(self):
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
role1 = Role(name='testrole1')
role2 = Role(name='testrole2')
db.session.add(role1)
db.session.add(role2)
self.get_user().roles = [role1, role2]
db.session.commit()
roleid = role1.id
self.login_as('user')
r = self.client.post(path=url_for('selfservice.leave_role', roleid=roleid), follow_redirects=True)
dump('leave_role', r)
self.assertEqual(r.status_code, 200)
_user = self.get_user()
self.assertEqual(len(_user.roles), 1)
self.assertEqual(list(_user.roles)[0].name, 'testrole2')
def test_forgot_password(self):
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'))
dump('forgot_password', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': user.loginname, 'mail': user.primary_email.address}, follow_redirects=True)
dump('forgot_password_submit', r)
self.assertEqual(r.status_code, 200)
token = PasswordToken.query.filter(PasswordToken.user == user).first()
self.assertIsNotNone(token)
self.assertIn(token.token, str(self.app.last_mail.get_content()))
def test_forgot_password_wrong_user(self):
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'))
self.assertEqual(r.status_code, 200)
user = self.get_user()
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': 'not_a_user', 'mail': user.primary_email.address}, follow_redirects=True)
dump('forgot_password_submit_wrong_user', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
def test_forgot_password_wrong_email(self):
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': user.loginname, 'mail': 'not_an_email@example.com'}, follow_redirects=True)
dump('forgot_password_submit_wrong_email', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
# Regression test for #31
def test_forgot_password_invalid_user(self):
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': '=', 'mail': 'test@example.com'}, follow_redirects=True)
dump('forgot_password_submit_invalid_user', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
def test_forgot_password_wrong_user(self):
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'))
self.assertEqual(r.status_code, 200)
user = self.get_user()
user.is_deactivated = True
db.session.commit()
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': user.loginname, 'mail': user.primary_email.address}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
def test_token_password(self):
user = self.get_user()
token = PasswordToken(user=user)
db.session.add(token)
db.session.commit()
self.assertFalse(token.expired)
r = self.client.get(path=url_for('selfservice.token_password', token_id=token.id, token=token.token), follow_redirects=True)
dump('token_password', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.token_password', token_id=token.id, token=token.token),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_submit', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(self.get_user().password.verify('newpassword'))
def test_token_password_emptydb(self):
user = self.get_user()
r = self.client.get(path=url_for('selfservice.token_password', token_id=1, token='A'*128), follow_redirects=True)
dump('token_password_emptydb', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token_id=1, token='A'*128),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_emptydb_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_token_password_invalid(self):
user = self.get_user()
token = PasswordToken(user=user)
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token_id=token.id, token='A'*128), follow_redirects=True)
dump('token_password_invalid', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token_id=token.id, token='A'*128),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_invalid_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_token_password_expired(self):
user = self.get_user()
token = PasswordToken(user=user, created=(datetime.datetime.utcnow() - datetime.timedelta(days=10)))
db.session.add(token)
db.session.commit()
self.assertTrue(token.expired)
r = self.client.get(path=url_for('selfservice.token_password', token_id=token.id, token=token.token), follow_redirects=True)
dump('token_password_invalid_expired', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token_id=token.id, token=token.token),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_invalid_expired_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Link invalid or expired', r.data)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_token_password_different_passwords(self):
user = self.get_user()
token = PasswordToken(user=user)
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token_id=token.id, token=token.token), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.token_password', token_id=token.id, token=token.token),
data={'password1': 'newpassword', 'password2': 'differentpassword'}, follow_redirects=True)
dump('token_password_different_passwords_submit', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(self.get_user().password.verify('userpassword'))
def get_fido2_test_cred(self):
try:
from uffd.fido2_compat import AttestedCredentialData
except ImportError:
self.skipTest('fido2 could not be imported')
# Example public key from webauthn spec 6.5.1.1
return AttestedCredentialData(bytes.fromhex('00000000000000000000000000000000'+'0040'+'053cbcc9d37a61d3bac87cdcc77ee326256def08ab15775d3a720332e4101d14fae95aeee3bc9698781812e143c0597dc6e180595683d501891e9dd030454c0a'+'A501020326200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c'))
class TestMfaViews(UffdTestCase):
def setUp(self):
super().setUp()
db.session.add(RecoveryCodeMethod(user=self.get_admin()))
db.session.add(TOTPMethod(user=self.get_admin(), name='Admin Phone'))
# We don't want to skip all tests only because fido2 is not installed!
#db.session.add(WebauthnMethod(user=get_testadmin(), cred=get_fido2_test_cred(self), name='Admin FIDO2 dongle'))
db.session.commit()
def add_recovery_codes(self, count=10):
user = self.get_user()
for _ in range(count):
db.session.add(RecoveryCodeMethod(user=user))
db.session.commit()
def add_totp(self):
db.session.add(TOTPMethod(user=self.get_user(), name='My phone'))
db.session.commit()
def add_webauthn(self):
db.session.add(WebauthnMethod(user=self.get_user(), cred=get_fido2_test_cred(self), name='My FIDO2 dongle'))
db.session.commit()
def test_setup_disabled(self):
self.login_as('user')
r = self.client.get(path=url_for('selfservice.setup_mfa'), follow_redirects=True)
dump('mfa_setup_disabled', r)
self.assertEqual(r.status_code, 200)
def test_setup_recovery_codes(self):
self.login_as('user')
self.add_recovery_codes()
r = self.client.get(path=url_for('selfservice.setup_mfa'), follow_redirects=True)
dump('mfa_setup_only_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_setup_enabled(self):
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
self.add_webauthn()
r = self.client.get(path=url_for('selfservice.setup_mfa'), follow_redirects=True)
dump('mfa_setup_enabled', r)
self.assertEqual(r.status_code, 200)
def test_setup_few_recovery_codes(self):
self.login_as('user')
self.add_totp()
self.add_recovery_codes(1)
r = self.client.get(path=url_for('selfservice.setup_mfa'), follow_redirects=True)
dump('mfa_setup_few_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_setup_no_recovery_codes(self):
self.login_as('user')
self.add_totp()
r = self.client.get(path=url_for('selfservice.setup_mfa'), follow_redirects=True)
dump('mfa_setup_no_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_disable(self):
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
admin_methods = len(MFAMethod.query.filter_by(user=self.get_admin()).all())
r = self.client.get(path=url_for('selfservice.disable_mfa'), follow_redirects=True)
dump('mfa_disable', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.disable_mfa_confirm'), follow_redirects=True)
dump('mfa_disable_submit', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(user=request.user).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(user=self.get_admin()).all()), admin_methods)
def test_disable_recovery_only(self):
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
self.login_as('user')
self.add_recovery_codes()
admin_methods = len(MFAMethod.query.filter_by(user=self.get_admin()).all())
self.assertNotEqual(len(MFAMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.get(path=url_for('selfservice.disable_mfa'), follow_redirects=True)
dump('mfa_disable_recovery_only', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.disable_mfa_confirm'), follow_redirects=True)
dump('mfa_disable_recovery_only_submit', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(user=request.user).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(user=self.get_admin()).all()), admin_methods)
def test_setup_recovery(self):
self.login_as('user')
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.post(path=url_for('selfservice.setup_mfa_recovery'), follow_redirects=True)
dump('mfa_setup_recovery', r)
self.assertEqual(r.status_code, 200)
methods = RecoveryCodeMethod.query.filter_by(user=request.user).all()
self.assertNotEqual(len(methods), 0)
r = self.client.post(path=url_for('selfservice.setup_mfa_recovery'), follow_redirects=True)
dump('mfa_setup_recovery_reset', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(id=methods[0].id).all()), 0)
self.assertNotEqual(len(methods), 0)
def test_setup_totp(self):
self.login_as('user')
self.add_recovery_codes()
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
dump('mfa_setup_totp', r)
self.assertEqual(r.status_code, 200)
self.assertNotEqual(len(session.get('mfa_totp_key', '')), 0)
def test_setup_totp_without_recovery(self):
self.login_as('user')
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
dump('mfa_setup_totp_without_recovery', r)
self.assertEqual(r.status_code, 200)
def test_setup_totp_finish(self):
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
r = self.client.post(path=url_for('selfservice.setup_mfa_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 1)
def test_setup_totp_finish_without_recovery(self):
self.login_as('user')
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
r = self.client.post(path=url_for('selfservice.setup_mfa_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish_without_recovery', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
def test_setup_totp_finish_wrong_code(self):
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
code = str(int(code[0])+1)[-1] + code[1:]
r = self.client.post(path=url_for('selfservice.setup_mfa_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish_wrong_code', r)
self.assertEqual(r.status_code, 200)
db_flush()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
def test_setup_totp_finish_empty_code(self):
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
r = self.client.get(path=url_for('selfservice.setup_mfa_totp', name='My TOTP Authenticator'), follow_redirects=True)
r = self.client.post(path=url_for('selfservice.setup_mfa_totp_finish', name='My TOTP Authenticator'), data={'code': ''}, follow_redirects=True)
dump('mfa_setup_totp_finish_empty_code', r)
self.assertEqual(r.status_code, 200)
db_flush()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 0)
def test_delete_totp(self):
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(request.user, name='test')
db.session.add(method)
db.session.commit()
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 2)
r = self.client.get(path=url_for('selfservice.delete_mfa_totp', id=method.id), follow_redirects=True)
dump('mfa_delete_totp', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(id=method.id).all()), 0)
self.assertEqual(len(TOTPMethod.query.filter_by(user=request.user).all()), 1)
# TODO: webauthn setup tests
from flask import url_for
from uffd.database import db
from uffd.models import Service, ServiceUser, OAuth2Client, APIClient, RemailerMode
from tests.utils import dump, UffdTestCase
class TestServices(UffdTestCase):
def setUpApp(self):
self.app.config['SERVICES'] = [
{
'title': 'Service Title',
'subtitle': 'Service Subtitle',
'description': 'Short description of the service as plain text',
'url': 'https://example.com/',
'logo_url': '/static/fairy-dust-color.png',
'required_group': 'users',
'permission_levels': [
{'name': 'Moderator', 'required_group': 'moderators'},
{'name': 'Admin', 'required_group': 'uffd_admin'},
],
'confidential': True,
'groups': [
{'name': 'Group "crew_crew"', 'required_group': 'users'},
{'name': 'Group "crew_logistik"', 'required_group': 'uffd_admin'},
],
'infos': [
{'title': 'Documentation', 'html': '<p>Some information about the service as html</p>', 'required_group': 'users'},
],
'links': [
{'title': 'Link to an external site', 'url': '#', 'required_group': 'users'},
],
},
{
'title': 'Minimal Service Title',
}
]
self.app.config['SERVICES_PUBLIC'] = True
def test_overview(self):
r = self.client.get(path=url_for('service.overview'))
dump('service_overview_guest', r)
self.assertEqual(r.status_code, 200)
self.assertNotIn(b'https://example.com/', r.data)
self.login_as('user')
r = self.client.get(path=url_for('service.overview'))
dump('service_overview_user', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'https://example.com/', r.data)
def test_overview_disabled(self):
self.app.config['SERVICES'] = []
# Should return login page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_disabled_guest', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'name="password"', r.data)
self.login_as('user')
# Should return access denied page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_disabled_user', r)
self.assertEqual(r.status_code, 403)
self.login_as('admin')
# Should return (empty) overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_disabled_admin', r)
self.assertEqual(r.status_code, 200)
def test_overview_nonpublic(self):
self.app.config['SERVICES_PUBLIC'] = False
# Should return login page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_nonpublic_guest', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'name="password"', r.data)
self.login_as('user')
# Should return overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_nonpublic_user', r)
self.assertEqual(r.status_code, 200)
self.login_as('admin')
# Should return overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_nonpublic_admin', r)
self.assertEqual(r.status_code, 200)
def test_overview_public(self):
# Should return overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_public_guest', r)
self.assertEqual(r.status_code, 200)
self.login_as('user')
# Should return overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_public_user', r)
self.assertEqual(r.status_code, 200)
self.login_as('admin')
# Should return overview page
r = self.client.get(path=url_for('service.overview'), follow_redirects=True)
dump('service_overview_public_admin', r)
self.assertEqual(r.status_code, 200)
class TestServiceAdminViews(UffdTestCase):
def setUpDB(self):
db.session.add(Service(
name='test1',
oauth2_clients=[OAuth2Client(client_id='test1_oauth2_client1', client_secret='test'), OAuth2Client(client_id='test1_oauth2_client2', client_secret='test')],
api_clients=[APIClient(auth_username='test1_api_client1', auth_password='test'), APIClient(auth_username='test1_api_client2', auth_password='test')],
))
db.session.add(Service(name='test2'))
db.session.add(Service(name='test3'))
db.session.commit()
self.service_id = Service.query.filter_by(name='test1').one().id
def test_index(self):
self.login_as('admin')
r = self.client.get(path=url_for('service.index'), follow_redirects=True)
dump('service_index', r)
self.assertEqual(r.status_code, 200)
def test_show(self):
self.login_as('admin')
r = self.client.get(path=url_for('service.show', id=self.service_id), follow_redirects=True)
dump('service_show', r)
self.assertEqual(r.status_code, 200)
def test_new(self):
self.login_as('admin')
r = self.client.get(path=url_for('service.show'), follow_redirects=True)
dump('service_new', r)
self.assertEqual(r.status_code, 200)
def test_new_submit(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit'),
follow_redirects=True,
data={
'name': 'new-service',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
dump('service_new_submit', r)
self.assertEqual(r.status_code, 200)
service = Service.query.filter_by(name='new-service').one_or_none()
self.assertIsNotNone(service)
self.assertEqual(service.limit_access, True)
self.assertEqual(service.access_group, None)
self.assertEqual(service.remailer_mode, RemailerMode.DISABLED)
self.assertEqual(service.enable_email_preferences, False)
def test_edit(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'new-name',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
dump('service_edit_submit', r)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.name, 'new-name')
self.assertEqual(service.limit_access, True)
self.assertEqual(service.access_group, None)
self.assertEqual(service.remailer_mode, RemailerMode.DISABLED)
self.assertEqual(service.enable_email_preferences, False)
self.assertEqual(service.hide_deactivated_users, False)
def test_edit_access_all(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': 'all',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.limit_access, False)
self.assertEqual(service.access_group, None)
def test_edit_access_group(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': str(self.get_users_group().id),
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.limit_access, True)
self.assertEqual(service.access_group, self.get_users_group())
def test_edit_hide_deactivated_users(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
'hide_deactivated_users': '1',
},
)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.hide_deactivated_users, True)
def test_edit_email_preferences(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
'enable_email_preferences': '1',
},
)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.enable_email_preferences, True)
def test_edit_remailer_mode(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'ENABLED_V2',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
self.assertEqual(r.status_code, 200)
service = Service.query.get(self.service_id)
self.assertEqual(service.remailer_mode, RemailerMode.ENABLED_V2)
def test_edit_remailer_overwrite_enable(self):
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': 'testuser, testadmin',
},
)
self.assertEqual(r.status_code, 200)
service_user1 = ServiceUser.query.get((self.service_id, self.get_user().id))
service_user2 = ServiceUser.query.get((self.service_id, self.get_admin().id))
self.assertEqual(service_user1.remailer_overwrite_mode, RemailerMode.ENABLED_V2)
self.assertEqual(service_user2.remailer_overwrite_mode, RemailerMode.ENABLED_V2)
self.assertEqual(
set(ServiceUser.query.filter(
ServiceUser.service_id == self.service_id,
ServiceUser.remailer_overwrite_mode != None
).all()),
{service_user1, service_user2}
)
def test_edit_remailer_overwrite_change(self):
service_user = ServiceUser.query.get((self.service_id, self.get_user().id))
service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V2
db.session.commit()
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V1',
'remailer-overwrite-users': ', testadmin',
},
)
self.assertEqual(r.status_code, 200)
service_user = ServiceUser.query.get((self.service_id, self.get_admin().id))
self.assertEqual(service_user.remailer_overwrite_mode, RemailerMode.ENABLED_V1)
self.assertEqual(
ServiceUser.query.filter(
ServiceUser.service_id == self.service_id,
ServiceUser.remailer_overwrite_mode != None
).all(),
[service_user]
)
def test_edit_remailer_overwrite_disable(self):
service_user = ServiceUser.query.get((self.service_id, self.get_user().id))
service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V2
db.session.commit()
self.login_as('admin')
r = self.client.post(
path=url_for('service.edit_submit', id=self.service_id),
follow_redirects=True,
data={
'name': 'test1',
'access-group': '',
'remailer-mode': 'DISABLED',
'remailer-overwrite-mode': 'ENABLED_V2',
'remailer-overwrite-users': '',
},
)
self.assertEqual(r.status_code, 200)
self.assertEqual(
ServiceUser.query.filter(
ServiceUser.service_id == self.service_id,
ServiceUser.remailer_overwrite_mode != None
).all(),
[]
)
def test_delete(self):
self.login_as('admin')
r = self.client.get(path=url_for('service.delete', id=self.service_id), follow_redirects=True)
dump('service_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(Service.query.get(self.service_id))
import time
import unittest
from flask import url_for, request
from uffd.database import db
from uffd.password_hash import PlaintextPasswordHash
from uffd.models import DeviceLoginConfirmation, Service, OAuth2Client, OAuth2DeviceLoginInitiation, User, RecoveryCodeMethod, TOTPMethod
from uffd.models.mfa import _hotp
from uffd.views.session import login_required
from tests.utils import dump, UffdTestCase, db_flush
class TestSession(UffdTestCase):
def setUpApp(self):
self.app.config['SESSION_LIFETIME_SECONDS'] = 2
@self.app.route('/test_login_required')
@login_required()
def test_login_required():
return 'SUCCESS ' + request.user.loginname, 200
@self.app.route('/test_group_required1')
@login_required(lambda: request.user.is_in_group('users'))
def test_group_required1():
return 'SUCCESS', 200
@self.app.route('/test_group_required2')
@login_required(lambda: request.user.is_in_group('notagroup'))
def test_group_required2():
return 'SUCCESS', 200
def setUp(self):
super().setUp()
self.assertIsNone(request.user)
def login(self):
self.login_as('user')
self.assertIsNotNone(request.user)
def assertLoggedIn(self):
self.assertEqual(self.client.get(path=url_for('test_login_required'), follow_redirects=True).data, b'SUCCESS testuser')
def assertLoggedOut(self):
self.assertNotIn(b'SUCCESS', self.client.get(path=url_for('test_login_required'), follow_redirects=True).data)
def test_login(self):
self.assertLoggedOut()
r = self.client.get(path=url_for('session.login'), follow_redirects=True)
dump('login', r)
self.assertEqual(r.status_code, 200)
r = self.login_as('user')
dump('login_post', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedIn()
def test_login_password_rehash(self):
self.get_user().password = PlaintextPasswordHash.from_password('userpassword')
db.session.commit()
self.assertIsInstance(self.get_user().password, PlaintextPasswordHash)
db_flush()
r = self.login_as('user')
self.assertEqual(r.status_code, 200)
self.assertLoggedIn()
self.assertIsInstance(self.get_user().password, User.password.method_cls)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_titlecase_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': self.get_user().loginname.title(), 'password': 'userpassword'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertLoggedIn()
def test_redirect(self):
r = self.login_as('user', ref=url_for('test_login_required'))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, b'SUCCESS testuser')
def test_wrong_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': self.get_user().loginname, 'password': 'wrongpassword'},
follow_redirects=True)
dump('login_wrong_password', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_empty_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': self.get_user().loginname, 'password': ''}, follow_redirects=True)
dump('login_empty_password', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
# Regression test for #100 (uncatched LDAPSASLPrepError)
def test_saslprep_invalid_password(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'wrongpassword\n'}, follow_redirects=True)
dump('login_saslprep_invalid_password', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_wrong_user(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'nouser', 'password': 'userpassword'},
follow_redirects=True)
dump('login_wrong_user', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_empty_user(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': '', 'password': 'userpassword'}, follow_redirects=True)
dump('login_empty_user', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_no_access(self):
r = self.client.post(path=url_for('session.login'),
data={'loginname': 'testservice', 'password': 'servicepassword'}, follow_redirects=True)
dump('login_no_access', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.login_as('user')
dump('login_deactivated', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_deactivated_after_login(self):
self.login_as('user')
self.get_user().is_deactivated = True
db.session.commit()
self.assertLoggedOut()
def test_group_required(self):
self.login()
self.assertEqual(self.client.get(path=url_for('test_group_required1'),
follow_redirects=True).data, b'SUCCESS')
self.assertNotEqual(self.client.get(path=url_for('test_group_required2'),
follow_redirects=True).data, b'SUCCESS')
def test_logout(self):
self.login()
r = self.client.get(path=url_for('session.logout'), follow_redirects=True)
dump('logout', r)
self.assertEqual(r.status_code, 200)
self.assertLoggedOut()
def test_timeout(self):
self.login()
time.sleep(3)
self.assertLoggedOut()
def test_ratelimit(self):
for i in range(20):
self.client.post(path=url_for('session.login'),
data={'loginname': self.get_user().loginname,
'password': 'wrongpassword_%i'%i}, follow_redirects=True)
r = self.login_as('user')
dump('login_ratelimit', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_deviceauth(self):
oauth2_client = OAuth2Client(service=Service(name='test', limit_access=False), client_id='test', client_secret='testsecret', redirect_uris=['http://localhost:5009/callback', 'http://localhost:5009/callback2'])
initiation = OAuth2DeviceLoginInitiation(client=oauth2_client)
db.session.add(initiation)
db.session.commit()
code = initiation.code
self.login()
r = self.client.get(path=url_for('session.deviceauth'), follow_redirects=True)
dump('deviceauth', r)
self.assertEqual(r.status_code, 200)
r = self.client.get(path=url_for('session.deviceauth', **{'initiation-code': code}), follow_redirects=True)
dump('deviceauth_check', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'test', r.data)
r = self.client.post(path=url_for('session.deviceauth_submit'), data={'initiation-code': code}, follow_redirects=True)
dump('deviceauth_submit', r)
self.assertEqual(r.status_code, 200)
initiation = OAuth2DeviceLoginInitiation.query.filter_by(code=code).one()
self.assertEqual(len(initiation.confirmations), 1)
self.assertEqual(initiation.confirmations[0].session.user.loginname, 'testuser')
self.assertIn(initiation.confirmations[0].code.encode(), r.data)
r = self.client.get(path=url_for('session.deviceauth_finish'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(DeviceLoginConfirmation.query.all(), [])
class TestMfaViews(UffdTestCase):
def add_recovery_codes(self, count=10):
user = self.get_user()
for _ in range(count):
db.session.add(RecoveryCodeMethod(user=user))
db.session.commit()
def add_totp(self):
db.session.add(TOTPMethod(user=self.get_user(), name='My phone'))
db.session.commit()
def test_auth_integration(self):
self.add_recovery_codes()
self.add_totp()
db.session.commit()
self.assertIsNone(request.user)
r = self.login_as('user')
dump('mfa_auth_redirected', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'/mfa/auth', r.data)
self.assertIsNone(request.user)
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
dump('mfa_auth', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_disabled(self):
self.assertIsNone(request.user)
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth', ref='/redirecttarget'), follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
def test_auth_recovery_only(self):
self.add_recovery_codes()
self.assertIsNone(request.user)
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth', ref='/redirecttarget'), follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
def test_auth_recovery_code(self):
self.add_recovery_codes()
self.add_totp()
method = RecoveryCodeMethod(user=self.get_user())
db.session.add(method)
db.session.commit()
method_id = method.id
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
dump('mfa_auth_recovery_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('session.mfa_auth_finish', ref='/redirecttarget'), data={'code': method.code_value})
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(id=method_id).all()), 0)
def test_auth_totp_code(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
dump('mfa_auth_totp_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_totp_code_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIsNotNone(request.user)
def test_auth_totp_code_reuse(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': code}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertIsNotNone(request.user)
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': code}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_empty_code(self):
self.add_recovery_codes()
self.add_totp()
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': ''}, follow_redirects=True)
dump('mfa_auth_empty_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_invalid_code(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('session.mfa_auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
code = str(int(code[0])+1)[-1] + code[1:]
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_invalid_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_ratelimit(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
inv_code = str(int(code[0])+1)[-1] + code[1:]
for i in range(20):
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': inv_code}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('session.mfa_auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_ratelimit', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
# TODO: webauthn auth tests
import unittest
import datetime
import time
from flask import url_for, session
from flask import url_for, request
# These imports are required, because otherwise we get circular imports?!
from uffd import user
from uffd.ldap import ldap
from uffd.database import db
from uffd.models import User, Signup, Role, RoleGroup, FeatureFlag
from uffd import create_app, db
from uffd.signup.models import Signup
from uffd.user.models import User
from uffd.session.views import get_current_user, is_valid_session, login_get_user
from utils import dump, UffdTestCase, db_flush
from tests.utils import dump, UffdTestCase, db_flush
def refetch_signup(signup):
db.session.add(signup)
db.session.commit()
token = signup.token
db_flush()
return Signup.query.get(token)
# We assume in all tests that Signup.validate and Signup.check_password do
# not alter any state
class TestSignupModel(UffdTestCase):
def assert_validate_valid(self, signup):
valid, msg = signup.validate()
self.assertTrue(valid)
self.assertIsInstance(msg, str)
def assert_validate_invalid(self, signup):
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
self.assertNotEqual(msg, '')
def assert_finish_success(self, signup, password):
self.assertIsNone(signup.user)
user, msg = signup.finish(password)
ldap.session.commit()
self.assertIsNotNone(user)
self.assertIsInstance(msg, str)
self.assertIsNotNone(signup.user)
def assert_finish_failure(self, signup, password):
prev_dn = signup.user_dn
user, msg = signup.finish(password)
self.assertIsNone(user)
self.assertIsInstance(msg, str)
self.assertNotEqual(msg, '')
self.assertEqual(signup.user_dn, prev_dn)
def test_password(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com')
self.assertFalse(signup.check_password('notsecret'))
self.assertFalse(signup.check_password(''))
self.assertFalse(signup.check_password('wrongpassword'))
signup.password = 'notsecret'
self.assertTrue(signup.check_password('notsecret'))
self.assertFalse(signup.check_password('wrongpassword'))
def test_expired(self):
# TODO: Find a better way to test this!
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(signup.expired)
signup.created = created=datetime.datetime.now() - datetime.timedelta(hours=49)
self.assertTrue(signup.expired)
def test_completed(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(signup.completed)
signup.finish('notsecret')
ldap.session.commit()
self.assertTrue(signup.completed)
signup = refetch_signup(signup)
self.assertTrue(signup.completed)
def test_validate(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_validate_valid(signup)
self.assert_validate_valid(refetch_signup(signup))
def test_validate_completed(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_finish_success(signup, 'notsecret')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_expired(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com',
password='notsecret', created=datetime.datetime.now()-datetime.timedelta(hours=49))
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_loginname(self):
signup = Signup(loginname='', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_displayname(self):
signup = Signup(loginname='newuser', displayname='', mail='test@example.com', password='notsecret')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_mail(self):
signup = Signup(loginname='newuser', displayname='New User', mail='', password='notsecret')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_password(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_validate_exists(self):
signup = Signup(loginname='testuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_validate_invalid(signup)
self.assert_validate_invalid(refetch_signup(signup))
def test_finish(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
if self.use_openldap:
self.assertIsNone(login_get_user('newuser', 'notsecret'))
self.assert_finish_success(signup, 'notsecret')
user = User.query.get('uid=newuser,ou=users,dc=example,dc=com')
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'test@example.com')
if self.use_openldap:
self.assertIsNotNone(login_get_user('newuser', 'notsecret'))
def test_finish_completed(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_finish_success(signup, 'notsecret')
self.assert_finish_failure(refetch_signup(signup), 'notsecret')
def test_finish_expired(self):
# TODO: Find a better way to test this!
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com',
password='notsecret', created=datetime.datetime.now()-datetime.timedelta(hours=49))
self.assert_finish_failure(signup, 'notsecret')
self.assert_finish_failure(refetch_signup(signup), 'notsecret')
def test_finish_wrongpassword(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com')
self.assert_finish_failure(signup, '')
self.assert_finish_failure(signup, 'wrongpassword')
signup = refetch_signup(signup)
self.assert_finish_failure(signup, '')
self.assert_finish_failure(signup, 'wrongpassword')
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_finish_failure(signup, 'wrongpassword')
self.assert_finish_failure(refetch_signup(signup), 'wrongpassword')
def test_finish_ldaperror(self):
signup = Signup(loginname='testuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assert_finish_failure(signup, 'notsecret')
self.assert_finish_failure(refetch_signup(signup), 'notsecret')
def test_duplicate(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test1@example.com', password='notsecret')
self.assert_validate_valid(signup)
db.session.add(signup)
db.session.commit()
signup1_token = signup.token
signup = Signup(loginname='newuser', displayname='New User', mail='test2@example.com', password='notsecret')
self.assert_validate_valid(signup)
db.session.add(signup)
db.session.commit()
signup2_token = signup.token
db_flush()
signup = Signup.query.get(signup2_token)
self.assert_finish_success(signup, 'notsecret')
db.session.commit()
db_flush()
signup = Signup.query.get(signup1_token)
self.assert_finish_failure(signup, 'notsecret')
user = User.query.get('uid=newuser,ou=users,dc=example,dc=com')
self.assertEqual(user.mail, 'test2@example.com')
class TestSignupModelOL(TestSignupModel):
use_openldap = True
id = signup.id
db.session.expunge(signup)
return Signup.query.get(id)
class TestSignupViews(UffdTestCase):
def setUpApp(self):
......@@ -195,7 +25,7 @@ class TestSignupViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
self.assertEqual(Signup.query.filter_by(loginname='newuser').all(), [])
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_submit', r)
self.assertEqual(r.status_code, 200)
......@@ -205,9 +35,9 @@ class TestSignupViews(UffdTestCase):
signup = signups[0]
self.assertEqual(signup.loginname, 'newuser')
self.assertEqual(signup.displayname, 'New User')
self.assertEqual(signup.mail, 'test@example.com')
self.assertEqual(signup.mail, 'new@example.com')
self.assertIn(signup.token, str(self.app.last_mail.get_content()))
self.assertTrue(signup.check_password('notsecret'))
self.assertTrue(signup.password.verify('notsecret'))
self.assertTrue(signup.validate()[0])
def test_signup_disabled(self):
......@@ -217,7 +47,7 @@ class TestSignupViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
self.assertEqual(Signup.query.filter_by(loginname='newuser').all(), [])
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_submit_disabled', r)
self.assertEqual(r.status_code, 200)
......@@ -226,7 +56,7 @@ class TestSignupViews(UffdTestCase):
def test_signup_wrongpassword(self):
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notthesame'})
dump('test_signup_wrongpassword', r)
self.assertEqual(r.status_code, 200)
......@@ -234,7 +64,7 @@ class TestSignupViews(UffdTestCase):
def test_signup_invalid(self):
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': '', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': '', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_invalid', r)
self.assertEqual(r.status_code, 200)
......@@ -243,7 +73,7 @@ class TestSignupViews(UffdTestCase):
def test_signup_mailerror(self):
self.app.config['MAIL_SKIP_SEND'] = 'fail'
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_mailerror', r)
self.assertEqual(r.status_code, 200)
......@@ -262,7 +92,7 @@ class TestSignupViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_hostlimit', r)
self.assertEqual(r.status_code, 200)
......@@ -272,12 +102,12 @@ class TestSignupViews(UffdTestCase):
def test_signup_maillimit(self):
for i in range(3):
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('signup.signup_submit'), follow_redirects=True,
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'new@example.com',
'password1': 'notsecret', 'password2': 'notsecret'})
dump('test_signup_maillimit', r)
self.assertEqual(r.status_code, 200)
......@@ -323,122 +153,138 @@ class TestSignupViews(UffdTestCase):
self.assertEqual(r.json['status'], 'ratelimited')
def test_confirm(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
self.assertFalse(signup.completed)
if self.use_openldap:
self.assertIsNone(login_get_user('newuser', 'notsecret'))
r = self.client.get(path=url_for('signup.signup_confirm', token=signup.token), follow_redirects=True)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
r = self.client.get(path=url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token), follow_redirects=True)
dump('test_signup_confirm', r)
self.assertEqual(r.status_code, 200)
signup = refetch_signup(signup)
self.assertFalse(signup.completed)
if self.use_openldap:
self.assertIsNone(login_get_user('newuser', 'notsecret'))
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_submit', r)
self.assertEqual(r.status_code, 200)
signup = refetch_signup(signup)
self.assertTrue(signup.completed)
self.assertEqual(signup.user.loginname, 'newuser')
self.assertEqual(signup.user.displayname, 'New User')
self.assertEqual(signup.user.mail, 'test@example.com')
if self.use_openldap:
self.assertIsNotNone(login_get_user('newuser', 'notsecret'))
self.assertTrue(is_valid_session())
self.assertEqual(get_current_user().loginname, 'newuser')
self.assertEqual(signup.user.primary_email.address, 'new@example.com')
self.assertTrue(User.query.filter_by(loginname='newuser').one_or_none().password.verify('notsecret'))
def test_confirm_loggedin(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
baserole = Role(name='baserole', is_default=True)
db.session.add(baserole)
baserole.groups[self.get_access_group()] = RoleGroup()
db.session.commit()
self.login_as('user')
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
self.client.post(path=url_for('session.login'),
data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
self.assertFalse(signup.completed)
self.assertTrue(is_valid_session())
self.assertEqual(get_current_user().loginname, 'testuser')
r = self.client.get(path=url_for('signup.signup_confirm', token=signup.token), follow_redirects=True)
self.assertIsNotNone(request.user)
self.assertEqual(request.user.loginname, self.get_user().loginname)
r = self.client.get(path=url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
self.assertEqual(r.status_code, 200)
signup = refetch_signup(signup)
self.assertTrue(signup.completed)
self.assertTrue(is_valid_session())
self.assertEqual(get_current_user().loginname, 'newuser')
self.assertIsNotNone(request.user)
self.assertEqual(request.user.loginname, 'newuser')
def test_confirm_notfound(self):
r = self.client.get(path=url_for('signup.signup_confirm', token='notasignuptoken'), follow_redirects=True)
r = self.client.get(path=url_for('signup.signup_confirm', signup_id=1, token='notasignuptoken'), follow_redirects=True)
dump('test_signup_confirm_notfound', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token='notasignuptoken'), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=1, token='notasignuptoken'), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_submit_notfound', r)
self.assertEqual(r.status_code, 200)
def test_confirm_expired(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
signup.created = datetime.datetime.now() - datetime.timedelta(hours=49)
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup.created = datetime.datetime.utcnow() - datetime.timedelta(hours=49)
signup = refetch_signup(signup)
r = self.client.get(path=url_for('signup.signup_confirm', token=signup.token), follow_redirects=True)
r = self.client.get(path=url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token), follow_redirects=True)
dump('test_signup_confirm_expired', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_submit_expired', r)
self.assertEqual(r.status_code, 200)
def test_confirm_completed(self):
signup = Signup(loginname='testuser', displayname='New User', mail='test@example.com', password='notsecret')
signup.user = User.query.get('uid=testuser,ou=users,dc=example,dc=com')
signup = Signup(loginname=self.get_user().loginname, displayname='New User', mail='new@example.com', password='notsecret')
signup.user = self.get_user()
signup = refetch_signup(signup)
self.assertTrue(signup.completed)
r = self.client.get(path=url_for('signup.signup_confirm', token=signup.token), follow_redirects=True)
r = self.client.get(path=url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token), follow_redirects=True)
dump('test_signup_confirm_completed', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_submit_completed', r)
self.assertEqual(r.status_code, 200)
def test_confirm_wrongpassword(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'wrongpassword'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'wrongpassword'})
dump('test_signup_confirm_wrongpassword', r)
signup = refetch_signup(signup)
self.assertEqual(r.status_code, 200)
self.assertFalse(signup.completed)
def test_confirm_error(self):
# finish returns None and error message (here: because the user already exists)
signup = Signup(loginname='testuser', displayname='New User', mail='test@example.com', password='notsecret')
signup = Signup(loginname=self.get_user().loginname, displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_error', r)
signup = refetch_signup(signup)
self.assertEqual(r.status_code, 200)
self.assertFalse(signup.completed)
def test_confirm_error_email_uniqueness(self):
FeatureFlag.unique_email_addresses.enable()
db.session.commit()
# finish returns None and error message (here: because the email address already exists)
# This case is interesting, because the error also invalidates the ORM session
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
db.session.add(signup)
db.session.commit()
signup_id = signup.id
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_error_email_uniqueness', r)
self.assertEqual(r.status_code, 200)
signup = Signup.query.get(signup_id)
self.assertFalse(signup.completed)
def test_confirm_hostlimit(self):
for i in range(20):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'wrongpassword%d'%i})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'wrongpassword%d'%i})
self.assertEqual(r.status_code, 200)
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
self.assertFalse(signup.completed)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_hostlimit', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(signup.completed)
def test_confirm_confirmlimit(self):
signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
signup = Signup(loginname='newuser', displayname='New User', mail='new@example.com', password='notsecret')
signup = refetch_signup(signup)
self.assertFalse(signup.completed)
for i in range(5):
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'wrongpassword%d'%i})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'wrongpassword%d'%i})
self.assertEqual(r.status_code, 200)
self.assertFalse(signup.completed)
r = self.client.post(path=url_for('signup.signup_confirm_submit', token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
r = self.client.post(path=url_for('signup.signup_confirm_submit', signup_id=signup.id, token=signup.token), follow_redirects=True, data={'password': 'notsecret'})
dump('test_signup_confirm_confirmlimit', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(signup.completed)
class TestSignupViewsOL(TestSignupViews):
use_openldap = True
from flask import url_for, request
from uffd.database import db
from uffd.models import User, UserEmail, Group, Role, Service, ServiceUser, FeatureFlag, MFAMethod, RecoveryCodeMethod, TOTPMethod
from tests.utils import dump, UffdTestCase
class TestUserViews(UffdTestCase):
def setUp(self):
super().setUp()
self.app.last_mail = None
self.login_as('admin')
def test_index(self):
r = self.client.get(path=url_for('user.index'), follow_redirects=True)
dump('user_index', r)
self.assertEqual(r.status_code, 200)
def test_new(self):
db.session.add(Role(name='base', is_default=True))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
db.session.commit()
role1_id = role1.id
r = self.client.get(path=url_for('user.show'), follow_redirects=True)
dump('user_new', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'newuser', 'email': 'newuser@example.com', 'displayname': 'New User',
f'role-{role1_id}': '1', 'password': 'newpassword'}, follow_redirects=True)
dump('user_new_submit', r)
self.assertEqual(r.status_code, 200)
user_ = User.query.filter_by(loginname='newuser').one_or_none()
roles = sorted([r.name for r in user_.roles_effective])
self.assertIsNotNone(user_)
self.assertFalse(user_.is_service_user)
self.assertEqual(user_.loginname, 'newuser')
self.assertEqual(user_.displayname, 'New User')
self.assertEqual(user_.primary_email.address, 'newuser@example.com')
self.assertFalse(user_.password)
self.assertGreaterEqual(user_.unix_uid, self.app.config['USER_MIN_UID'])
self.assertLessEqual(user_.unix_uid, self.app.config['USER_MAX_UID'])
role1 = Role(name='role1')
self.assertEqual(roles, ['base', 'role1'])
self.assertIsNotNone(self.app.last_mail)
def test_new_service(self):
db.session.add(Role(name='base', is_default=True))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
db.session.commit()
role1_id = role1.id
r = self.client.get(path=url_for('user.show'), follow_redirects=True)
dump('user_new_service', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'newuser', 'email': 'newuser@example.com', 'displayname': 'New User',
f'role-{role1_id}': '1', 'password': 'newpassword', 'serviceaccount': '1'}, follow_redirects=True)
dump('user_new_submit', r)
self.assertEqual(r.status_code, 200)
user = User.query.filter_by(loginname='newuser').one_or_none()
roles = sorted([r.name for r in user.roles])
self.assertIsNotNone(user)
self.assertTrue(user.is_service_user)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.primary_email.address, 'newuser@example.com')
self.assertTrue(user.unix_uid)
self.assertFalse(user.password)
role1 = Role(name='role1')
self.assertEqual(roles, ['role1'])
self.assertIsNone(self.app.last_mail)
def test_new_invalid_loginname(self):
r = self.client.post(path=url_for('user.create'),
data={'loginname': '!newuser', 'email': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_invalid_loginname', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
def test_new_empty_loginname(self):
r = self.client.post(path=url_for('user.create'),
data={'loginname': '', 'email': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_empty_loginname', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
def test_new_conflicting_loginname(self):
self.assertEqual(User.query.filter_by(loginname='testuser').count(), 1)
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'testuser', 'email': 'newuser@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_conflicting_loginname', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(User.query.filter_by(loginname='testuser').count(), 1)
def test_new_empty_email(self):
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'newuser', 'email': '', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_empty_email', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
def test_new_conflicting_email(self):
FeatureFlag.unique_email_addresses.enable()
db.session.commit()
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'newuser', 'email': 'test@example.com', 'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_conflicting_email', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
def test_new_invalid_display_name(self):
r = self.client.post(path=url_for('user.create'),
data={'loginname': 'newuser', 'email': 'newuser@example.com', 'displayname': 'A'*200,
'password': 'newpassword'}, follow_redirects=True)
dump('user_new_invalid_display_name', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(User.query.filter_by(loginname='newuser').one_or_none())
def test_update(self):
user_unupdated = self.get_user()
email_id = str(user_unupdated.primary_email.id)
db.session.add(Role(name='base', is_default=True))
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
role2.members.append(user_unupdated)
db.session.commit()
role1_id = role1.id
r = self.client.get(path=url_for('user.show', id=user_unupdated.id), follow_redirects=True)
dump('user_update', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', id=user_unupdated.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1', 'primary_email': email_id, 'recovery_email': 'primary',
'displayname': 'New User', f'role-{role1_id}': '1', 'password': ''},
follow_redirects=True)
dump('user_update_submit', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_user()
roles = sorted([r.name for r in user_updated.roles_effective])
self.assertEqual(user_updated.displayname, 'New User')
self.assertEqual(user_updated.primary_email.address, 'test@example.com')
self.assertEqual(user_updated.unix_uid, user_unupdated.unix_uid)
self.assertEqual(user_updated.loginname, user_unupdated.loginname)
self.assertTrue(user_updated.password.verify('userpassword'))
self.assertEqual(roles, ['base', 'role1'])
def test_update_password(self):
user_unupdated = self.get_user()
email_id = str(user_unupdated.primary_email.id)
r = self.client.get(path=url_for('user.show', id=user_unupdated.id), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', id=user_unupdated.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1', 'primary_email': email_id, 'recovery_email': 'primary',
'displayname': 'New User',
'password': 'newpassword'}, follow_redirects=True)
dump('user_update_password', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_user()
self.assertEqual(user_updated.displayname, 'New User')
self.assertEqual(user_updated.primary_email.address, 'test@example.com')
self.assertEqual(user_updated.unix_uid, user_unupdated.unix_uid)
self.assertEqual(user_updated.loginname, user_unupdated.loginname)
self.assertTrue(user_updated.password.verify('newpassword'))
self.assertFalse(user_updated.password.verify('userpassword'))
def test_update_invalid_password(self):
user_unupdated = self.get_user()
email_id = str(user_unupdated.primary_email.id)
r = self.client.get(path=url_for('user.show', id=user_unupdated.id), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', id=user_unupdated.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1', 'primary_email': email_id, 'recovery_email': 'primary',
'displayname': 'New User',
'password': 'A'}, follow_redirects=True)
dump('user_update_invalid_password', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_user()
self.assertFalse(user_updated.password.verify('A'))
self.assertTrue(user_updated.password.verify('userpassword'))
self.assertEqual(user_updated.displayname, user_unupdated.displayname)
self.assertEqual(user_updated.primary_email.address, user_unupdated.primary_email.address)
self.assertEqual(user_updated.loginname, user_unupdated.loginname)
# Regression test for #100 (login not possible if password contains character disallowed by SASLprep)
def test_update_saslprep_invalid_password(self):
user_unupdated = self.get_user()
email_id = str(user_unupdated.primary_email.id)
r = self.client.get(path=url_for('user.show', id=user_unupdated.id), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', id=user_unupdated.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1', 'primary_email': email_id, 'recovery_email': 'primary',
'displayname': 'New User',
'password': 'newpassword\n'}, follow_redirects=True)
dump('user_update_invalid_password', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_user()
self.assertFalse(user_updated.password.verify('newpassword\n'))
self.assertTrue(user_updated.password.verify('userpassword'))
self.assertEqual(user_updated.displayname, user_unupdated.displayname)
self.assertEqual(user_updated.primary_email.address, user_unupdated.primary_email.address)
self.assertEqual(user_updated.loginname, user_unupdated.loginname)
def test_update_email(self):
user = self.get_user()
email = UserEmail(user=user, address='foo@example.com')
service1 = Service(name='service1', enable_email_preferences=True, limit_access=False)
service2 = Service(name='service2', enable_email_preferences=True, limit_access=False)
db.session.add_all([service1, service2])
db.session.commit()
email1_id = user.primary_email.id
email2_id = email.id
service1_id = service1.id
service2_id = service2.id
r = self.client.post(path=url_for('user.update', id=user.id),
data={'loginname': 'testuser',
f'email-{email1_id}-present': '1',
f'email-{email2_id}-present': '1',
f'email-{email2_id}-verified': '1',
f'newemail-1-address': 'new1@example.com',
f'newemail-2-address': 'new2@example.com', f'newemail-2-verified': '1',
'primary_email': email2_id, 'recovery_email': email1_id,
f'service_{service1_id}_email': 'primary',
f'service_{service2_id}_email': email2_id,
'displayname': 'Test User', 'password': ''},
follow_redirects=True)
dump('user_update_email', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertEqual(user.primary_email.id, email2_id)
self.assertEqual(user.recovery_email.id, email1_id)
self.assertEqual(ServiceUser.query.get((service1.id, user.id)).service_email, None)
self.assertEqual(ServiceUser.query.get((service2.id, user.id)).service_email.id, email2_id)
self.assertEqual(
{email.address: email.verified for email in user.all_emails},
{
'test@example.com': True,
'foo@example.com': True,
'new1@example.com': False,
'new2@example.com': True,
}
)
def test_update_email_conflict(self):
user = self.get_user()
user_id = user.id
email_id = user.primary_email.id
email_address = user.primary_email.address
r = self.client.post(path=url_for('user.update', id=user.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1',
f'newemail-1-address': user.primary_email.address},
follow_redirects=True)
dump('user_update_email_conflict', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(UserEmail.query.filter_by(user_id=user_id).count(), 1)
def test_update_email_strict_uniqueness(self):
FeatureFlag.unique_email_addresses.enable()
db.session.commit()
user = self.get_user()
email = UserEmail(user=user, address='foo@example.com')
service1 = Service(name='service1', enable_email_preferences=True, limit_access=False)
service2 = Service(name='service2', enable_email_preferences=True, limit_access=False)
db.session.add_all([service1, service2])
db.session.commit()
email1_id = user.primary_email.id
email2_id = email.id
service1_id = service1.id
service2_id = service2.id
r = self.client.post(path=url_for('user.update', id=user.id),
data={'loginname': 'testuser',
f'email-{email1_id}-present': '1',
f'email-{email2_id}-present': '1',
f'email-{email2_id}-verified': '1',
f'newemail-1-address': 'new1@example.com',
f'newemail-2-address': 'new2@example.com', f'newemail-2-verified': '1',
'primary_email': email2_id, 'recovery_email': email1_id,
f'service_{service1_id}_email': 'primary',
f'service_{service2_id}_email': email2_id,
'displayname': 'Test User', 'password': ''},
follow_redirects=True)
dump('user_update_email_strict_uniqueness', r)
self.assertEqual(r.status_code, 200)
user = self.get_user()
self.assertEqual(user.primary_email.id, email2_id)
self.assertEqual(user.recovery_email.id, email1_id)
self.assertEqual(ServiceUser.query.get((service1.id, user.id)).service_email, None)
self.assertEqual(ServiceUser.query.get((service2.id, user.id)).service_email.id, email2_id)
self.assertEqual(
{email.address: email.verified for email in user.all_emails},
{
'test@example.com': True,
'foo@example.com': True,
'new1@example.com': False,
'new2@example.com': True,
}
)
def test_update_email_strict_uniqueness_conflict(self):
FeatureFlag.unique_email_addresses.enable()
db.session.commit()
user = self.get_user()
user_id = user.id
email_id = user.primary_email.id
email_address = user.primary_email.address
r = self.client.post(path=url_for('user.update', id=user.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1',
f'newemail-1-address': user.primary_email.address},
follow_redirects=True)
dump('user_update_email_strict_uniqueness_conflict', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(UserEmail.query.filter_by(user_id=user_id).count(), 1)
def test_update_invalid_display_name(self):
user_unupdated = self.get_user()
email_id = str(user_unupdated.primary_email.id)
r = self.client.get(path=url_for('user.show', id=user_unupdated.id), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('user.update', id=user_unupdated.id),
data={'loginname': 'testuser',
f'email-{email_id}-present': '1', 'primary_email': email_id, 'recovery_email': 'primary',
'displayname': 'A'*200,
'password': 'newpassword'}, follow_redirects=True)
dump('user_update_invalid_display_name', r)
self.assertEqual(r.status_code, 200)
user_updated = self.get_user()
self.assertEqual(user_updated.displayname, user_unupdated.displayname)
self.assertEqual(user_updated.primary_email.address, user_unupdated.primary_email.address)
self.assertEqual(user_updated.loginname, user_unupdated.loginname)
self.assertFalse(user_updated.password.verify('newpassword'))
self.assertTrue(user_updated.password.verify('userpassword'))
def test_show(self):
r = self.client.get(path=url_for('user.show', id=self.get_user().id), follow_redirects=True)
dump('user_show', r)
self.assertEqual(r.status_code, 200)
def test_show_self(self):
r = self.client.get(path=url_for('user.show', id=self.get_admin().id), follow_redirects=True)
dump('user_show_self', r)
self.assertEqual(r.status_code, 200)
def test_delete(self):
r = self.client.get(path=url_for('user.delete', id=self.get_user().id), follow_redirects=True)
dump('user_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(self.get_user())
def test_deactivate(self):
r = self.client.get(path=url_for('user.deactivate', id=self.get_user().id), follow_redirects=True)
dump('user_deactivate', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(self.get_user().is_deactivated)
def test_activate(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('user.activate', id=self.get_user().id), follow_redirects=True)
dump('user_activate', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(self.get_user().is_deactivated)
def test_disable_mfa(self):
db.session.add(RecoveryCodeMethod(user=self.get_admin()))
user = self.get_user()
for _ in range(10):
db.session.add(RecoveryCodeMethod(user=user))
db.session.add(TOTPMethod(user=self.get_user(), name='My phone'))
db.session.commit()
self.login_as('admin')
admin_methods = len(MFAMethod.query.filter_by(user=self.get_admin()).all())
r = self.client.get(path=url_for('user.disable_mfa', id=self.get_user().id), follow_redirects=True)
dump('user_disable_mfa', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(user=self.get_user()).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(user=self.get_admin()).all()), admin_methods)
def test_csvimport(self):
role1 = Role(name='role1')
db.session.add(role1)
role2 = Role(name='role2')
db.session.add(role2)
db.session.commit()
data = f'''\
newuser1,newuser1@example.com,
newuser2,newuser2@example.com,{role1.id}
newuser3,newuser3@example.com,{role1.id};{role2.id}
newuser4,newuser4@example.com,9999
newuser5,newuser5@example.com,notanumber
newuser6,newuser6@example.com,{role1.id};{role2.id};
newuser7,invalidmail,
newuser8,,
,newuser9@example.com,
,,
,,,
newuser10,newuser10@example.com,
newuser11,newuser11@example.com, {role1.id};{role2.id}
newuser12,newuser12@example.com,{role1.id};{role1.id}
<invalid tag-like thingy>'''
r = self.client.post(path=url_for('user.csvimport'), data={'csv': data}, follow_redirects=True)
dump('user_csvimport', r)
self.assertEqual(r.status_code, 200)
user = User.query.filter_by(loginname='newuser1').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser1')
self.assertEqual(user.displayname, 'newuser1')
self.assertEqual(user.primary_email.address, 'newuser1@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, [])
user = User.query.filter_by(loginname='newuser2').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser2')
self.assertEqual(user.displayname, 'newuser2')
self.assertEqual(user.primary_email.address, 'newuser2@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, ['role1'])
user = User.query.filter_by(loginname='newuser3').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser3')
self.assertEqual(user.displayname, 'newuser3')
self.assertEqual(user.primary_email.address, 'newuser3@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, ['role1', 'role2'])
user = User.query.filter_by(loginname='newuser4').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser4')
self.assertEqual(user.displayname, 'newuser4')
self.assertEqual(user.primary_email.address, 'newuser4@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, [])
user = User.query.filter_by(loginname='newuser5').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser5')
self.assertEqual(user.displayname, 'newuser5')
self.assertEqual(user.primary_email.address, 'newuser5@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, [])
user = User.query.filter_by(loginname='newuser6').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser6')
self.assertEqual(user.displayname, 'newuser6')
self.assertEqual(user.primary_email.address, 'newuser6@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, ['role1', 'role2'])
self.assertIsNone(User.query.filter_by(loginname='newuser7').one_or_none())
self.assertIsNone(User.query.filter_by(loginname='newuser8').one_or_none())
self.assertIsNone(User.query.filter_by(loginname='newuser9').one_or_none())
user = User.query.filter_by(loginname='newuser10').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser10')
self.assertEqual(user.displayname, 'newuser10')
self.assertEqual(user.primary_email.address, 'newuser10@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, [])
user = User.query.filter_by(loginname='newuser11').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser11')
self.assertEqual(user.displayname, 'newuser11')
self.assertEqual(user.primary_email.address, 'newuser11@example.com')
# Currently the csv import is not very robust, imho newuser11 should have role1 and role2!
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, ['role2'])
user = User.query.filter_by(loginname='newuser12').one_or_none()
self.assertIsNotNone(user)
self.assertEqual(user.loginname, 'newuser12')
self.assertEqual(user.displayname, 'newuser12')
self.assertEqual(user.primary_email.address, 'newuser12@example.com')
roles = sorted([r.name for r in user.roles])
self.assertEqual(roles, ['role1'])
class TestGroupViews(UffdTestCase):
def setUp(self):
super().setUp()
self.login_as('admin')
def test_index(self):
r = self.client.get(path=url_for('group.index'), follow_redirects=True)
dump('group_index', r)
self.assertEqual(r.status_code, 200)
def test_show(self):
r = self.client.get(path=url_for('group.show', gid=20001), follow_redirects=True)
dump('group_show', r)
self.assertEqual(r.status_code, 200)
def test_new(self):
r = self.client.get(path=url_for('group.show'), follow_redirects=True)
dump('group_new', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(Group.query.filter_by(name='newgroup').one_or_none())
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': '', 'name': 'newgroup', 'description': 'Test description'},
follow_redirects=True)
dump('group_new_submit', r)
self.assertEqual(r.status_code, 200)
group = Group.query.filter_by(name='newgroup').one_or_none()
self.assertIsNotNone(group)
self.assertEqual(group.name, 'newgroup')
self.assertEqual(group.description, 'Test description')
self.assertGreaterEqual(group.unix_gid, self.app.config['GROUP_MIN_GID'])
self.assertLessEqual(group.unix_gid, self.app.config['GROUP_MAX_GID'])
def test_new_fixed_gid(self):
gid = self.app.config['GROUP_MAX_GID'] - 1
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': str(gid), 'name': 'newgroup', 'description': 'Test description'},
follow_redirects=True)
dump('group_new_fixed_gid', r)
self.assertEqual(r.status_code, 200)
group = Group.query.filter_by(name='newgroup').one_or_none()
self.assertIsNotNone(group)
self.assertEqual(group.name, 'newgroup')
self.assertEqual(group.description, 'Test description')
self.assertEqual(group.unix_gid, gid)
def test_new_existing_name(self):
gid = self.app.config['GROUP_MAX_GID'] - 1
db.session.add(Group(name='newgroup', description='Original description', unix_gid=gid))
db.session.commit()
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': '', 'name': 'newgroup', 'description': 'New description'},
follow_redirects=True)
dump('group_new_existing_name', r)
self.assertEqual(r.status_code, 400)
group = Group.query.filter_by(name='newgroup').one_or_none()
self.assertIsNotNone(group)
self.assertEqual(group.name, 'newgroup')
self.assertEqual(group.description, 'Original description')
self.assertEqual(group.unix_gid, gid)
def test_new_name_too_long(self):
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': '', 'name': 'a'*33, 'description': 'New description'},
follow_redirects=True)
dump('group_new_name_too_long', r)
self.assertEqual(r.status_code, 400)
group = Group.query.filter_by(name='a'*33).one_or_none()
self.assertIsNone(group)
def test_new_name_too_short(self):
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': '', 'name': '', 'description': 'New description'},
follow_redirects=True)
dump('group_new_name_too_short', r)
self.assertEqual(r.status_code, 400)
group = Group.query.filter_by(name='').one_or_none()
self.assertIsNone(group)
def test_new_name_invalid(self):
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': '', 'name': 'foo bar', 'description': 'New description'},
follow_redirects=True)
dump('group_new_name_invalid', r)
self.assertEqual(r.status_code, 400)
group = Group.query.filter_by(name='foo bar').one_or_none()
self.assertIsNone(group)
def test_new_existing_gid(self):
gid = self.app.config['GROUP_MAX_GID'] - 1
db.session.add(Group(name='newgroup', description='Original description', unix_gid=gid))
db.session.commit()
r = self.client.post(path=url_for('group.update'),
data={'unix_gid': str(gid), 'name': 'newgroup2', 'description': 'New description'},
follow_redirects=True)
dump('group_new_existing_gid', r)
self.assertEqual(r.status_code, 400)
group = Group.query.filter_by(name='newgroup').one_or_none()
self.assertIsNotNone(group)
self.assertEqual(group.name, 'newgroup')
self.assertEqual(group.description, 'Original description')
self.assertEqual(group.unix_gid, gid)
self.assertIsNone(Group.query.filter_by(name='newgroup2').one_or_none())
def test_update(self):
group = Group(name='newgroup', description='Original description')
db.session.add(group)
db.session.commit()
group_id = group.id
group_gid = group.unix_gid
new_gid = self.app.config['GROUP_MAX_GID'] - 1
r = self.client.post(path=url_for('group.update', id=group_id),
data={'unix_gid': str(new_gid), 'name': 'newgroup_changed', 'description': 'New description'},
follow_redirects=True)
dump('group_update', r)
self.assertEqual(r.status_code, 200)
group = Group.query.get(group_id)
self.assertEqual(group.name, 'newgroup') # Not changed
self.assertEqual(group.description, 'New description') # Changed
self.assertEqual(group.unix_gid, group_gid) # Not changed
def test_delete(self):
group1 = Group(name='newgroup1', description='Original description1')
group2 = Group(name='newgroup2', description='Original description2')
db.session.add(group1)
db.session.add(group2)
db.session.commit()
group1_id = group1.id
group2_id = group2.id
r = self.client.get(path=url_for('group.delete', id=group1_id), follow_redirects=True)
dump('group_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(Group.query.get(group1_id))
self.assertIsNotNone(Group.query.get(group2_id))
......@@ -2,77 +2,116 @@ import os
import secrets
import sys
from flask import Flask, redirect, url_for
from werkzeug.routing import IntegerConverter
from werkzeug.serving import make_ssl_devcert
from werkzeug.contrib.profiler import ProfilerMiddleware
from flask import Flask, redirect, url_for, request, render_template
from flask_babel import Babel
from babel.dates import LOCALTZ
from werkzeug.exceptions import Forbidden
from flask_migrate import Migrate
sys.path.append('deps/ldapalchemy')
from .database import db, customize_db_engine
from .template_helper import register_template_helper
from .navbar import setup_navbar
from .csrf import bp as csrf_bp
from . import models, views, commands
# pylint: disable=wrong-import-position
from uffd.database import db, SQLAlchemyJSON
from uffd.template_helper import register_template_helper
from uffd.navbar import setup_navbar
# pylint: enable=wrong-import-position
def load_config_file(app, path, silent=False):
if not os.path.exists(path):
if not silent:
raise Exception(f"Config file {path} not found")
return False
def create_app(test_config=None): # pylint: disable=too-many-locals
# create and configure the app
app = Flask(__name__, instance_relative_config=False)
app.json_encoder = SQLAlchemyJSON
if path.endswith(".json"):
app.config.from_json(path)
elif path.endswith(".yaml") or path.endswith(".yml"):
import yaml # pylint: disable=import-outside-toplevel disable=import-error
with open(path, encoding='utf-8') as ymlfile:
data = yaml.safe_load(ymlfile)
app.config.from_mapping(data)
else:
app.config.from_pyfile(path, silent=True)
return True
def init_config(app: Flask, test_config):
# set development default config values
app.config.from_mapping(
SECRET_KEY=secrets.token_hex(128),
SQLALCHEMY_DATABASE_URI="sqlite:///{}".format(os.path.join(app.instance_path, 'uffd.sqlit3')),
)
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{os.path.join(app.instance_path, 'uffd.sqlit3')}"
app.config.from_pyfile('default_config.cfg')
# load config
if test_config is not None:
app.config.from_mapping(test_config)
elif os.environ.get('CONFIG_PATH'):
load_config_file(app, os.environ['CONFIG_PATH'], silent=False)
else:
for filename in ["config.cfg", "config.json", "config.yml", "config.yaml"]:
if load_config_file(app, os.path.join(app.instance_path, filename), silent=True):
break
if app.secret_key is None:
if app.env == "production":
raise Exception("SECRET_KEY not configured and we are running in production mode!")
app.secret_key = secrets.token_hex(128)
def create_app(test_config=None): # pylint: disable=too-many-locals,too-many-statements
app = Flask(__name__, instance_relative_config=False)
init_config(app, test_config)
register_template_helper(app)
setup_navbar(app)
if not test_config:
# load the instance config, if it exists, when not testing
app.config.from_pyfile(os.path.join(app.instance_path, 'config.cfg'), silent=True)
# Sort the navbar positions by their blueprint names (from the left)
if app.config['DEFAULT_PAGE_SERVICES']:
positions = ["service", "selfservice"]
else:
# load the test config if passed in
app.config.from_mapping(test_config)
positions = ["selfservice", "service"]
positions += ["rolemod", "invite", "user", "group", "role", "mail"]
setup_navbar(app, positions)
app.register_blueprint(csrf_bp)
views.init_app(app)
commands.init_app(app)
# ensure the instance folder exists
# We never want to fail here, but at a file access that doesn't work.
# We might only have read access to app.instance_path
try:
os.makedirs(app.instance_path)
except OSError:
os.makedirs(app.instance_path, exist_ok=True)
except: # pylint: disable=bare-except
pass
db.init_app(app)
Migrate(app, db, render_as_batch=True)
# pylint: disable=C0415
from uffd import user, selfservice, role, mail, session, csrf, mfa, oauth2, services, signup, invite
# pylint: enable=C0415
for i in user.bp + selfservice.bp + role.bp + mail.bp + session.bp + csrf.bp + mfa.bp + oauth2.bp + services.bp + signup.bp + invite.bp:
app.register_blueprint(i)
@app.route("/")
def index(): #pylint: disable=unused-variable
return redirect(url_for('selfservice.index'))
@app.cli.command("gendevcert", help='Generates a self-signed TLS certificate for development')
def gendevcert(): #pylint: disable=unused-variable
if os.path.exists('devcert.crt') or os.path.exists('devcert.key'):
print('Refusing to overwrite existing "devcert.crt"/"devcert.key" file!')
return
make_ssl_devcert('devcert')
print('Certificate written to "devcert.crt", private key to "devcert.key".')
print('Run `flask run --cert devcert.crt --key devcert.key` to use it.')
@app.cli.command("profile", help='Runs app with profiler')
def profile(): #pylint: disable=unused-variable
# app.run() is silently ignored if executed from commands. We really want
# to do this, so we overwrite the check by overwriting the environment
# variable.
os.environ['FLASK_RUN_FROM_CLI'] = 'false'
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug=True)
Migrate(app, db, render_as_batch=True, directory=os.path.join(app.root_path, 'migrations'))
with app.app_context():
customize_db_engine(db.engine)
@app.shell_context_processor
def push_request_context(): #pylint: disable=unused-variable
ctx = {name: getattr(models, name) for name in models.__all__}
ctx.setdefault('db', db)
return ctx
# flask-babel requires pytz-style timezone objects, but in rare cases (e.g.
# non-IANA TZ values) LOCALTZ is stdlib-style (without normalize/localize)
if not hasattr(LOCALTZ, 'normalize'):
LOCALTZ.normalize = lambda dt: dt
if not hasattr(LOCALTZ, 'localize'):
LOCALTZ.localize = lambda dt: dt.replace(tzinfo=LOCALTZ)
class PatchedBabel(Babel):
@property
def default_timezone(self):
if self.app.config['BABEL_DEFAULT_TIMEZONE'] == 'LOCALTZ':
return LOCALTZ
return super().default_timezone
babel = PatchedBabel(app, default_timezone='LOCALTZ')
@babel.localeselector
def get_locale(): #pylint: disable=unused-variable
language_cookie = request.cookies.get('language')
if language_cookie is not None and language_cookie in app.config['LANGUAGES']:
return language_cookie
return request.accept_languages.best_match(list(app.config['LANGUAGES']))
app.add_template_global(get_locale)
return app
[python: **.py]
[jinja2: **/templates/**.html]
from .user import user_command
from .group import group_command
from .role import role_command
from .profile import profile_command
from .gendevcert import gendevcert_command
from .cleanup import cleanup_command
from .roles_update_all import roles_update_all_command
from .unique_email_addresses import unique_email_addresses_command
def init_app(app):
app.cli.add_command(user_command)
app.cli.add_command(group_command)
app.cli.add_command(role_command)
app.cli.add_command(gendevcert_command)
app.cli.add_command(profile_command)
app.cli.add_command(cleanup_command)
app.cli.add_command(roles_update_all_command)
app.cli.add_command(unique_email_addresses_command)