Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c
123 lines
4.9 KiB
Python
123 lines
4.9 KiB
Python
"""Run the in-pod adapter against actual privacyIDEA 3.12.2 models on SQLite.
|
|
|
|
Install that exact package in a disposable environment. No native cluster or
|
|
production config is used; this does not replace deployed-image acceptance.
|
|
"""
|
|
import importlib.metadata
|
|
import importlib.util
|
|
import io
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip('privacyidea')
|
|
if importlib.metadata.version('privacyIDEA') != '3.12.2':
|
|
pytest.skip('adapter verification requires privacyIDEA 3.12.2', allow_module_level=True)
|
|
|
|
from flask import Flask
|
|
from privacyidea.models import Admin, Policy, db
|
|
from privacyidea.lib.crypto import hash_with_pepper
|
|
from privacyidea.lib.auth import verify_db_admin
|
|
import privacyidea.cli
|
|
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
PASSWORD='SyntheticAdminCredential12345678901234567890'
|
|
|
|
|
|
@pytest.fixture
|
|
def native(tmp_path,monkeypatch):
|
|
spec=importlib.util.spec_from_file_location('admin_remote_native',ROOT/'sso-mfa/k8s/privacyidea/bootstrap-admin-remote.py')
|
|
remote=importlib.util.module_from_spec(spec);spec.loader.exec_module(remote)
|
|
app=Flask('disposable-bootstrap')
|
|
app.config.update(SQLALCHEMY_DATABASE_URI='sqlite:///'+str(tmp_path/'db.sqlite'),
|
|
SQLALCHEMY_TRACK_MODIFICATIONS=False,PI_PEPPER='synthetic-pepper',SECRET_KEY='synthetic-key')
|
|
db.init_app(app)
|
|
with app.app_context(): db.create_all()
|
|
monkeypatch.setattr(privacyidea.cli,'create_silent_app',lambda:app)
|
|
def invoke(password=PASSWORD):
|
|
import json
|
|
monkeypatch.setattr(sys,'stdin',io.TextIOWrapper(io.BytesIO(json.dumps({'password':password}).encode())))
|
|
return remote.main()
|
|
import logging
|
|
previous_disable = logging.root.manager.disable
|
|
try:
|
|
yield app,remote,invoke
|
|
finally:
|
|
logging.disable(previous_disable)
|
|
|
|
|
|
def test_real_model_creation_and_repeat_preserve_password_hash(native):
|
|
app,_,invoke=native
|
|
assert invoke()=={'result':'created','mfa_verified':False}
|
|
with app.app_context():
|
|
before=Admin.query.filter_by(username='pi-admin').one().password
|
|
assert verify_db_admin('pi-admin',PASSWORD)
|
|
assert [a.username for a in Admin.query.all()]==['pi-admin']
|
|
assert Policy.query.one().adminuser=='pi-admin'
|
|
assert invoke()=={'result':'verified','mfa_verified':False}
|
|
with app.app_context(): assert Admin.query.one().password==before
|
|
|
|
|
|
def test_real_model_mismatch_never_resets_password(native):
|
|
app,remote,invoke=native
|
|
invoke()
|
|
with app.app_context(): before=Admin.query.one().password
|
|
with pytest.raises(remote.Refused):invoke('DifferentSyntheticPassword1234567890123456')
|
|
with app.app_context(): assert Admin.query.one().password==before
|
|
|
|
|
|
def test_real_legacy_broad_policy_refused_before_account_creation(native):
|
|
app,remote,invoke=native
|
|
with app.app_context():
|
|
db.session.add(Policy(name='pi-admin-all-rights',scope='admin',action='*'));db.session.commit()
|
|
with pytest.raises(remote.Refused):invoke()
|
|
with app.app_context():
|
|
assert Admin.query.count()==0
|
|
assert Policy.query.one().adminuser==''
|
|
|
|
|
|
def test_real_other_admin_requires_explicit_policy_migration(native):
|
|
app,remote,invoke=native
|
|
with app.app_context():
|
|
db.session.add(Admin(username='other-admin',password=hash_with_pepper(PASSWORD)));db.session.commit()
|
|
with pytest.raises(remote.Refused):invoke()
|
|
with app.app_context():
|
|
assert [a.username for a in Admin.query.all()]==['other-admin']
|
|
assert Policy.query.count()==0
|
|
|
|
|
|
def test_real_failed_policy_insert_rolls_back_account(native,monkeypatch):
|
|
app,remote,invoke=native
|
|
original=db.session.add
|
|
def fail_policy(obj,*args,**kwargs):
|
|
if isinstance(obj,Policy):raise RuntimeError('synthetic-failure')
|
|
return original(obj,*args,**kwargs)
|
|
monkeypatch.setattr(db.session,'add',fail_policy)
|
|
with pytest.raises(remote.Refused):invoke()
|
|
with app.app_context():
|
|
assert Admin.query.count()==0
|
|
assert Policy.query.count()==0
|
|
|
|
|
|
def test_real_concurrent_account_conflict_never_updates_existing_row(native,monkeypatch):
|
|
import privacyidea.lib.auth
|
|
app,remote,invoke=native
|
|
with app.app_context():
|
|
db.session.add(Admin(username='pi-admin',password=hash_with_pepper('ExistingSyntheticPassword1234567890123456')))
|
|
db.session.commit();before=Admin.query.one().password
|
|
# Simulate an account appearing after the initial absence observation.
|
|
monkeypatch.setattr(privacyidea.lib.auth,'list_db_admin',lambda:[])
|
|
with pytest.raises(remote.Refused):invoke()
|
|
with app.app_context():
|
|
assert Admin.query.one().password==before
|
|
assert Policy.query.count()==0
|
|
|
|
|
|
def test_real_external_admin_realm_requires_owner_profile(native):
|
|
app,remote,invoke=native
|
|
app.config['SUPERUSER_REALM']=['directory-admins']
|
|
with pytest.raises(remote.Refused):invoke()
|
|
with app.app_context():
|
|
assert Admin.query.count()==0
|
|
assert Policy.query.count()==0
|