test_task_tokens.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import pytest
  2. from app.runner.auth import (
  3. TaskTokenExpired,
  4. TaskTokenInvalid,
  5. TaskTokenIssuer,
  6. TaskTokenVerifier,
  7. node_digest,
  8. )
  9. NODE = {
  10. "id": "read_orders",
  11. "type": "sql.query",
  12. "data_source_uid": "01900000-0000-7000-8000-000000000010",
  13. "purpose": "read",
  14. "config": {
  15. "statement": "SELECT * FROM orders WHERE day = :day",
  16. "parameters": {"day": "${parameters.day}"},
  17. },
  18. }
  19. def test_task_token_is_short_lived_and_bound_to_one_node():
  20. def clock():
  21. return 1_000
  22. issuer = TaskTokenIssuer("x" * 32, clock=clock, ttl_seconds=60)
  23. verifier = TaskTokenVerifier("x" * 32, clock=clock)
  24. token = issuer.issue(
  25. task_uid="01900000-0000-7000-8000-000000000011",
  26. dataflow_uid="01900000-0000-7000-8000-000000000012",
  27. workflow_version=7,
  28. correlation_id="01900000-0000-7000-8000-000000000013",
  29. node=NODE,
  30. )
  31. claims = verifier.verify(token, node=NODE)
  32. assert claims.node_id == "read_orders"
  33. assert claims.node_type == "sql.query"
  34. assert claims.purpose == "read"
  35. assert claims.node_digest == node_digest(NODE)
  36. assert claims.expires_at == 1_060
  37. assert claims.jti
  38. assert "password" not in token.lower()
  39. def test_task_token_rejects_expiry_tampering_and_other_node():
  40. now = [1_000]
  41. issuer = TaskTokenIssuer("x" * 32, clock=lambda: now[0], ttl_seconds=10)
  42. verifier = TaskTokenVerifier("x" * 32, clock=lambda: now[0])
  43. token = issuer.issue(
  44. task_uid="01900000-0000-7000-8000-000000000011",
  45. dataflow_uid="01900000-0000-7000-8000-000000000012",
  46. workflow_version=7,
  47. correlation_id="01900000-0000-7000-8000-000000000013",
  48. node=NODE,
  49. )
  50. changed = {**NODE, "config": {"statement": "DELETE FROM orders"}}
  51. with pytest.raises(TaskTokenInvalid, match="node binding"):
  52. verifier.verify(token, node=changed)
  53. header, payload, signature = token.split(".")
  54. changed_signature = ("A" if signature[0] != "A" else "B") + signature[1:]
  55. with pytest.raises(TaskTokenInvalid):
  56. verifier.verify(".".join((header, payload, changed_signature)), node=NODE)
  57. now[0] = 1_011
  58. with pytest.raises(TaskTokenExpired):
  59. verifier.verify(token, node=NODE)
  60. def test_task_token_secret_has_a_minimum_strength():
  61. with pytest.raises(ValueError, match="at least 32"):
  62. TaskTokenIssuer("too-short")