keycloak: include names in provisioning
This commit is contained in:
parent
e4a6cbc104
commit
a2ae62bb23
@ -384,6 +384,8 @@ def list_access_requests(ctx: AuthContext = Depends(_require_auth)) -> JSONRespo
|
||||
"id": row.get("request_code"),
|
||||
"username": row.get("username"),
|
||||
"email": row.get("contact_email") or "",
|
||||
"first_name": row.get("first_name") or "",
|
||||
"last_name": row.get("last_name") or "",
|
||||
"request_code": row.get("request_code"),
|
||||
"created_at": created_at.isoformat() if isinstance(created_at, datetime) else "",
|
||||
"note": row.get("note") or "",
|
||||
|
||||
@ -49,4 +49,6 @@ ARIADNE_ACCESS_REQUEST_ALTER = [
|
||||
"ALTER TABLE access_requests ADD COLUMN IF NOT EXISTS approval_flags TEXT[]",
|
||||
"ALTER TABLE access_requests ADD COLUMN IF NOT EXISTS approval_note TEXT",
|
||||
"ALTER TABLE access_requests ADD COLUMN IF NOT EXISTS denial_note TEXT",
|
||||
"ALTER TABLE access_requests ADD COLUMN IF NOT EXISTS first_name TEXT",
|
||||
"ALTER TABLE access_requests ADD COLUMN IF NOT EXISTS last_name TEXT",
|
||||
]
|
||||
|
||||
@ -25,6 +25,8 @@ REQUIRED_TASKS = (
|
||||
class AccessRequest:
|
||||
request_code: str
|
||||
username: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
contact_email: str
|
||||
status: str
|
||||
email_verified_at: datetime | None
|
||||
@ -113,8 +115,8 @@ class Storage:
|
||||
row = self._portal_db.fetchone(
|
||||
"""
|
||||
SELECT request_code, username, contact_email, status, email_verified_at,
|
||||
initial_password, initial_password_revealed_at, provision_attempted_at,
|
||||
approval_flags, approval_note, denial_note
|
||||
first_name, last_name, initial_password, initial_password_revealed_at,
|
||||
provision_attempted_at, approval_flags, approval_note, denial_note
|
||||
FROM access_requests
|
||||
WHERE request_code = %s
|
||||
""",
|
||||
@ -128,8 +130,8 @@ class Storage:
|
||||
row = self._portal_db.fetchone(
|
||||
"""
|
||||
SELECT request_code, username, contact_email, status, email_verified_at,
|
||||
initial_password, initial_password_revealed_at, provision_attempted_at,
|
||||
approval_flags, approval_note, denial_note
|
||||
first_name, last_name, initial_password, initial_password_revealed_at,
|
||||
provision_attempted_at, approval_flags, approval_note, denial_note
|
||||
FROM access_requests
|
||||
WHERE username = %s
|
||||
ORDER BY created_at DESC
|
||||
@ -144,7 +146,7 @@ class Storage:
|
||||
def list_pending_requests(self) -> list[dict[str, Any]]:
|
||||
return self._portal_db.fetchall(
|
||||
"""
|
||||
SELECT request_code, username, contact_email, note, status, created_at
|
||||
SELECT request_code, username, contact_email, first_name, last_name, note, status, created_at
|
||||
FROM access_requests
|
||||
WHERE status = 'pending'
|
||||
ORDER BY created_at ASC
|
||||
@ -156,8 +158,8 @@ class Storage:
|
||||
rows = self._portal_db.fetchall(
|
||||
"""
|
||||
SELECT request_code, username, contact_email, status, email_verified_at,
|
||||
initial_password, initial_password_revealed_at, provision_attempted_at,
|
||||
approval_flags, approval_note, denial_note
|
||||
first_name, last_name, initial_password, initial_password_revealed_at,
|
||||
provision_attempted_at, approval_flags, approval_note, denial_note
|
||||
FROM access_requests
|
||||
WHERE status IN ('approved', 'accounts_building')
|
||||
ORDER BY created_at ASC
|
||||
@ -351,6 +353,8 @@ class Storage:
|
||||
return AccessRequest(
|
||||
request_code=str(row.get("request_code") or ""),
|
||||
username=str(row.get("username") or ""),
|
||||
first_name=str(row.get("first_name") or ""),
|
||||
last_name=str(row.get("last_name") or ""),
|
||||
contact_email=str(row.get("contact_email") or ""),
|
||||
status=str(row.get("status") or ""),
|
||||
email_verified_at=row.get("email_verified_at"),
|
||||
|
||||
@ -44,6 +44,8 @@ class ProvisionOutcome:
|
||||
class RequestContext:
|
||||
request_code: str
|
||||
username: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
contact_email: str
|
||||
email_verified_at: datetime | None
|
||||
status: str
|
||||
@ -123,6 +125,8 @@ class ProvisioningManager:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT username,
|
||||
first_name,
|
||||
last_name,
|
||||
contact_email,
|
||||
email_verified_at,
|
||||
status,
|
||||
@ -142,6 +146,8 @@ class ProvisioningManager:
|
||||
return RequestContext(
|
||||
request_code=request_code,
|
||||
username=username,
|
||||
first_name=str(row.get("first_name") or ""),
|
||||
last_name=str(row.get("last_name") or ""),
|
||||
contact_email=str(row.get("contact_email") or ""),
|
||||
email_verified_at=row.get("email_verified_at"),
|
||||
status=str(row.get("status") or ""),
|
||||
@ -450,8 +456,15 @@ class ProvisioningManager:
|
||||
if existing_email_user and (existing_email_user.get("username") or "") != username:
|
||||
raise RuntimeError("email is already associated with an existing Atlas account")
|
||||
|
||||
def _new_user_payload(self, username: str, email: str, mailu_email: str) -> dict[str, Any]:
|
||||
return {
|
||||
def _new_user_payload(
|
||||
self,
|
||||
username: str,
|
||||
email: str,
|
||||
mailu_email: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
) -> dict[str, Any]:
|
||||
payload = {
|
||||
"username": username,
|
||||
"enabled": True,
|
||||
"email": email,
|
||||
@ -462,6 +475,13 @@ class ProvisioningManager:
|
||||
MAILU_ENABLED_ATTR: ["true"],
|
||||
},
|
||||
}
|
||||
if first_name:
|
||||
payload["firstName"] = first_name
|
||||
if last_name:
|
||||
payload["lastName"] = last_name
|
||||
else:
|
||||
payload["lastName"] = username
|
||||
return payload
|
||||
|
||||
def _create_or_fetch_user(self, ctx: RequestContext) -> dict[str, Any]:
|
||||
user = keycloak_admin.find_user(ctx.username)
|
||||
@ -469,7 +489,7 @@ class ProvisioningManager:
|
||||
return user
|
||||
email = self._require_verified_email(ctx)
|
||||
self._ensure_email_unused(email, ctx.username)
|
||||
payload = self._new_user_payload(ctx.username, email, ctx.mailu_email)
|
||||
payload = self._new_user_payload(ctx.username, email, ctx.mailu_email, ctx.first_name, ctx.last_name)
|
||||
try:
|
||||
created_id = keycloak_admin.create_user(payload)
|
||||
return keycloak_admin.get_user(created_id)
|
||||
|
||||
@ -23,10 +23,9 @@ class ProfileSyncSummary:
|
||||
|
||||
def _profile_complete(user: dict[str, Any]) -> bool:
|
||||
email = user.get("email") if isinstance(user.get("email"), str) else ""
|
||||
first_name = user.get("firstName") if isinstance(user.get("firstName"), str) else ""
|
||||
last_name = user.get("lastName") if isinstance(user.get("lastName"), str) else ""
|
||||
email_verified = bool(user.get("emailVerified"))
|
||||
return bool(email.strip() and first_name.strip() and last_name.strip() and email_verified)
|
||||
return bool(email.strip() and last_name.strip() and email_verified)
|
||||
|
||||
|
||||
def run_profile_sync() -> ProfileSyncSummary:
|
||||
|
||||
@ -15,7 +15,7 @@ def test_profile_sync_removes_required_actions(monkeypatch) -> None:
|
||||
"enabled": True,
|
||||
"email": "alice@example.com",
|
||||
"emailVerified": True,
|
||||
"firstName": "Alice",
|
||||
"firstName": "",
|
||||
"lastName": "Atlas",
|
||||
"requiredActions": ["UPDATE_PROFILE", "VERIFY_EMAIL"],
|
||||
}
|
||||
@ -44,7 +44,7 @@ def test_profile_sync_skips_incomplete(monkeypatch) -> None:
|
||||
"enabled": True,
|
||||
"email": "bob@example.com",
|
||||
"emailVerified": True,
|
||||
"firstName": "",
|
||||
"firstName": "Bob",
|
||||
"lastName": "",
|
||||
"requiredActions": ["UPDATE_PROFILE"],
|
||||
}
|
||||
|
||||
@ -209,6 +209,8 @@ def test_provisioning_creates_user_and_password(monkeypatch) -> None:
|
||||
|
||||
row = {
|
||||
"username": "alice",
|
||||
"first_name": "Alice",
|
||||
"last_name": "Atlas",
|
||||
"contact_email": "alice@example.com",
|
||||
"email_verified_at": datetime.now(timezone.utc),
|
||||
"status": "approved",
|
||||
@ -225,6 +227,8 @@ def test_provisioning_creates_user_and_password(monkeypatch) -> None:
|
||||
|
||||
assert outcome.status == "awaiting_onboarding"
|
||||
assert admin.created_payload is not None
|
||||
assert admin.created_payload.get("firstName") == "Alice"
|
||||
assert admin.created_payload.get("lastName") == "Atlas"
|
||||
assert admin.reset_calls
|
||||
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@ def test_row_to_request_flags() -> None:
|
||||
row = {
|
||||
"request_code": "abc",
|
||||
"username": "alice",
|
||||
"first_name": "Alice",
|
||||
"last_name": "Atlas",
|
||||
"contact_email": "a@example.com",
|
||||
"status": "pending",
|
||||
"email_verified_at": None,
|
||||
@ -57,6 +59,8 @@ def test_access_requests_use_portal_db() -> None:
|
||||
portal_row = {
|
||||
"request_code": "req",
|
||||
"username": "alice",
|
||||
"first_name": "Alice",
|
||||
"last_name": "Atlas",
|
||||
"contact_email": "a@example.com",
|
||||
"status": "pending",
|
||||
"email_verified_at": None,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user