buntu 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {buntu-0.1.0/src/buntu.egg-info → buntu-0.2.0}/PKG-INFO +1 -1
  2. {buntu-0.1.0 → buntu-0.2.0}/pyproject.toml +1 -1
  3. {buntu-0.1.0 → buntu-0.2.0/src/buntu.egg-info}/PKG-INFO +1 -1
  4. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_attacks.py +4 -4
  5. {buntu-0.1.0 → buntu-0.2.0}/tests/test_mfa.py +10 -6
  6. {buntu-0.1.0 → buntu-0.2.0}/LICENSE +0 -0
  7. {buntu-0.1.0 → buntu-0.2.0}/README.md +0 -0
  8. {buntu-0.1.0 → buntu-0.2.0}/setup.cfg +0 -0
  9. {buntu-0.1.0 → buntu-0.2.0}/src/buntu.egg-info/SOURCES.txt +0 -0
  10. {buntu-0.1.0 → buntu-0.2.0}/src/buntu.egg-info/dependency_links.txt +0 -0
  11. {buntu-0.1.0 → buntu-0.2.0}/src/buntu.egg-info/requires.txt +0 -0
  12. {buntu-0.1.0 → buntu-0.2.0}/src/buntu.egg-info/top_level.txt +0 -0
  13. {buntu-0.1.0 → buntu-0.2.0}/src/idp/__init__.py +0 -0
  14. {buntu-0.1.0 → buntu-0.2.0}/src/idp/app.py +0 -0
  15. {buntu-0.1.0 → buntu-0.2.0}/src/idp/cli.py +0 -0
  16. {buntu-0.1.0 → buntu-0.2.0}/src/idp/config.py +0 -0
  17. {buntu-0.1.0 → buntu-0.2.0}/src/idp/db/__init__.py +0 -0
  18. {buntu-0.1.0 → buntu-0.2.0}/src/idp/extensions.py +0 -0
  19. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/__init__.py +0 -0
  20. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/account.py +0 -0
  21. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/clients.py +0 -0
  22. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/grants.py +0 -0
  23. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/platform.py +0 -0
  24. {buntu-0.1.0 → buntu-0.2.0}/src/idp/models/users.py +0 -0
  25. {buntu-0.1.0 → buntu-0.2.0}/src/idp/oauth/__init__.py +0 -0
  26. {buntu-0.1.0 → buntu-0.2.0}/src/idp/oauth/claims.py +0 -0
  27. {buntu-0.1.0 → buntu-0.2.0}/src/idp/oauth/grants.py +0 -0
  28. {buntu-0.1.0 → buntu-0.2.0}/src/idp/oauth/jwt.py +0 -0
  29. {buntu-0.1.0 → buntu-0.2.0}/src/idp/oauth/server.py +0 -0
  30. {buntu-0.1.0 → buntu-0.2.0}/src/idp/plugins/__init__.py +0 -0
  31. {buntu-0.1.0 → buntu-0.2.0}/src/idp/plugins/sync.py +0 -0
  32. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/__init__.py +0 -0
  33. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/crypto.py +0 -0
  34. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/csrf.py +0 -0
  35. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/data/breached.txt.gz +0 -0
  36. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/keys.py +0 -0
  37. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/passwords.py +0 -0
  38. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/tokens.py +0 -0
  39. {buntu-0.1.0 → buntu-0.2.0}/src/idp/security/totp.py +0 -0
  40. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/__init__.py +0 -0
  41. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/accounts.py +0 -0
  42. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/activity.py +0 -0
  43. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/audit.py +0 -0
  44. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/backchannel.py +0 -0
  45. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/closing.py +0 -0
  46. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/connected.py +0 -0
  47. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/consent.py +0 -0
  48. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/credentials.py +0 -0
  49. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/devices.py +0 -0
  50. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/mail.py +0 -0
  51. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/mfa.py +0 -0
  52. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/own_apps.py +0 -0
  53. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/passkeys.py +0 -0
  54. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/platform.py +0 -0
  55. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/profile.py +0 -0
  56. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/retention.py +0 -0
  57. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/security_prompt.py +0 -0
  58. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/session_check.py +0 -0
  59. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/sessions.py +0 -0
  60. {buntu-0.1.0 → buntu-0.2.0}/src/idp/services/setup.py +0 -0
  61. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/account-D-MHfC1z.js +0 -0
  62. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/account-DN_Addf0.js +0 -0
  63. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/app-CzfYFveQ.js +0 -0
  64. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/di-D0iZ5SU4.js +0 -0
  65. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/di-dx_XE3Av.js +0 -0
  66. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  67. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  68. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  69. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  70. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  71. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  72. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  73. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  74. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  75. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  76. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  77. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/i18n-BZyjOlbu.js +0 -0
  78. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/i18n-Dog5o8z6.js +0 -0
  79. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/roboto-latin-variable-ccAYIvAh.woff2 +0 -0
  80. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/schemas-Bl_iabpv.js +0 -0
  81. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/secure-account-Dl1pS_Wo.js +0 -0
  82. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/service-BhG4-X2O.js +0 -0
  83. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/service-vCuiuvPs.js +0 -0
  84. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/theme-toggle-C07m3YIv.js +0 -0
  85. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/two-step-section-DMQmuG2q.js +0 -0
  86. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/assets/webauthn-BQas4tAd.js +0 -0
  87. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/idp.css +0 -0
  88. {buntu-0.1.0 → buntu-0.2.0}/src/idp/static/idp/idp.js +0 -0
  89. {buntu-0.1.0 → buntu-0.2.0}/src/idp/templates/shell.html +0 -0
  90. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/__init__.py +0 -0
  91. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/admin.py +0 -0
  92. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/api.py +0 -0
  93. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/developer.py +0 -0
  94. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/oidc.py +0 -0
  95. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/openapi.py +0 -0
  96. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/pages.py +0 -0
  97. {buntu-0.1.0 → buntu-0.2.0}/src/idp/web/validation.py +0 -0
  98. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_area.py +0 -0
  99. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_closing.py +0 -0
  100. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_connected.py +0 -0
  101. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_profile.py +0 -0
  102. {buntu-0.1.0 → buntu-0.2.0}/tests/test_account_security.py +0 -0
  103. {buntu-0.1.0 → buntu-0.2.0}/tests/test_app_overview.py +0 -0
  104. {buntu-0.1.0 → buntu-0.2.0}/tests/test_authorization_code.py +0 -0
  105. {buntu-0.1.0 → buntu-0.2.0}/tests/test_authorization_code_attacks.py +0 -0
  106. {buntu-0.1.0 → buntu-0.2.0}/tests/test_backchannel_logout.py +0 -0
  107. {buntu-0.1.0 → buntu-0.2.0}/tests/test_boundary_attacks.py +0 -0
  108. {buntu-0.1.0 → buntu-0.2.0}/tests/test_brand.py +0 -0
  109. {buntu-0.1.0 → buntu-0.2.0}/tests/test_cli.py +0 -0
  110. {buntu-0.1.0 → buntu-0.2.0}/tests/test_console_api.py +0 -0
  111. {buntu-0.1.0 → buntu-0.2.0}/tests/test_console_users.py +0 -0
  112. {buntu-0.1.0 → buntu-0.2.0}/tests/test_credentials.py +0 -0
  113. {buntu-0.1.0 → buntu-0.2.0}/tests/test_data_retention.py +0 -0
  114. {buntu-0.1.0 → buntu-0.2.0}/tests/test_deployment.py +0 -0
  115. {buntu-0.1.0 → buntu-0.2.0}/tests/test_developer_api.py +0 -0
  116. {buntu-0.1.0 → buntu-0.2.0}/tests/test_flows.py +0 -0
  117. {buntu-0.1.0 → buntu-0.2.0}/tests/test_grant_attacks.py +0 -0
  118. {buntu-0.1.0 → buntu-0.2.0}/tests/test_invitations.py +0 -0
  119. {buntu-0.1.0 → buntu-0.2.0}/tests/test_members.py +0 -0
  120. {buntu-0.1.0 → buntu-0.2.0}/tests/test_offline_access.py +0 -0
  121. {buntu-0.1.0 → buntu-0.2.0}/tests/test_openapi.py +0 -0
  122. {buntu-0.1.0 → buntu-0.2.0}/tests/test_own_apps.py +0 -0
  123. {buntu-0.1.0 → buntu-0.2.0}/tests/test_passkeys.py +0 -0
  124. {buntu-0.1.0 → buntu-0.2.0}/tests/test_platform_gates.py +0 -0
  125. {buntu-0.1.0 → buntu-0.2.0}/tests/test_platform_organization.py +0 -0
  126. {buntu-0.1.0 → buntu-0.2.0}/tests/test_plugin_attacks.py +0 -0
  127. {buntu-0.1.0 → buntu-0.2.0}/tests/test_plugins.py +0 -0
  128. {buntu-0.1.0 → buntu-0.2.0}/tests/test_review_rules.py +0 -0
  129. {buntu-0.1.0 → buntu-0.2.0}/tests/test_reviews.py +0 -0
  130. {buntu-0.1.0 → buntu-0.2.0}/tests/test_role_matrix.py +0 -0
  131. {buntu-0.1.0 → buntu-0.2.0}/tests/test_scope_rules.py +0 -0
  132. {buntu-0.1.0 → buntu-0.2.0}/tests/test_security_prompt.py +0 -0
  133. {buntu-0.1.0 → buntu-0.2.0}/tests/test_session_attacks.py +0 -0
  134. {buntu-0.1.0 → buntu-0.2.0}/tests/test_session_check.py +0 -0
  135. {buntu-0.1.0 → buntu-0.2.0}/tests/test_setup.py +0 -0
  136. {buntu-0.1.0 → buntu-0.2.0}/tests/test_token_attacks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buntu
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: OAuth 2.1 and OpenID Connect provider
5
5
  Author: Ibrahima Touré
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "buntu"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "OAuth 2.1 and OpenID Connect provider"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buntu
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: OAuth 2.1 and OpenID Connect provider
5
5
  Author: Ibrahima Touré
