Present single-use password setup handoff
This commit is contained in:
parent
bbe291dac3
commit
4efce34821
5 changed files with 45 additions and 0 deletions
|
|
@ -66,4 +66,9 @@ class HTTPIdentityProvisioningAdapter:
|
|||
external_subject=str(result["external_subject"]),
|
||||
status=str(result["status"]),
|
||||
resumed=bool(result.get("resumed", False)),
|
||||
password_setup_url=(
|
||||
str(result["password_setup_url"])
|
||||
if result.get("password_setup_url")
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class ProvisioningResult:
|
|||
external_subject: str
|
||||
status: str
|
||||
resumed: bool = False
|
||||
password_setup_url: str | None = None
|
||||
|
||||
|
||||
class IdentityProvisioningPort(Protocol):
|
||||
|
|
|
|||
|
|
@ -302,6 +302,14 @@ class PortalApplication:
|
|||
subject=result.external_subject, provider=result.provider,
|
||||
correlation_id=correlation_id,
|
||||
)
|
||||
if result.password_setup_url:
|
||||
return self._html(
|
||||
start_response,
|
||||
self._password_setup_handoff(
|
||||
result.password_setup_url, tenant
|
||||
),
|
||||
correlation_id,
|
||||
)
|
||||
query = urlencode({"provisioned": user.user_id, "status": result.status})
|
||||
return self._redirect(start_response, f"/admin/{tenant}?{query}", correlation_id)
|
||||
if len(parts) == 6 and parts[3] == "users" and parts[5] == "status":
|
||||
|
|
@ -481,6 +489,20 @@ class PortalApplication:
|
|||
f"<td>{'linked' if directory else 'pending'}</td><td>{action}</td></tr>"
|
||||
)
|
||||
|
||||
def _password_setup_handoff(self, setup_url: str, tenant: str) -> str:
|
||||
if not setup_url.startswith("https://"):
|
||||
raise ValidationError("password setup handoff must use HTTPS")
|
||||
return self._page_html(
|
||||
"Password setup",
|
||||
"<h1>Login identity created</h1>"
|
||||
"<p>The password is handled only by the NetKingdom identity "
|
||||
"surface. This short-lived link is single use.</p>"
|
||||
f'<p><a class="button" rel="noreferrer" href="{escape(setup_url)}">'
|
||||
"Continue to password setup</a></p>"
|
||||
f'<p><a href="/admin/{escape(tenant)}">'
|
||||
"Return to tenant administration</a></p>",
|
||||
)
|
||||
|
||||
def _redirect(self, start_response: StartResponse, location: str, correlation_id: str) -> list[bytes]:
|
||||
start_response("303 See Other", [("Location", location), *self._security_headers(correlation_id)])
|
||||
return [b""]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class ProvisioningAdapterTests(unittest.TestCase):
|
|||
"external_subject": "person-1",
|
||||
"status": "password_setup_required",
|
||||
"resumed": True,
|
||||
"password_setup_url": "https://kc.example/setup/password?token=opaque",
|
||||
}).encode())
|
||||
adapter = HTTPIdentityProvisioningAdapter(
|
||||
base_url="http://provisioner", bearer_token="secret\n"
|
||||
|
|
@ -33,6 +34,10 @@ class ProvisioningAdapterTests(unittest.TestCase):
|
|||
correlation_id="corr-1",
|
||||
))
|
||||
self.assertTrue(result.resumed)
|
||||
self.assertEqual(
|
||||
"https://kc.example/setup/password?token=opaque",
|
||||
result.password_setup_url,
|
||||
)
|
||||
request = opener.call_args.args[0]
|
||||
self.assertEqual("Bearer secret", request.headers["Authorization"])
|
||||
self.assertIn(b"idem-1234567890123456", request.data)
|
||||
|
|
|
|||
|
|
@ -187,6 +187,17 @@ class PortalApplicationTests(unittest.TestCase):
|
|||
self.assertEqual("200 OK", page["status"])
|
||||
self.assertIn(b"ada@example.test", html)
|
||||
self.assertIn(b"Create login", html)
|
||||
user_id = next(iter(self.app.service.store.users))
|
||||
handoff, html = invoke(
|
||||
self.app,
|
||||
f"/admin/tenant:friendly:binky/users/{user_id}/provision",
|
||||
method="POST",
|
||||
cookie="ue_session=browser",
|
||||
form={"csrf_token": "csrf-test-token"},
|
||||
)
|
||||
self.assertEqual("200 OK", handoff["status"])
|
||||
self.assertIn(b"Continue to password setup", html)
|
||||
self.assertIn(b"https://kc.example/setup/password?token=opaque", html)
|
||||
|
||||
|
||||
class FakeProvisioning:
|
||||
|
|
@ -199,6 +210,7 @@ class FakeProvisioning:
|
|||
provider="netkingdom-lldap",
|
||||
external_subject=request.primary_email.split("@")[0],
|
||||
status="password_setup_required",
|
||||
password_setup_url="https://kc.example/setup/password?token=opaque",
|
||||
)
|
||||
|
||||
def suspend(self, *, external_subject, idempotency_key, correlation_id):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue