229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
"""Tests for flag-based permission resolution (permissions.py).
|
|
|
|
Replaces test/authprovider-find_config_flags.js,
|
|
test/authprovider-find_database_flags.js,
|
|
test/authprovider-impersonate.js,
|
|
test/authprovider-effective_permissions.js.
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
from cteward_ng.permissions import (
|
|
find_config_flags,
|
|
find_database_flags,
|
|
impersonate,
|
|
effective_permissions,
|
|
)
|
|
|
|
|
|
# ── helpers ───────────────────────────────────────────────────────
|
|
|
|
def _make_request(query_params=None, view_args=None):
|
|
req = Mock()
|
|
req.args = Mock()
|
|
req.args.get = Mock(return_value=query_params)
|
|
req.view_args = view_args
|
|
return req
|
|
|
|
|
|
# ── find_config_flags ─────────────────────────────────────────────
|
|
|
|
class TestFindConfigFlags:
|
|
def test_basic_flags(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'request': _make_request(view_args={'crewname': 'bob'}),
|
|
'config': {'auth': {'flags': {'alice': ['_board_', '_admin_']}}},
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_config_flags(ctx)
|
|
assert '_anonymous_' in ctx['flags']
|
|
assert '_board_' in ctx['flags']
|
|
assert '_admin_' in ctx['flags']
|
|
|
|
def test_self_flag_when_querying_own_record(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'request': _make_request(view_args={'crewname': 'alice'}),
|
|
'config': {'auth': {'flags': {}}},
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_config_flags(ctx)
|
|
assert '_self_' in ctx['flags']
|
|
|
|
def test_no_self_flag_when_querying_other(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'request': _make_request(view_args={'crewname': 'bob'}),
|
|
'config': {'auth': {'flags': {}}},
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_config_flags(ctx)
|
|
assert '_self_' not in ctx['flags']
|
|
|
|
def test_impersonating_limited_strips_privileges(self):
|
|
ctx = {
|
|
'username': 'limiter',
|
|
'request': _make_request(view_args={'crewname': 'someone'}),
|
|
'config': {'auth': {'flags': {'limiter': ['_admin_', '_board_', '_bot_', '_impersonate_']}}},
|
|
'flags': ['_anonymous_', '_impersonate_'],
|
|
}
|
|
find_config_flags(ctx)
|
|
# After stripping, none of admin/board/bot should remain
|
|
for bad in ('_admin_', '_board_', '_bot_'):
|
|
assert bad not in ctx['flags'], f"{bad} should have been stripped"
|
|
|
|
def test_no_view_args(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'request': _make_request(view_args=None),
|
|
'config': {'auth': {'flags': {'alice': ['_bot_']}}},
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_config_flags(ctx)
|
|
assert '_bot_' in ctx['flags']
|
|
|
|
|
|
# ── find_database_flags ───────────────────────────────────────
|
|
|
|
class TestFindDatabaseFlags:
|
|
@patch('cteward_ng.permissions.member_lookup')
|
|
def test_crew_gets_member_flag(self, mock_lookup):
|
|
mock_lookup.return_value = {'Kennung3': 'crew', 'Kurzname': 'alice'}
|
|
ctx = {
|
|
'username': 'alice',
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_database_flags(ctx)
|
|
assert '_member_' in ctx['flags']
|
|
|
|
@patch('cteward_ng.permissions.member_lookup')
|
|
def test_raumfahrer_gets_astronaut_flag(self, mock_lookup):
|
|
mock_lookup.return_value = {'Kennung3': 'raumfahrer', 'Kurzname': 'astronaut'}
|
|
ctx = {
|
|
'username': 'astronaut',
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_database_flags(ctx)
|
|
assert '_astronaut_' in ctx['flags']
|
|
|
|
@patch('cteward_ng.permissions.member_lookup')
|
|
def test_passiv_gets_passive_flag(self, mock_lookup):
|
|
mock_lookup.return_value = {'Kennung3': 'passiv', 'Kurzname': 'oldtimer'}
|
|
ctx = {
|
|
'username': 'oldtimer',
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_database_flags(ctx)
|
|
assert '_passive_' in ctx['flags']
|
|
|
|
@patch('cteward_ng.permissions.member_lookup')
|
|
def test_not_in_db_no_extra_flags(self, mock_lookup):
|
|
mock_lookup.side_effect = RuntimeError("Not found.")
|
|
ctx = {
|
|
'username': 'nobody',
|
|
'flags': ['_anonymous_'],
|
|
}
|
|
find_database_flags(ctx)
|
|
assert ctx['flags'] == ['_anonymous_']
|
|
|
|
|
|
# ── impersonate ───────────────────────────────────────────────
|
|
|
|
class TestImpersonate:
|
|
def test_no_impersonate_param(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'flags': ['_anonymous_'],
|
|
'request': _make_request(query_params=None),
|
|
}
|
|
impersonate(ctx)
|
|
assert ctx['username'] == 'alice'
|
|
|
|
def test_impersonate_self_is_noop(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'flags': ['_anonymous_'],
|
|
'request': _make_request(query_params='alice'),
|
|
}
|
|
impersonate(ctx)
|
|
assert ctx['username'] == 'alice'
|
|
|
|
def test_unauthorized_impersonation_aborts(self):
|
|
ctx = {
|
|
'username': 'nobody',
|
|
'flags': ['_anonymous_'],
|
|
'request': _make_request(query_params='admin'),
|
|
}
|
|
with pytest.raises(Exception):
|
|
impersonate(ctx)
|
|
|
|
@patch('cteward_ng.permissions.find_config_flags')
|
|
@patch('cteward_ng.permissions.find_database_flags')
|
|
def test_admin_can_impersonate(self, mock_dbf, mock_cfg):
|
|
ctx = {
|
|
'username': 'admin',
|
|
'flags': ['_anonymous_', '_admin_'],
|
|
'request': _make_request(query_params='target_user'),
|
|
'config': {'auth': {'flags': {}}},
|
|
}
|
|
impersonate(ctx)
|
|
assert ctx['username'] == 'target_user'
|
|
mock_cfg.assert_called_once()
|
|
mock_dbf.assert_called_once()
|
|
|
|
|
|
# ── effective_permissions ─────────────────────────────────────
|
|
|
|
class TestEffectivePermissions:
|
|
def test_lowest_level_wins(self):
|
|
ctx = {
|
|
'username': 'alice',
|
|
'flags': ['_anonymous_', '_board_'],
|
|
'permissions': {
|
|
'_anonymous_': {'query': 'Q1', 'level': 3},
|
|
'_board_': {'query': 'Q2', 'level': 0},
|
|
},
|
|
}
|
|
effective_permissions(ctx)
|
|
assert ctx['permission']['query'] == 'Q2'
|
|
assert ctx['query'] == 'Q2'
|
|
|
|
def test_anonymous_only(self):
|
|
ctx = {
|
|
'username': 'anon',
|
|
'flags': ['_anonymous_'],
|
|
'permissions': {
|
|
'_anonymous_': {'query': 'Q1', 'level': 3},
|
|
},
|
|
}
|
|
effective_permissions(ctx)
|
|
assert ctx['permission']['query'] == 'Q1'
|
|
|
|
def test_no_matching_permission_aborts(self):
|
|
ctx = {
|
|
'username': 'nobody',
|
|
'flags': ['_anonymous_'],
|
|
'permissions': {
|
|
'_board_': {'query': 'Q1', 'level': 0},
|
|
},
|
|
}
|
|
with pytest.raises(Exception):
|
|
effective_permissions(ctx)
|
|
|
|
def test_permission_with_filter(self):
|
|
from cteward_ng.filters import memberlist_active_only
|
|
ctx = {
|
|
'username': 'alice',
|
|
'flags': ['_anonymous_', '_member_'],
|
|
'permissions': {
|
|
'_member_': {
|
|
'query': 'Q1',
|
|
'level': 1,
|
|
'filter': memberlist_active_only,
|
|
},
|
|
},
|
|
}
|
|
effective_permissions(ctx)
|
|
assert ctx['filter'] is memberlist_active_only
|