6
6
  License-Expression: MIT
@@ -7,7 +7,7 @@ from typing import Any
7
7
 
8
8
  from flask import Flask
9
9
  from flask.testing import FlaskClient
10
- from test_mfa import enable, password_step
10
+ from test_mfa import enable, password_step, wrong_code
11
11
 
12
12
  from idp.security import totp
13
13
  from idp.services import mail
@@ -34,7 +34,7 @@ def test_replaying_an_old_cookie_does_not_reset_the_two_step_limit(
34
34
  cookie = other.get_cookie(COOKIE)
35
35
  assert cookie is not None
36
36
  for _ in range(8):
37
- api(_with_cookie(app, cookie.value), "POST", "/api/mfa/verify", {"code": "000000"}, csrf)
37
+ api(_with_cookie(app, cookie.value), "POST", "/api/mfa/verify", {"code": wrong_code(secret)}, csrf)
38
38
  good = totp.code_at(secret, totp.current_step() + 1) # the setup used the current step
39
39
  last = api(_with_cookie(app, cookie.value), "POST", "/api/mfa/verify", {"code": good}, csrf)
40
40
  assert last.status_code == 400 and last.json["error"] == "too_many_attempts", last.json
@@ -47,7 +47,7 @@ def test_the_two_step_limit_also_holds_across_fresh_password_steps(
47
47
  for _ in range(2):
48
48
  other, csrf, _ = password_step(app)
49
49
  for _ in range(5):
50
- api(other, "POST", "/api/mfa/verify", {"code": "000000"}, csrf)
50
+ api(other, "POST", "/api/mfa/verify", {"code": wrong_code(secret)}, csrf)
51
51
  other, csrf, _ = password_step(app)
52
52
  good = totp.code_at(secret, totp.current_step() + 1)
53
53
  late = api(other, "POST", "/api/mfa/verify", {"code": good}, csrf)
@@ -67,7 +67,7 @@ def test_the_account_owner_gets_in_again_after_the_pause(
67
67
  secret, _ = enable(browser, sign_up(browser, outbox))
68
68
  other, csrf, _ = password_step(app)
69
69
  for _ in range(5):
70
- api(other, "POST", "/api/mfa/verify", {"code": "000000"}, csrf)
70
+ api(other, "POST", "/api/mfa/verify", {"code": wrong_code(secret)}, csrf)
71
71
  with app.app_context():
72
72
  db().execute(update(User).values(second_factor_failed_at=utcnow() - timedelta(minutes=16)))
73
73
  db().commit()
@@ -31,14 +31,18 @@ def test_a_code_works_once_and_only_inside_the_window() -> None:
31
31
  assert totp.matching_step(secret, old, None, now) is None
32
32
 
33
33
 
34
+ def wrong_code(secret: str) -> str:
35
+ """A code no step around now produces, so a refusal never depends on chance or the clock."""
36
+ here = totp.current_step()
37
+ taken = {totp.code_at(secret, step) for step in range(here - 2, here + 3)}
38
+ return next(c for c in ("000000", "111111", "222222", "333333", "444444", "555555") if c not in taken)
39
+
40
+
34
41
  def enable(browser: FlaskClient, csrf: str) -> tuple[str, list[str]]:
35
42
  setup = api(browser, "POST", "/api/mfa/totp", {}, csrf).json
36
43
  assert setup["qr"].startswith("data:image/svg+xml") and setup["uri"].startswith("otpauth://totp/")
37
- wrong = api(browser, "POST", "/api/mfa/totp/confirm", {"code": "000000"}, csrf)
38
- assert (
39
- wrong.json == {"error": "invalid_code"}
40
- or totp.code_at(setup["secret"], totp.current_step()) == "000000"
41
- )
44
+ wrong = api(browser, "POST", "/api/mfa/totp/confirm", {"code": wrong_code(setup["secret"])}, csrf)
45
+ assert wrong.json == {"error": "invalid_code"}
42
46
  code = totp.code_at(setup["secret"], totp.current_step())
43
47
  done = api(browser, "POST", "/api/mfa/totp/confirm", {"code": code}, csrf)
44
48
  assert done.status_code == 200, done.json
@@ -99,7 +103,7 @@ def test_five_wrong_codes_start_over(app: Flask, browser: FlaskClient, outbox: l
99
103
  def test_disabling_needs_a_code(app: Flask, browser: FlaskClient, outbox: list[mail.Message]) -> None:
100
104
  csrf = sign_up(browser, outbox)
101
105
  secret, _ = enable(browser, csrf)
102
- assert api(browser, "POST", "/api/mfa/disable", {"code": "000001"}, csrf).status_code == 400
106
+ assert api(browser, "POST", "/api/mfa/disable", {"code": wrong_code(secret)}, csrf).status_code == 400
103
107
  code = totp.code_at(secret, totp.current_step() + 1)
104
108
  assert api(browser, "POST", "/api/mfa/disable", {"code": code}, csrf).json == {"enabled": False}
105
109
  _, _, reply = password_step(app)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes