251 lines
8.9 KiB
Python
251 lines
8.9 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2019 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import datetime
|
|
import importlib
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from django.core import management
|
|
from django.utils import six
|
|
from django.utils.timezone import now
|
|
import py
|
|
|
|
from authentic2.models import Attribute, DeletedUser
|
|
from authentic2_auth_oidc.models import OIDCProvider
|
|
from django_rbac.utils import get_ou_model
|
|
|
|
from utils import login
|
|
|
|
if six.PY2:
|
|
FileType = file
|
|
else:
|
|
from io import TextIOWrapper, BufferedReader, BufferedWriter
|
|
FileType = (TextIOWrapper, BufferedReader, BufferedWriter)
|
|
|
|
|
|
def test_changepassword(db, simple_user, monkeypatch):
|
|
import getpass
|
|
|
|
def _getpass(*args, **kwargs):
|
|
return 'pass'
|
|
|
|
monkeypatch.setattr(getpass, 'getpass', _getpass)
|
|
management.call_command('changepassword', 'user')
|
|
old_pass = simple_user.password
|
|
simple_user.refresh_from_db()
|
|
assert old_pass != simple_user.password
|
|
|
|
|
|
def test_clean_unused_account(db, simple_user, mailoutbox, freezer):
|
|
freezer.move_to('2018-01-01')
|
|
simple_user.ou.clean_unused_accounts_alert = 2
|
|
simple_user.ou.clean_unused_accounts_deletion = 3
|
|
simple_user.ou.save()
|
|
|
|
simple_user.last_login = now() - datetime.timedelta(days=2)
|
|
simple_user.save()
|
|
|
|
management.call_command('clean-unused-accounts')
|
|
assert not DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 1
|
|
|
|
freezer.move_to('2018-01-01 12:00:00')
|
|
# no new mail, no deletion
|
|
management.call_command('clean-unused-accounts')
|
|
assert not DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 1
|
|
|
|
freezer.move_to('2018-01-02')
|
|
management.call_command('clean-unused-accounts')
|
|
assert DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 2
|
|
|
|
|
|
def test_clean_unused_account_user_logs_in(app, db, simple_user, mailoutbox, freezer):
|
|
freezer.move_to('2018-01-01')
|
|
simple_user.ou.clean_unused_accounts_alert = 2
|
|
simple_user.ou.clean_unused_accounts_deletion = 3
|
|
simple_user.ou.save()
|
|
|
|
simple_user.last_login = now() - datetime.timedelta(days=2)
|
|
simple_user.save()
|
|
|
|
management.call_command('clean-unused-accounts')
|
|
assert len(mailoutbox) == 1
|
|
|
|
login(app, simple_user)
|
|
|
|
# the day of deletion, nothing happens
|
|
freezer.move_to('2018-01-02')
|
|
assert not DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 1
|
|
|
|
# when new alert delay is reached, user gets alerted again
|
|
freezer.move_to('2018-01-04')
|
|
management.call_command('clean-unused-accounts')
|
|
assert not DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 2
|
|
|
|
|
|
def test_clean_unused_account_disabled_by_default(db, simple_user, mailoutbox):
|
|
simple_user.last_login = now() - datetime.timedelta(days=2)
|
|
simple_user.save()
|
|
|
|
management.call_command('clean-unused-accounts')
|
|
assert not DeletedUser.objects.filter(user=simple_user).exists()
|
|
assert len(mailoutbox) == 0
|
|
|
|
|
|
def test_clean_unused_account_always_alert(db, simple_user, mailoutbox, freezer):
|
|
simple_user.ou.clean_unused_accounts_alert = 2
|
|
simple_user.ou.clean_unused_accounts_deletion = 3 # one day between alert and actual deletion
|
|
simple_user.ou.save()
|
|
|
|
simple_user.last_login = now() - datetime.timedelta(days=4)
|
|
simple_user.save()
|
|
|
|
# even if account last login in past deletion delay, an alert is always sent first
|
|
management.call_command('clean-unused-accounts')
|
|
assert not len(DeletedUser.objects.filter(user=simple_user))
|
|
assert len(mailoutbox) == 1
|
|
|
|
# and calling again as no effect, since one day must pass before account is deleted
|
|
management.call_command('clean-unused-accounts')
|
|
assert not len(DeletedUser.objects.filter(user=simple_user))
|
|
assert len(mailoutbox) == 1
|
|
|
|
|
|
@pytest.mark.parametrize("deletion_delay,formatted",
|
|
[(730, u'2\xa0years'), (500, u'1\xa0year'), (65, u'2\xa0months')])
|
|
def test_clean_unused_account_human_duration_format(simple_user, mailoutbox, deletion_delay, formatted):
|
|
simple_user.ou.clean_unused_accounts_alert = deletion_delay - 1
|
|
simple_user.ou.clean_unused_accounts_deletion = deletion_delay
|
|
simple_user.ou.save()
|
|
simple_user.last_login = now() - datetime.timedelta(days=deletion_delay + 1)
|
|
simple_user.save()
|
|
|
|
# alert email
|
|
management.call_command('clean-unused-accounts')
|
|
mail = mailoutbox[0]
|
|
assert formatted in mail.body
|
|
assert formatted in mail.subject and not '\n' in mail.subject
|
|
|
|
# deletion email
|
|
simple_user.last_account_deletion_alert = now() - datetime.timedelta(days=2)
|
|
simple_user.save()
|
|
management.call_command('clean-unused-accounts')
|
|
mail = mailoutbox[1]
|
|
assert formatted in mail.body
|
|
|
|
|
|
def test_clean_unused_account_login_url(simple_user, mailoutbox):
|
|
simple_user.ou.clean_unused_accounts_alert = 1
|
|
simple_user.ou.clean_unused_accounts_deletion = 2
|
|
simple_user.ou.save()
|
|
simple_user.last_login = now() - datetime.timedelta(days=1)
|
|
simple_user.save()
|
|
management.call_command('clean-unused-accounts')
|
|
mail = mailoutbox[0]
|
|
assert 'href="http://localhost/login/"' in mail.message().as_string()
|
|
|
|
|
|
def test_cleanupauthentic(db):
|
|
management.call_command('cleanupauthentic')
|
|
|
|
|
|
def test_load_ldif(db, monkeypatch, tmpdir):
|
|
ldif = tmpdir.join('some.ldif')
|
|
ldif.ensure()
|
|
|
|
class MockPArser(object):
|
|
def __init__(self, *args, **kwargs):
|
|
self.users = []
|
|
assert len(args) == 1
|
|
assert isinstance(args[0], FileType)
|
|
assert kwargs['options']['extra_attribute'] == {'ldap_attr': 'first_name'}
|
|
assert kwargs['options']['result'] == 'result'
|
|
|
|
def parse(self):
|
|
pass
|
|
|
|
oidc_cmd = importlib.import_module(
|
|
'authentic2.management.commands.load-ldif')
|
|
monkeypatch.setattr(oidc_cmd, 'DjangoUserLDIFParser', MockPArser)
|
|
management.call_command(
|
|
'load-ldif', ldif.strpath, result='result', extra_attribute={'ldap_attr': 'first_name'})
|
|
|
|
# test ExtraAttributeAction
|
|
class MockPArser(object):
|
|
def __init__(self, *args, **kwargs):
|
|
self.users = []
|
|
assert len(args) == 1
|
|
assert isinstance(args[0], FileType)
|
|
assert kwargs['options']['extra_attribute'] == {
|
|
'ldap_attr': 'first_name'}
|
|
assert kwargs['options']['result'] == 'result'
|
|
|
|
def parse(self):
|
|
pass
|
|
|
|
monkeypatch.setattr(oidc_cmd, 'DjangoUserLDIFParser', MockPArser)
|
|
management.call_command(
|
|
'load-ldif', '--extra-attribute', 'ldap_attr', 'first_name',
|
|
'--result', 'result', ldif.strpath)
|
|
|
|
|
|
def test_oidc_register_issuer(db, tmpdir, monkeypatch):
|
|
oidc_conf_f = py.path.local(__file__).dirpath('openid_configuration.json')
|
|
with oidc_conf_f.open() as f:
|
|
oidc_conf = json.load(f)
|
|
|
|
def register_issuer(
|
|
name, issuer=None, openid_configuration=None, verify=True, timeout=None,
|
|
ou=None):
|
|
OU = get_ou_model()
|
|
ou = OU.objects.get(default=True)
|
|
return OIDCProvider.objects.create(
|
|
name=name, ou=ou, issuer=issuer, strategy='create',
|
|
authorization_endpoint=openid_configuration['authorization_endpoint'],
|
|
token_endpoint=openid_configuration['token_endpoint'],
|
|
userinfo_endpoint=openid_configuration['userinfo_endpoint'],
|
|
end_session_endpoint=openid_configuration['end_session_endpoint'])
|
|
|
|
oidc_cmd = importlib.import_module(
|
|
'authentic2_auth_oidc.management.commands.oidc-register-issuer')
|
|
monkeypatch.setattr(oidc_cmd, 'register_issuer', register_issuer)
|
|
|
|
oidc_conf = py.path.local(__file__).dirpath('openid_configuration.json').strpath
|
|
management.call_command(
|
|
'oidc-register-issuer', '--openid-configuration', oidc_conf, '--issuer', 'issuer',
|
|
'somename')
|
|
|
|
provider = OIDCProvider.objects.get(name='somename')
|
|
assert provider.issuer == 'issuer'
|
|
|
|
|
|
def test_resetpassword(simple_user):
|
|
management.call_command('resetpassword', 'user')
|
|
old_pass = simple_user.password
|
|
simple_user.refresh_from_db()
|
|
assert old_pass != simple_user.password
|
|
|
|
|
|
def test_sync_metadata(db):
|
|
test_file = py.path.local(__file__).dirpath('metadata.xml').strpath
|
|
management.call_command('sync-metadata', test_file)
|