"""What the identity provider's groups make somebody here. Run: DATABASE_URL=sqlite:///:memory: PYTHONPATH=backend python -m unittest discover -s backend/tests """ import os os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") import unittest from types import SimpleNamespace from sqlalchemy import create_engine from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool from app.database import Base from app.models.user import User from app.services import sso_roles def config(claim="groups", admin="", moderator=""): return SimpleNamespace(OIDC_ROLE_CLAIM=claim, OIDC_ADMIN_GROUPS=admin, OIDC_MODERATOR_GROUPS=moderator) class RoleMappingTests(unittest.TestCase): def test_off_unless_configured(self): # The old behaviour, exactly: no claim named, no opinion about anybody. self.assertIsNone(sso_roles.role_for({"groups": ["peds-admins"]}, config())) self.assertIsNone(sso_roles.role_for({"groups": ["peds-admins"]}, config(claim="", admin="peds-admins"))) def test_reads_the_three_shapes_a_provider_sends(self): cfg = config(admin="Peds-Admins") for claim in (["peds-admins"], "peds-admins", "peds-admins other", "peds-admins,other", ("PEDS-ADMINS",)): self.assertEqual(sso_roles.role_for({"groups": claim}, cfg), "admin", claim) def test_admin_beats_moderator(self): cfg = config(admin="bosses", moderator="educators") self.assertEqual( sso_roles.role_for({"groups": ["educators", "bosses"]}, cfg), "admin") def test_belonging_to_neither_is_a_learner(self): cfg = config(admin="bosses", moderator="educators") self.assertEqual(sso_roles.role_for({"groups": ["everyone"]}, cfg), "user") self.assertEqual(sso_roles.role_for({}, cfg), "user") class RoleSyncTests(unittest.TestCase): def setUp(self): self.engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) Base.metadata.create_all(self.engine) self.db = Session(self.engine) def tearDown(self): self.db.close() self.engine.dispose() def add(self, email, role): user = User(name=email, email=email, hashed_password="unused", role=role) self.db.add(user) self.db.commit() return user def test_the_directory_promotes_and_demotes(self): # Demotion is the point. A group somebody can be added to and never # removed from is not a list anybody can rely on. cfg = config(admin="bosses", moderator="educators") learner = self.add("new@example.test", "user") self.assertEqual(sso_roles.apply(self.db, learner, {"groups": ["educators"]}, cfg), "moderator") self.assertEqual(learner.role, "moderator") self.assertEqual(sso_roles.apply(self.db, learner, {"groups": []}, cfg), "user") self.assertEqual(learner.role, "user") def test_it_will_not_demote_the_last_admin(self): # A mistyped group name should not lock everybody out of the settings # page, including the person who could fix the group name. cfg = config(admin="bosses") boss = self.add("boss@example.test", "admin") self.assertIsNone(sso_roles.apply(self.db, boss, {"groups": ["typo"]}, cfg)) self.assertEqual(boss.role, "admin") # With a second admin standing, the demotion goes through. self.add("other@example.test", "admin") self.assertEqual(sso_roles.apply(self.db, boss, {"groups": ["typo"]}, cfg), "user") self.assertEqual(boss.role, "user") def test_an_unconfigured_site_never_touches_a_role(self): boss = self.add("boss@example.test", "admin") self.assertIsNone(sso_roles.apply(self.db, boss, {"groups": []}, config())) self.assertEqual(boss.role, "admin") if __name__ == "__main__": unittest.main()