TT Lab
开始
学习 学习路径 课程

测试工具实战

发现访问控制缺陷的测试矩阵

在 TT Lab 中继续学习

目标

编写负面测试,把“未认证”“无权限”“他人所有”和“正常请求”分开。

为什么重要

只确认成功响应的访问控制测试一直是绿灯。即使在路径中漏掉了 Header 注入,或者删掉了 scope 检查,测试也不会失败。验证者不能只罗列允许的用例,而必须分别观察每一种拒绝的原因。

步骤

  1. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:bearer(header) 在请求头恰好以 'Bearer ' 开头、后面只有一个不含空白的令牌时返回该令牌。None、空令牌、其他 scheme 和多余的空白都会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

先做一次准备。已有的文件不会被覆盖。

mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab
  1. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:principal(token, users) 返回令牌字典中的用户 {id, scopes},并且连 scopes 列表也一并复制。未知令牌会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  2. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:require_scope(user, scope) 在 scopes 中恰好存在该 scope 字符串时返回 None,否则抛出 PermissionError。read-all 不等于 read。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  3. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:visible(user, document) 仅当 document 不是 None,且 owner 与 user 的 id 完全相同时才为 True。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  4. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:public_document(document) 返回一个只含 id 和 title 的新字典。不包含 owner 或 internal_cost。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  5. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:authenticate(header, users) 把 bearer 与 principal 连接起来。ValueError 要变成 HTTPException(401),且 headers 中 WWW-Authenticate 的值为 Bearer。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  6. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:read_document(user, documents, document_id) 在没有 read scope 时抛出 HTTPException(403),文档不存在或属于他人时抛出 HTTPException(404),否则返回 public_document 的结果。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

  7. 在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:create_app(users, documents) 返回一个 FastAPI 应用,它在 GET /documents/{document_id} 上接收 Authorization 请求头,并调用 authenticate 和 read_document。通过真实请求验证 200、401、403、404 以及非公开字段的移除。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

参考

拆分 Bearer 请求头——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:bearer(header) 在请求头恰好以 'Bearer ' 开头、后面只有一个不含空白的令牌时返回该令牌。None、空令牌、其他 scheme 和多余的空白都会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

先做一次准备。已有的文件不会被覆盖。

mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab

如果随意把请求头切成多段,就可能把空白错误当成正常令牌接受。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/01-contract.sh 确认。

复制身份后再返回——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:principal(token, users) 返回令牌字典中的用户 {id, scopes},并且连 scopes 列表也一并复制。未知令牌会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

修改返回的 scopes 时,如果连原始用户的权限也被改变,不同请求之间的权限就会混在一起。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/02-contract.sh 确认。

精确比较权限——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:require_scope(user, scope) 在 scopes 中恰好存在该 scope 字符串时返回 None,否则抛出 PermissionError。read-all 不等于 read。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

子串比较会把更长的权限名误认为另一个权限。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/03-contract.sh 确认。

单独确认所有权——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:visible(user, document) 仅当 document 不是 None,且 owner 与 user 的 id 完全相同时才为 True。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

把资源不存在与他人所有归为同一个判定。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/04-contract.sh 确认。

用白名单挑选响应字段——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:public_document(document) 返回一个只含 id 和 title 的新字典。不包含 owner 或 internal_cost。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

不要从原始数据中删除字段,而是组装一个新的响应。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/05-contract.sh 确认。

让错误符合 HTTP 契约——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:authenticate(header, users) 把 bearer 与 principal 连接起来。ValueError 要变成 HTTPException(401),且 headers 中 WWW-Authenticate 的值为 Bearer。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

不要把认证失败和应用错误笼统地合成一个 500。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/06-contract.sh 确认。

固定拒绝的顺序——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:read_document(user, documents, document_id) 在没有 read scope 时抛出 HTTPException(403),文档不存在或属于他人时抛出 HTTPException(404),否则返回 public_document 的结果。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

即使认证通过之后,也必须分别确认 scope 和所有权。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/07-contract.sh 确认。

在真实请求中封住边界——测试

在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:create_app(users, documents) 返回一个 FastAPI 应用,它在 GET /documents/{document_id} 上接收 Authorization 请求头,并调用 authenticate 和 read_document。通过真实请求验证 200、401、403、404 以及非公开字段的移除。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。

即使各个函数单独都是对的,只要在路径里漏掉了调用,访问控制就不会生效。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。

保存后用 bash /opt/lab/checks/test-auth-matrix-lab/08-contract.sh 确认。