Skip to content

Commit 4ed0c3e

Browse files
committed
test(security): cover legacy compatibility and route enforcement
1 parent 29012ee commit 4ed0c3e

3 files changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from security.manager import get_protocol, is_permitted, load_legacy_records
2+
from security.models import ActionChecks, SecProtocol
3+
from server.auth import ROLE_ADMIN, ROLE_USER, create_access_token
4+
5+
6+
def test_action_checks_enforces_login_users_and_roles():
7+
checks = ActionChecks(login=True, valid_users=["alice"], allowed_roles=[ROLE_ADMIN])
8+
9+
assert checks.is_permitted("alice", {"role": ROLE_ADMIN})
10+
assert not checks.is_permitted("", {"role": ROLE_ADMIN})
11+
assert not checks.is_permitted("bob", {"role": ROLE_ADMIN})
12+
assert not checks.is_permitted("alice", {"role": ROLE_USER})
13+
14+
15+
def test_protocol_round_trip_from_legacy_records():
16+
records = {
17+
"countries": {
18+
"update": {
19+
"user_list": ["alice"],
20+
"checks": {
21+
"login": True,
22+
"allowed_roles": [ROLE_ADMIN],
23+
},
24+
},
25+
},
26+
}
27+
28+
load_legacy_records(records)
29+
protocol = get_protocol("countries")
30+
31+
assert protocol is not None
32+
assert protocol.name == "countries"
33+
assert protocol.update.login is True
34+
assert protocol.update.valid_users == ["alice"]
35+
assert protocol.update.allowed_roles == [ROLE_ADMIN]
36+
37+
38+
def test_manager_is_permitted_uses_jwt_payload_when_no_user_id_is_passed():
39+
records = {
40+
"countries": {
41+
"update": {
42+
"user_list": ["alice"],
43+
"checks": {
44+
"login": True,
45+
"allowed_roles": [ROLE_ADMIN],
46+
},
47+
},
48+
},
49+
}
50+
load_legacy_records(records)
51+
token = create_access_token("alice", ROLE_ADMIN, expires_hours=1)
52+
53+
assert is_permitted("countries", "update", auth_header=f"Bearer {token}")
54+
assert not is_permitted("countries", "update")
55+
56+
57+
def test_unknown_protocol_is_open_by_default():
58+
load_legacy_records({})
59+
assert is_permitted("missing", "read")
60+
61+
62+
def test_duplicate_protocol_add_is_rejected():
63+
protocol = SecProtocol("people")
64+
load_legacy_records({})
65+
from security.manager import add_protocol
66+
67+
add_protocol(protocol)
68+
try:
69+
add_protocol(protocol)
70+
assert False, "Expected ValueError"
71+
except ValueError as exc:
72+
assert "Duplicate protocol" in str(exc)

security/tests/test_security.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,17 @@ def test_read():
77
for feature in recs:
88
assert isinstance(feature, str)
99
assert len(feature) > 0
10+
11+
12+
def test_read_feature_legacy_shape_is_preserved():
13+
feature = sec.read_feature(sec.PEOPLE)
14+
assert feature == sec.temp_recs[sec.PEOPLE]
15+
16+
17+
def test_read_protocol_builds_richer_protocol_view():
18+
protocol = sec.read_protocol(sec.PEOPLE)
19+
assert protocol is not None
20+
assert protocol.name == sec.PEOPLE
21+
assert protocol.create.login is True
22+
assert sec.is_permitted(sec.PEOPLE, sec.CREATE, user_id='ejc369@nyu.edu')
23+
assert not sec.is_permitted(sec.PEOPLE, sec.CREATE, user_id='someone-else@nyu.edu')
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from flask import Flask
2+
3+
from security.decorators import require_protocol
4+
from server.auth import ROLE_ADMIN, ROLE_USER, create_access_token
5+
6+
7+
def _create_test_app():
8+
app = Flask(__name__)
9+
10+
@app.get("/protected")
11+
@require_protocol("countries", "update")
12+
def protected():
13+
return {"ok": True}, 200
14+
15+
return app
16+
17+
18+
def test_security_decorator_disabled_by_default(monkeypatch):
19+
monkeypatch.delenv("SECURITY_ENFORCEMENT", raising=False)
20+
app = _create_test_app()
21+
22+
with app.test_client() as client:
23+
response = client.get("/protected")
24+
25+
assert response.status_code == 200
26+
27+
28+
def test_security_decorator_blocks_when_enforcement_enabled(monkeypatch):
29+
monkeypatch.setenv("SECURITY_ENFORCEMENT", "true")
30+
app = _create_test_app()
31+
32+
from security.manager import load_legacy_records
33+
load_legacy_records({
34+
"countries": {
35+
"update": {
36+
"user_list": ["alice"],
37+
"checks": {
38+
"login": True,
39+
"allowed_roles": [ROLE_ADMIN],
40+
},
41+
},
42+
},
43+
})
44+
45+
with app.test_client() as client:
46+
response = client.get("/protected")
47+
48+
assert response.status_code == 403
49+
50+
51+
def test_security_decorator_allows_authorized_request(monkeypatch):
52+
monkeypatch.setenv("SECURITY_ENFORCEMENT", "true")
53+
app = _create_test_app()
54+
55+
from security.manager import load_legacy_records
56+
load_legacy_records({
57+
"countries": {
58+
"update": {
59+
"user_list": ["alice"],
60+
"checks": {
61+
"login": True,
62+
"allowed_roles": [ROLE_ADMIN],
63+
},
64+
},
65+
},
66+
})
67+
token = create_access_token("alice", ROLE_ADMIN, expires_hours=1)
68+
69+
with app.test_client() as client:
70+
response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
71+
72+
assert response.status_code == 200
73+
74+
75+
def test_security_decorator_audit_only_does_not_block(monkeypatch):
76+
monkeypatch.setenv("SECURITY_ENFORCEMENT", "true")
77+
monkeypatch.setenv("SECURITY_AUDIT_ONLY", "true")
78+
app = _create_test_app()
79+
80+
from security.manager import load_legacy_records
81+
load_legacy_records({
82+
"countries": {
83+
"update": {
84+
"user_list": ["alice"],
85+
"checks": {
86+
"login": True,
87+
"allowed_roles": [ROLE_ADMIN],
88+
},
89+
},
90+
},
91+
})
92+
token = create_access_token("alice", ROLE_USER, expires_hours=1)
93+
94+
with app.test_client() as client:
95+
response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
96+
97+
assert response.status_code == 200

0 commit comments

Comments
 (0)