Skip to content

Commit 31fa54a

Browse files
authored
Merge pull request #172 from duo-labs/mm/add-profile-debug-info
Add "debug" mode for opt-in to more details
2 parents 76a0bb9 + 008f3d5 commit 31fa54a

File tree

11 files changed

+349
-22
lines changed

11 files changed

+349
-22
lines changed

_app/homepage/cookies.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import datetime
2+
3+
4+
def get_debug_cookie_name() -> str:
5+
"""
6+
Return the name of the debug cookie that reveals more info in various places
7+
"""
8+
return "debug"
9+
10+
11+
def get_debug_cookie_value() -> str:
12+
"""
13+
Keeping things simple for now
14+
"""
15+
return "true"
16+
17+
18+
def get_debug_cookie_expiration() -> datetime:
19+
"""
20+
Expires one year from now
21+
"""
22+
_now = datetime.now()
23+
return datetime(
24+
year=(_now.year + 1),
25+
month=_now.month,
26+
day=_now.day,
27+
)

_app/homepage/services/session.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
1-
from django.http import HttpRequest
1+
from django.contrib.sessions.backends.base import SessionBase
22

33

44
class SessionService:
5-
def start_session(self, *, request: HttpRequest) -> None:
5+
def start_session(self, *, session: SessionBase) -> None:
66
"""
77
Start a session so that we have a unique ID we can associate options with, even when
88
we don't have a username
99
"""
10-
if not request.session.exists(request.session.session_key):
11-
request.session.create()
12-
request.session.set_expiry(0)
10+
if not session.exists(session.session_key):
11+
session.create()
12+
session.set_expiry(0)
1313

14-
def log_in_user(self, *, request: HttpRequest, username: str) -> None:
14+
def log_in_user(self, *, session: SessionBase, username: str) -> None:
1515
"""
1616
Use a session cookie to temporarily remember the user
1717
"""
18-
request.session["username"] = username
18+
session["username"] = username
19+
session.save()
1920

20-
def log_out_user(self, *, request: HttpRequest) -> None:
21+
def log_out_user(self, *, session: SessionBase) -> None:
2122
"""
2223
Annihilate the user's session so we can forget about them
2324
"""
24-
request.session.flush()
25+
session.flush()
2526

26-
def user_is_logged_in(self, *, request: HttpRequest) -> bool:
27+
def user_is_logged_in(self, *, session: SessionBase) -> bool:
2728
try:
28-
username = request.session["username"]
29+
username = session["username"]
2930
return username is not None
3031
except KeyError:
3132
pass
3233

3334
return False
3435

35-
def get_session_key(self, *, request: HttpRequest) -> str:
36-
key = request.session.session_key
36+
def get_session_key(self, *, session: SessionBase) -> str:
37+
key = session.session_key
3738

3839
if not key:
3940
raise Exception("Attempted to get session key before session was created")

_app/homepage/templates/homepage/profile.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ <h4>{{ cred.id }}</h4>
8484
<br>
8585
<strong>AAGUID:</strong> {{ cred.aaguid }}
8686
</li>
87+
{% if cred.debug_info %}
88+
<li>
89+
<strong>Debug Information:</strong>
90+
<br>
91+
<strong>Public Key Type:</strong> {{ cred.debug_info.public_key.kty }}
92+
<br>
93+
<strong>Public Key Algorithm:</strong> {{ cred.debug_info.public_key.alg }}
94+
</li>
95+
{% endif %}
8796
<li class="credential-delete">
8897
<button type="button" class="btn btn-outline-danger" onclick="deleteCredential('{{cred.raw_id}}')">Delete</button>
8998
</li>

