Link a pending portal member to the local user with their email
A portal identity whose email a local user already has is still refused
on arrival. POST /cloud/members/{portal_sub}/link lets a superuser map it
onto that user, never a superuser's, after the portal confirms the
membership. GET /cloud/members marks such members linkable, and Admin →
Users offers "Link portal account" for them, saying that a linked user is
deleted with a removal at the portal or a Disconnect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -167,6 +167,9 @@ class PortalMember(BaseModel):
|
||||
email: str
|
||||
#: Their local user, or None while they have not opened the instance yet.
|
||||
local_user_id: uuid.UUID | None = None
|
||||
#: A local user has their email, which keeps them out until a superuser
|
||||
#: links the two. Never true of a superuser's account.
|
||||
linkable: bool = False
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -208,16 +211,66 @@ def read_members(session: SessionDep) -> Any:
|
||||
user.portal_sub: user.id
|
||||
for user in session.exec(select(User).where(col(User.portal_sub).in_(subs)))
|
||||
}
|
||||
emails = [str(member["email"]) for member in members]
|
||||
linkable = {
|
||||
user.email
|
||||
for user in session.exec(select(User).where(col(User.email).in_(emails)))
|
||||
if not user.is_superuser and not user.portal_sub
|
||||
}
|
||||
return [
|
||||
PortalMember(
|
||||
user_id=str(member["user_id"]),
|
||||
email=str(member["email"]),
|
||||
local_user_id=local.get(str(member["user_id"])),
|
||||
linkable=str(member["user_id"]) not in local
|
||||
and str(member["email"]) in linkable,
|
||||
)
|
||||
for member in members
|
||||
]
|
||||
|
||||
|
||||
@router.post(
|
||||
"/members/{portal_sub}/link",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
response_model=UserPublic,
|
||||
)
|
||||
def link_member(session: SessionDep, portal_sub: uuid.UUID) -> Any:
|
||||
"""Let a pending member in as the local user that already has their email.
|
||||
|
||||
Arriving on their own they are refused, because handing an existing
|
||||
account to whoever holds that address at the portal is not a guess to
|
||||
make. This is a superuser deciding it, and never for a superuser's account.
|
||||
"""
|
||||
if cloud_config.load() is None:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="This instance is not connected to a portal"
|
||||
)
|
||||
sub = str(portal_sub)
|
||||
member = enroll_mod.lookup_member(sub)
|
||||
if member is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="The portal did not confirm this member"
|
||||
)
|
||||
if session.exec(select(User).where(User.portal_sub == sub)).first():
|
||||
raise HTTPException(status_code=409, detail="That account already has access")
|
||||
user = session.exec(select(User).where(User.email == str(member["email"]))).first()
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="No local user has this email")
|
||||
if user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=403, detail="A superuser cannot be linked to a portal account"
|
||||
)
|
||||
if user.portal_sub:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="This user is linked to another portal account"
|
||||
)
|
||||
user.portal_sub = sub
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/members/{portal_sub}",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
|
||||
Reference in New Issue
Block a user