File size: 2,744 Bytes
a7ffb22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6f5bc4
 
 
 
a7ffb22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient

from mathly2.access import GRANTABLE, PERMISSIONS, ROLES, allowed, need, permissions_of

KNOWN = set().union(*PERMISSIONS.values()) - {"*"}


def test_there_are_five_roles_and_developer_is_never_grantable():
    assert ROLES == ("student", "teacher", "manager", "admin", "developer")
    assert "developer" not in GRANTABLE and set(GRANTABLE) < set(ROLES)


@pytest.mark.parametrize("role,yes,no", [
    ("student", {"path.use"}, {"class.teach", "class.roster", "classes.manage", "students.list", "roles.manage", "content.manage"}),
    ("teacher", {"path.use", "class.teach", "class.roster"}, {"classes.manage", "students.list", "roles.manage", "content.manage"}),
    ("manager", {"classes.manage", "class.roster", "students.list", "content.manage"}, {"path.use", "class.teach", "roles.manage"}),
    ("admin", {"roles.manage"}, {"path.use", "class.teach", "class.roster", "classes.manage", "students.list", "content.manage"}),
])
def test_each_role_has_only_its_permissions(role, yes, no):
    for permission in yes:
        assert allowed([role], permission), (role, permission)
    for permission in no:
        assert not allowed([role], permission), (role, permission)


def test_developer_has_every_permission_even_ones_not_invented_yet():
    assert all(allowed(["developer"], p) for p in KNOWN | {"something.new"})


def test_several_roles_add_up():
    assert permissions_of(["manager", "teacher"]) >= {"classes.manage", "class.teach", "path.use"}
    assert not allowed([], "path.use") and permissions_of(["nonsense"]) == frozenset()


def test_manager_and_admin_see_no_learning_data():
    # learning data is the student's path and a teacher's view of their own classes: neither role has them
    for role in ("manager", "admin"):
        assert not allowed([role], "path.use") and not allowed([role], "class.teach")


def test_need_refuses_a_missing_permission_with_403_and_a_missing_sign_in_with_401():
    from mathly2.auth import DevAuthenticator
    app = FastAPI()
    app.state.authenticator = DevAuthenticator()

    class Roles:
        def resolve(self, email, source=None):
            return {"boss@x.test": frozenset({"manager"})}.get(email, frozenset({"student"}))
    app.state.roles = Roles()

    @app.get("/manage")
    def manage(user: dict = Depends(need("classes.manage"))):
        return {"ok": user["email"]}

    client = TestClient(app)
    assert client.get("/manage").status_code == 401
    assert client.get("/manage", headers={"Authorization": "Bearer dev:kid@x.test"}).status_code == 403
    assert client.get("/manage", headers={"Authorization": "Bearer dev:boss@x.test"}).json() == {"ok": "boss@x.test"}