_app/homepage/tests/e2e_ml_dsa.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from django.test import TestCase
2+
from django.urls import reverse
3+
from django.conf import settings
4+
5+
from webauthn.helpers import base64url_to_bytes
6+
7+
from homepage.services import AuthenticationService, CredentialService, RegistrationService
8+
9+
10+
class TestE2EMLDSA(TestCase):
11+
"""
12+
End-to-end tests of support for authenticators using ML-DSA algorithms
13+
"""
14+
15+
authentication_service = AuthenticationService()
16+
credential_service = CredentialService()
17+
registration_service = RegistrationService()
18+
19+
def setUp(self):
20+
settings.RP_ID = "webauthn.io"
21+
settings.RP_EXPECTED_ORIGIN = "https://webauthn.io"
22+
# Initialize a session
23+
self.client.get(reverse("index"))
24+
25+
def test_supports_ml_dsa_44(self):
26+
username = "mmiller"
27+
# Generate registration options
28+
self.client.post(
29+
reverse("registration-options"),
30+
{
31+
"username": username,
32+
"user_verification": "preferred",
33+
"attestation": "direct",
34+
"attachment": "platform",
35+
"algorithms": ["mldsa44"],
36+
"discoverable_credential": "required",
37+
},
38+
content_type="application/json",
39+
)
40+
41+
# Set an expected challenge
42+
options = self.registration_service._get_options(username)
43+
assert options
44+
options.challenge = base64url_to_bytes(
45+
"4l8disV6VitGCg_EJvCNx7V92QLtBn_RYq9tTBEZ7j4B5hZI9kijJ2InLxQNRYVlgLROF3nfj80Yi7MPhXQwjw"
46+
)
47+
self.registration_service._save_options(username, options)
48+
49+
# Verify registration response
50+
reg_response = self.client.post(
51+
reverse("registration-verification"),
52+
{
53+
"username": "mmiller",
54+
"response": {
55+
"id": "-EM9FDFIdFVeqWdTycRjoZVN2ZS4vnVE-MBpg7k0pl4jpuqj4GnMCW3Wqlm2WWI2PQ",
56+
"rawId": "-EM9FDFIdFVeqWdTycRjoZVN2ZS4vnVE-MBpg7k0pl4jpuqj4GnMCW3Wqlm2WWI2PQ",
57+
"response": {
58+
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkFoHSm6pITyZwvdLIkkrMgz0AmKpTBqVCgOX8pJQtghB7wxQAAAAQAAAAAAAAAAAAAAAAAAAAAADH4Qz0UMUh0VV6pZ1PJxGOhlU3ZlLi-dUT4wGmDuTSmXiOm6qPgacwJbdaqWbZZYjY9owEHAzgvIFkFIC4AIUrgARve17AEk0W30POluaL08p91eLXkktSjmAlmZdNTWhtUFj3wkseZEt4xpmWarG28Za86i7yq-B4df3uOuq3zQVTKOQUWJLWGJ3-wUUuyywPtkdgSqzQdcli6xMgwnVqh9r6FVL9Xp7x3kgjUVDqhux_k1D2d4ts2zqi1rUrSF6FNX139g3dd1VnUNQrMLdrwohR9CmE0fZ6Am4Df_OV2JxOrUEPzMFi5SeBcrU1oSj2lX_91gY179PO0wIOtTa1KzWvwOYa_KjOj9Ow16AtmsXrcpL-jYW4_bFn4kpT9G-vDG4qPFDpint62g0DDjEt7JrF288aIZXOpsbVmnjw2_O_5pFFvFpH32gD7_NdmvE6PSymNxPcTCnMzY3xv5wJXiEDhO21E85n78Oay4k7PzWHvzQxlJldIYw-9TfKZXqZa6sIbE-LyZj_Y2FV1Owd4WLvKCNcO-IIP3XFcZ7__XPZtAsBTJ5Z5w18jRnlMNKTygva-F2Ec65tA2skED9PnVyS_WjtZN5VjbhuU-D9DIDXEgUjitdcXWbCruDjxaBwjuDFXOI9cYdp4n-KWCZGJdX9QFHDGkvX6zDXupFrFV1q1JeKCayuMJjL3Z44AMF2UtjNODzhlviE8neX5NSfXdf36FWGFER6D6YCGGvooW8EBCx8OLPRNGGwoKBrEflr_ISYIdyw8-rDkAG0-bka_ulzfg8uTY8BXNu0HhqsteUPni4HlhUXMb0yI1DbLi5hTTkpBEBfmjzTJ8JMDe9sOOaqU2PrOzvIs5c7fx_VBqQZbF6amei2Y41okZJWwW0LWNvL2JQ_Yj9deHMczichCHWVX3uCL-SfPL3AaLeWLPjTAejU-H1Lnn2jWQeHtiRxBL1eleZNmJVqFbrgclcMXirM6rrmPrsbFe41fDF3Hm1KgcKkpZMPSICijfDCT4csVeLDxmsg9aDYwboxigOVHZa-zAmePLBZrPJIWDNEHNBG9CdEG-RfeshvnRbPerB1zLzA9jP-Jj55_Xd4igau4FEc7dLWgyn2b2Q3aMAaDnKCzEScd301WeuZtutm6flzqDPCUTJnoniUHuO__bALWkzIxe5rHW6wQ_wPBEX32bQNN-gtI6_yiw-UTwu3egro3tDp7ZzHkMSslF9FHD7divbmeEzsE8N4iOwO5kWFt2jY9VpjGXAhyCcGZtWU68SzllOpWzvuacFjlE5KZ_c4nHhYdaphJAjXvbkog-vGUwjffCXe9gQhIliwPzREtccZdgyLKiBAlypp0pwVKe6disU9-2kflk_BXPRf1PkBEqO41ySFZWLb6eSij9FrIXtPAo4RFmeKPLoYT-ce3gi8_XftVv7MDl9s0hoFlgh5vTh1xMdpxEt-6BxdesEF3zJycxNY4QFVkUKE78geXogQFz2QE7kW4ncTXjq4IydHOKX9Bp2P8uGcCJ6dzW3PFE-Zurf1klV-rkvT7xE-Tds7CPeWkrRr_Ckhn6rQ2Z3-Sjz5bgIRHiBnd0iZfm6ZgD77nVHY7ztaSmUQ7JWbeFSz0eoYExgXi7HfSdV77DlHxIjcNlrSh58SGWfkSwUVboOUJKy_B3EbBDeweqn1pf7QIjAJnYL7WiogmAku2UxEBQijtPAusmyhLf0_aTEFFc3zdGutHim3dzAKfJucy2aBm8ViQxY_U1N26WVO6sfui7dZVhqkQniZLCq8N_xqEMqWV6utksRHOvITvB_SqmeDacy2ZfiSogU8K5G2ha2NyZWRQcm90ZWN0Ag",
59+
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiNGw4ZGlzVjZWaXRHQ2dfRUp2Q054N1Y5MlFMdEJuX1JZcTl0VEJFWjdqNEI1aFpJOWtpakoySW5MeFFOUllWbGdMUk9GM25majgwWWk3TVBoWFF3anciLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
60+
},
61+
"type": "public-key",
62+
"clientExtensionResults": {},
63+
},
64+
},
65+
content_type="application/json",
66+
)
67+
68+
self.assertEquals(reg_response.json(), {"verified": True})
69+
70+
# Request authentication options
71+
self.client.post(
72+
reverse("authentication-options"),
73+
{
74+
"username": username,
75+
"user_verification": "preferred",
76+
},
77+
content_type="application/json",
78+
)
79+
80+
# Set an expected challenge
81+
options = self.authentication_service._get_options(
82+
cache_key=self.client.session.session_key,
83+
)
84+
assert options
85+
options.challenge = base64url_to_bytes(
86+
"Ji15971jSESa9haCUYb7s_pMhV8DNNwYT8Wb5zbEo151Ab7s_MuT-_MIjnousfaF2Q3emFAx7GkpXkTUmMicTQ"
87+
)
88+
self.authentication_service._save_options(
89+
cache_key=self.client.session.session_key,
90+
options=options,
91+
)
92+
93+
# Verify authentication response
94+
auth_response = self.client.post(
95+
reverse("authentication-verification"),
96+
{
97+
"username": username,
98+
"response": {
99+
"id": "-EM9FDFIdFVeqWdTycRjoZVN2ZS4vnVE-MBpg7k0pl4jpuqj4GnMCW3Wqlm2WWI2PQ",
100+
"rawId": "-EM9FDFIdFVeqWdTycRjoZVN2ZS4vnVE-MBpg7k0pl4jpuqj4GnMCW3Wqlm2WWI2PQ",
101+
"response": {
102+
"authenticatorData": "dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAACA",
103+
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiSmkxNTk3MWpTRVNhOWhhQ1VZYjdzX3BNaFY4RE5Od1lUOFdiNXpiRW8xNTFBYjdzX011VC1fTUlqbm91c2ZhRjJRM2VtRkF4N0drcFhrVFVtTWljVFEiLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0",
104+
"signature": "e7L-Xli-2lj9ZlP2s26sbrvFGLkVrz74BZnDsLW-7HOhj7AcEl5Zgtm3VLvLtcrfqyKE0PTuFrswsikm7t6ddhxXphxWcSo4ggarl6ODQk8NdPCYoFhoK8qwpqKZKmJAl9xDsJE1HAudrWLgq_747JV4QmGLizK0_oJgGM7WLd5xVYvKsl14odBFjU_ZBCrjB0UHIMg8aAq1727yZnY1eiNeF_sEmci_pigYCo-MbxbHmQWPp-U75sGSPPfK0soN2-29_aIxRO4Fg8P37WrwVUrEFdG2PFNgAhcM-ljjyv3mkCfsLUiQNuS-a0cn6MeygREc2HBwE6ChS351-dpNTbkfnb-o1fA6suP1sh3-i7YZrEn9e2J7UZxIAEJPpmuKxYFA4Fj0lAGUhi3lvnkWPOnS8BUjPr5q5z5iEbyL4MokDP75G723Tyy-5L8u1pLmlSLiuwvuW5MBkEhjVVj0RpVSnCoqzwE9A9ZmqZx5wv5gQEi4hAA64mSoXGdUZ5EGkPGrrIDjGyIOrLjuHOSZ6hjyioZvMA1nCQJ76oaL5-Pn1FR1VTurI5ccTWrDAo5sHuo8uGjx9bsyy_aMT3Nzosu29PArTm0AkJFd7INXky0L9itCmhujSnali9zTO8UuwV0G8sZeB2BG4VZN2nkjT1Ib8VeBnSMTlIFVOI2JHlD9kePZuV3nCuAvK8j5qH5OPJoEeJzuxGHP2k7f0941kzyW9sjBaD90HEusVGGgST0qWigEKU3kKaO_Du5ZngcqlmKnnFVKXQk5mEV5nFs9ia76sKe9FKYUp_ZxsVFATcjXETW5GuXF1qIj0ZTCSmhn8V_cqEH29fQWyy9qxNa6kkKc4koZUP36B-h5yVawIDdfyjl-VueUvUyEunPv1EyqXQaf9bo-WThxD_5v3Bd2sYTOI__0PIsUvCASjZJMQU4jpwyXoR2EsLWDRD4fsAxLmdao0iXNxdlH0Ys2MqkXkkMbIylccEHkFjbm_VB5tPYQkFRqqRX13KUyYqqTpaT6MdD8IpltlzJxcNLd9mazUvOfSaf5ho2FFtv8TMubekKU8b92MoPQpjeS1DJ9y2pvMrtIiZP0Lm0WTeniN6luRfUN-4v5GU6FPajkOPLNV9OXJKLREhrA_SvbDldSF9RtWZOqIk1WeTlbnEWlejtwWFoLSScCSfExu6bu_vv9NKK-E8mTWF8_f4bCvlZQp58BEsTHrZuBiQzH34Z5wPeOZuuQlqbAquIS4_W6z_XmNW-d4FhIp2U3y9sYC7wpo1M7N7MB3HKwAliPVgsNHBRI4ZLZ-dL3FCyCMThKJqQMNrMcRif_Mm5Du--Atjn1UH2u2gAxiBA6IY7uSlSn-OJEO-m8qeif8zsdvVhXtJMxNAWZHhQ4QuRFgjuDgxy1nVuhGHdmXi6tseiiC2NQ9iqGuBRetexfz84R93RVSbKMkYlvBU1KPe8ARVf_N52C1KC9F2b3Uo5To9iD2lXShcsGkQkcAX5gjmhy4jrmTv5-pUJYAHa6A9Vorr59D7-Y-CVvVX59YJB9-kMT8wzHQdj2XimbcLKnS5Z4BKsMMEIt01LVkdHcBP9tKBQ20e-Kmf25wsUr9TqFa7ukQEhfLwgflIBbobAJoGKFC2_3fIKaEBuoAOoErASxPClLNAbBqG1JAdrAq9Ki3WC46aN4b-Q6ykfbk2azLAxOzFftJuhLWGLLCkOxbxjfaUrRJ51h8Dwrpy2xBT1qWurNnfFTrzScouK-R8G4SfsyaSiejiaLYLsWZVCcpeH_S8cqQuBFCMpQfxiPn2reOgMFhSzbdSDkzwUTQsjGq94QTs7bdS0LlyRb4OUa0s3szGSIa7n4vQ10uc9gzHlDxEqgaKSpPlVDyvZs58GC3PCZ2HJiiVbuc508_rV1xuydd7asPlyaOAMXImKPxp7d6rAGLbDOQOKS4U9sr6wKQVPnfPqg7TvmtuXTJS5Y_M6mutU7Bn8y6qmbjt5EtoETTSORHfx71ySMLZ8zxveJdsaNow6lfjvI0myk8oSDIucRar1j9G2m13B3K2Kr0URBweH6JkJz3Z7mYFTe09B6GMzIOcPoaYzzJP_PSrpuAfvb6V0AwVCX_HixF2qkCvcdrLyvGaGkkkYh32T2Renu7QWj8Wz06MvimWYCA4pB8SPJpjyw8mNZHOWJXgkI8hgD90O_rDF8mhEIMbDtfTZdOPjekS1a7-LNUGM6ajWLzDehU5YQBzTuGwgoPd2RV0E68iYR6QplHTmhh5vToa7eHvbQrYn8NUzJ6CP5YXcoxl7H9HsQ3AXDHmCtZ3e5p4FLV-Lz64_hVJWaTLOgHecFGAFqXMmnp1BtoKlzwbMnXaFMVaT1T7CkC_XZsoggQA1WFO3vFuXpnw4D6BPNGTmEZrEmINmfBVeFHB4SHDPJXDYX4wwTK8kgUpCHSI8ozIYFy0nw4uJqhkAYjXnvbEeCPsPkf7SPGS7xujgIdlYbtizeg-op2ZyI020Jt-hx2GogXRD_bsNcHaToWZ90fTI8M_Y1-F8iMJG4OnxinHlHnTj7R6wRuM2AZ4-Ov_yhd9w6yXenoKh56RReHCbYAvGCt3aDDyOrcX9WX7VePrBHH3C9ubCwj3PNcuP16or5ho6XRNlXC1s63J99dgi42FWatXeYdUvvcmK7fKxFZWSCXko-cArTT1KqgucxXg8wMk7gaGSwfb2j3pNt1hf4y5MOJQ-HbS0uhuywUbBiHBe8ns6FzUJpM4T8sNXAfbslBk1nIYU9BCMn1Veqw8puCYcwkjWJgxrKU_d2Jx0b8DKpIbbdFKkrdR4vAGRTJ74IgWPuk7wZTSWCddJAB4Q1PU1nbXO3MsxxzlQrWXY1jD9Zp3E69NEUss4qMTT6u5W-RG6RB6ge6sOt47l40v3IO-1LgsCwJtFyQzks0msArf0MSSQ9HreubNnjYqaMOqgUleX4-a0P8BhQwOhwt7C6zyGCnbcBiQx0RWAs0mvf-k5mgqn9Ij5mpGoGVV5L4OhBdY6pm51h7v02bgoWlzUzZHImgBhQtkx0jlBM9XeCIo4t8EQ4ZbqGmLsbj3CTu7KZbc9uQJkWyXSov2WWMvzZfOgCHbizXOGazd47v44BDRMXIiUmJzI1ZXiFkqPB0NftBw80ZnCHlpeq1Nzq8_QQHiM2R1SEsc_T1QQcSlJTVmBpdnqesbv9AAAAAAAAAAAAAAAAAAAAAAAAAAAAABMhLDo",
105+
"userHandle": "d2ViYXV0aG5pby1tbC1kc2EtNDQ",
106+
},
107+
"type": "public-key",
108+
"clientExtensionResults": {},
109+
},
110+
},
111+
content_type="application/json",
112+
)
113+
114+
self.assertEquals(auth_response.json(), {"verified": True})
115+
116+
# Clean up
117+
self.credential_service.delete_credential_by_id(
118+
credential_id="-EM9FDFIdFVeqWdTycRjoZVN2ZS4vnVE-MBpg7k0pl4jpuqj4GnMCW3Wqlm2WWI2PQ"
119+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.test import TestCase
2+
from django.urls import reverse
3+
from django.http.response import HttpResponse
4+
5+
6+
class TestViewIndex(TestCase):
7+
route = reverse("index")
8+
9+
def tearDown(self) -> None:
10+
self.client.cookies.clear()
11+
12+
def test_renders_page(self) -> None:
13+
response = self.client.get(self.route)
14+
self.assertEqual(response.status_code, HttpResponse.status_code)
15+
self.assertEqual(response.headers.get("content-type"), "text/html; charset=utf-8")
16+
17+
def test_debug_query_param_sets_debug_cookie(self) -> None:
18+
# The user can set the "?debug=true" query param to get the "debug" cookie
19+
self.client.get(self.route, QUERY_STRING="debug=true")
20+
21+
cookie_debug = self.client.cookies.get("debug")
22+
self.assertIsNotNone(cookie_debug)
23+
assert cookie_debug # for mypy
24+
self.assertEqual(cookie_debug.value, "true")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from django.test import TestCase
2+
from django.urls import reverse
3+
from webauthn.helpers import base64url_to_bytes
4+
from webauthn.registration.verify_registration_response import VerifiedRegistration
5+
from webauthn.helpers.structs import (
6+
CredentialDeviceType,
7+
AttestationFormat,
8+
PublicKeyCredentialType,
9+
)
10+
11+
from homepage.services import SessionService, CredentialService
12+
13+
credential_id = "AAAA"
14+
registered_passkey = VerifiedRegistration(
15+
credential_id=base64url_to_bytes(credential_id),
16+
credential_public_key=base64url_to_bytes(
17+
"pQECAyYgASFYIEhW1CRfuNlIN6XTPKw0RbvzeaIlRMrDwwep-uq_-3WQIlgg1FZwd_RZRsqS_qgKCDvcVh7ScoKNo3w5h5fv3ihUSww"
18+
),
19+
aaguid="00000000-0000-0000-0000-00000000",
20+
attestation_object=base64url_to_bytes(
21+
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAFwAAAAAAAAAAAAAAAAAAAAAAQPctcQPE5oNRRJk_nO_371mf7qE7qIodzr0eOf6ACvnMB1oQG165dqutoi1U44shGezu5_gkTjmOPeJO0N8a7P-lAQIDJiABIVggSFbUJF-42Ug3pdM8rDRFu_N5oiVEysPDB6n66r_7dZAiWCDUVnB39FlGypL-qAoIO9xWHtJygo2jfDmHl-_eKFRLDA"
22+
),
23+
credential_backed_up=True,
24+
credential_device_type=CredentialDeviceType.MULTI_DEVICE,
25+
credential_type=PublicKeyCredentialType.PUBLIC_KEY,
26+
fmt=AttestationFormat.NONE,
27+
sign_count=0,
28+
user_verified=True,
29+
)
30+
31+
32+
class TestViewProfile(TestCase):
33+
route = reverse("profile")
34+
credential_service = CredentialService()
35+
session_service = SessionService()
36+
username = "mmiller"
37+
38+
def setUp(self):
39+
# The user has authenticated
40+
self.client.get(reverse("index"))
41+
self.session_service.log_in_user(session=self.client.session, username=self.username)
42+
43+
def tearDown(self) -> None:
44+
self.client.cookies.clear()
45+
46+
def test_sets_debug_cookie_with_query_param(self) -> None:
47+
# The user set the magic query parameter to get the "debug" cookie
48+
self.client.get(self.route, QUERY_STRING="debug=true")
49+
50+
cookie_debug = self.client.cookies.get("debug")
51+
self.assertIsNotNone(cookie_debug)
52+
assert cookie_debug # for mypy
53+
self.assertEqual(str(cookie_debug.coded_value), "true")
54+
55+
def test_no_debug_hide_pubkey_kty_and_alg(self) -> None:
56+
# Pretend the user's registered the following passkey
57+
self.credential_service.store_credential(
58+
username=self.username,
59+
verification=registered_passkey,
60+
)
61+
62+
# The user wants to see debug info
63+
response = self.client.get(self.route)
64+
65+
html_doc = str(response.content)
66+
67+
self.assertEqual(html_doc.find("Public Key Type"), -1)
68+
self.assertEqual(html_doc.find("(EC2)"), -1)
69+
self.assertEqual(html_doc.find("Public Key Algorithm"), -1)
70+
self.assertEqual(html_doc.find("ECDSA-SHA-256 (-7)"), -1)
71+
72+
self.credential_service.delete_credential_by_id(credential_id=credential_id)
73+
74+
def test_debug_show_pubkey_kty_and_alg(self) -> None:
75+
# Pretend the user's registered the following passkey
76+
self.credential_service.store_credential(
77+
username=self.username,
78+
verification=registered_passkey,
79+
)
80+
81+
# The user wants to see debug info
82+
response = self.client.get(self.route, QUERY_STRING="debug=true")
83+
84+
html_doc = str(response.content)
85+
86+
self.assertGreater(html_doc.find("Public Key Type"), -1)
87+
self.assertGreater(html_doc.find("EC2 (2)"), -1)
88+
self.assertGreater(html_doc.find("Public Key Algorithm"), -1)
89+
self.assertGreater(html_doc.find("ECDSA-SHA-256 (-7)"), -1)
90+
91+
self.credential_service.delete_credential_by_id(credential_id=credential_id)

_app/homepage/views/authentication_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def authentication_options(request: HttpRequest) -> JsonResponse:
4545
return JsonResponseBadRequest({"error": "That username has no registered credentials"})
4646

4747
authentication_options = authentication_service.generate_authentication_options(
48-
cache_key=session_service.get_session_key(request=request),
48+
cache_key=session_service.get_session_key(session=request.session),
4949
user_verification=options_user_verification,
5050
existing_credentials=existing_credentials,
5151
)

_app/homepage/views/authentication_verification.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def authentication_verification(request: HttpRequest) -> JsonResponse:
3939
)
4040

4141
verification = authentication_service.verify_authentication_response(
42-
cache_key=session_service.get_session_key(request=request),
42+
cache_key=session_service.get_session_key(session=request.session),
4343
existing_credential=existing_credential,
4444
response=options_webauthn_response,
4545
)
@@ -49,6 +49,6 @@ def authentication_verification(request: HttpRequest) -> JsonResponse:
4949
except Exception as err:
5050
return JsonResponseBadRequest({"error": str(err)})
5151

52-
session_service.log_in_user(request=request, username=verification.username)
52+
session_service.log_in_user(session=request.session, username=verification.username)
5353

5454
return JsonResponse({"verified": True})

0 commit comments

Comments
 (0)