TT Lab
Get started
Learn Learning paths Courses

Testing Tools in Practice

An access-control test matrix that finds defects

Continue in TT Lab

Goal

You write negative tests that separate no authentication, no permission, someone else's resource, and a normal request.

Why it matters

An access control test that checked only success responses was green. Even when the route forgot to inject the Header or the scope check was deleted, the test did not break. A verifier must not just list the allowed cases; it has to observe the reason for each rejection separately.

Steps

  1. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: bearer(header) returns the token when the header starts with exactly 'Bearer ' and is followed by one token with no whitespace. None, an empty token, a different scheme, and extra whitespace are ValueError. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Prepare this once at the start. Existing files are not overwritten.

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. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: principal(token, users) returns the user {id, scopes} from the token dictionary, but copies the scopes list as well. An unknown token is ValueError. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  2. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: require_scope(user, scope) returns None when the scope string is exactly present in scopes, and raises PermissionError otherwise. read-all is not read. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  3. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: visible(user, document) is True only when document is not None and owner is exactly equal to the user's id. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  4. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: public_document(document) is a new dictionary that has only id and title. It does not include owner or internal_cost. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  5. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: authenticate(header, users) connects bearer and principal. ValueError becomes HTTPException(401), and the WWW-Authenticate value in headers is Bearer. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  6. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: read_document(user, documents, document_id) raises HTTPException(403) if there is no read scope, HTTPException(404) if the document does not exist or belongs to someone else, and otherwise returns the result of public_document. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

  7. In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: create_app(users, documents) returns a FastAPI app that receives the Authorization header in GET /documents/{document_id} and calls authenticate and read_document. Verify 200, 401, 403, 404, and the removal of private fields with real requests. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Notes

Split the Bearer header — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: bearer(header) returns the token when the header starts with exactly 'Bearer ' and is followed by one token with no whitespace. None, an empty token, a different scheme, and extra whitespace are ValueError. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Prepare this once at the start. Existing files are not overwritten.

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

If you split the header into several pieces arbitrarily, a whitespace error can be accepted as a normal token. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/01-contract.sh.

Copy and return the identity — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: principal(token, users) returns the user {id, scopes} from the token dictionary, but copies the scopes list as well. An unknown token is ValueError. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

If modifying the returned scopes also changes the original user's permissions, permissions get mixed between requests. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/02-contract.sh.

Compare permissions exactly — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: require_scope(user, scope) returns None when the scope string is exactly present in scopes, and raises PermissionError otherwise. read-all is not read. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

A substring comparison mistakes a longer permission name for a different permission. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/03-contract.sh.

Check ownership separately — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: visible(user, document) is True only when document is not None and owner is exactly equal to the user's id. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

A missing resource and a resource owned by someone else are bundled into the same decision. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/04-contract.sh.

Choose response fields with an allow list — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: public_document(document) is a new dictionary that has only id and title. It does not include owner or internal_cost. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Do not delete fields from the original; assemble a new response. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/05-contract.sh.

Match errors to the HTTP contract — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: authenticate(header, users) connects bearer and principal. ValueError becomes HTTPException(401), and the WWW-Authenticate value in headers is Bearer. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Do not lump an authentication failure and an application error together into a single 500. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/06-contract.sh.

Fix the order of rejection — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: read_document(user, documents, document_id) raises HTTPException(403) if there is no read scope, HTTPException(404) if the document does not exist or belongs to someone else, and otherwise returns the result of public_document. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Even after authentication, scope and ownership must each be checked. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/07-contract.sh.

Close the boundary in a real request — test

In /root/work/test-auth-matrix-lab/test_service.py, test the following public contract of the provided service.py: create_app(users, documents) returns a FastAPI app that receives the Authorization header in GET /documents/{document_id} and calls authenticate and read_document. Verify 200, 401, 403, 404, and the removal of private fields with real requests. It must pass against the correct implementation and be caught, through a failure in the body of an actual test, in an implementation that breaks this contract. Keep the tests from the earlier steps and add a test_ function.

Even if the functions are each correct, access control is not applied if the route forgets to call them. Do not modify the implementation file. Use pytest.raises to check the expected exception, and assert a concrete expected value for the normal result.

After saving, check with bash /opt/lab/checks/test-auth-matrix-lab/08-contract.sh.