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