Make API updates for member updates (#541)

* Add API endpoint that lists pending invites for all orgs (superuser-only)
* Add API endpoint that lists pending invites for org
* Add user emails to /api/orgs/<oid> response
This commit is contained in:
Tessa Walsh 2023-02-01 16:44:00 -05:00 committed by GitHub
parent 9048d46c6c
commit 58aafc4191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 1 deletions

View File

@ -189,6 +189,14 @@ class InviteOps:
return False
async def get_pending_invites(self, org=None):
"""return list of pending invites."""
if org:
invites = self.invites.find({"oid": org.id})
else:
invites = self.invites.find()
return [invite async for invite in invites]
def init_invites(mdb, email):
"""init InviteOps"""

View File

@ -136,6 +136,7 @@ class Organization(BaseMongoModel):
result["users"][id_] = {
"role": role,
"name": org_user.get("name", ""),
"email": org_user.get("email", ""),
}
return result
@ -463,6 +464,11 @@ def init_orgs_api(app, mdb, user_manager, invites, user_dep: User):
await user_manager.user_db.update(user)
return {"added": True}
@router.get("/invites", tags=["invites"])
async def get_pending_org_invites(org: Organization = Depends(org_owner_dep)):
pending_invites = await user_manager.invites.get_pending_invites(org)
return {"pending_invites": pending_invites}
@router.post("/remove", tags=["invites"])
async def remove_user_from_org(
remove: RemoveFromOrg, org: Organization = Depends(org_owner_dep)

View File

@ -151,7 +151,7 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
"""return list of user names for given ids"""
user_ids = [UUID4(id_) for id_ in user_ids]
cursor = self.user_db.collection.find(
{"id": {"$in": user_ids}}, projection=["id", "name"]
{"id": {"$in": user_ids}}, projection=["id", "name", "email"]
)
return await cursor.to_list(length=1000)
@ -363,6 +363,7 @@ class BearerOrQueryTransport(BearerTransport):
# ============================================================================
# pylint: disable=too-many-locals
def init_users_api(app, user_manager):
"""init fastapi_users"""
bearer_transport = BearerOrQueryTransport(tokenUrl="auth/jwt/login")
@ -488,6 +489,14 @@ def init_users_api(app, user_manager):
await user_manager.invites.remove_invite(token)
return {"removed": True}
@users_router.get("/invites", tags=["invites"])
async def get_pending_invites(user: User = Depends(current_active_user)):
if not user.is_superuser:
raise HTTPException(status_code=403, detail="Not Allowed")
pending_invites = await user_manager.invites.get_pending_invites()
return {"pending_invites": pending_invites}
app.include_router(users_router, prefix="/users", tags=["users"])
return fastapi_users

View File

@ -18,6 +18,8 @@ CRAWLER_PW = "crawlerPASSWORD!"
_admin_config_id = None
_crawler_config_id = None
NON_DEFAULT_ORG_NAME = "Non-default org"
@pytest.fixture(scope="session")
def admin_auth_headers():
@ -52,6 +54,27 @@ def default_org_id(admin_auth_headers):
time.sleep(5)
@pytest.fixture(scope="session")
def non_default_org_id(admin_auth_headers):
r = requests.post(
f"{API_PREFIX}/orgs/create",
headers=admin_auth_headers,
json={"name": NON_DEFAULT_ORG_NAME},
)
assert r.status_code == 200
while True:
r = requests.get(f"{API_PREFIX}/orgs", headers=admin_auth_headers)
data = r.json()
try:
for org in data["orgs"]:
if org["name"] == NON_DEFAULT_ORG_NAME:
return org["id"]
except:
print("Waiting for non-default org id")
time.sleep(5)
@pytest.fixture(scope="session")
def admin_crawl_id(admin_auth_headers, default_org_id):
# Start crawl.

View File

@ -0,0 +1,40 @@
import requests
from .conftest import API_PREFIX
def test_pending_invites(admin_auth_headers, default_org_id):
r = requests.get(f"{API_PREFIX}/users/invites", headers=admin_auth_headers)
assert r.status_code == 200
data = r.json()
assert data["pending_invites"] == []
# Add a pending invite and check it's returned
INVITE_EMAIL = "invite-pending@example.com"
r = requests.post(
f"{API_PREFIX}/users/invite",
headers=admin_auth_headers,
json={"email": INVITE_EMAIL},
)
assert r.status_code == 200
data = r.json()
assert data["invited"] == "new_user"
r = requests.get(f"{API_PREFIX}/users/invites", headers=admin_auth_headers)
assert r.status_code == 200
data = r.json()
invites = data["pending_invites"]
assert len(invites) == 1
invite = invites[0]
assert invite["_id"]
assert invite["email"] == INVITE_EMAIL
assert invite["oid"] == default_org_id
assert invite["created"]
assert invite["role"]
def test_pending_invites_crawler(crawler_auth_headers, default_org_id):
# Verify that only superusers can see pending invites
r = requests.get(f"{API_PREFIX}/users/invites", headers=crawler_auth_headers)
assert r.status_code == 403

View File

@ -16,6 +16,34 @@ def test_ensure_only_one_default_org(admin_auth_headers):
assert len(orgs_with_same_name) == 1
def test_get_org_admin(admin_auth_headers, default_org_id):
"""org owners should receive details on users."""
r = requests.get(f"{API_PREFIX}/orgs/{default_org_id}", headers=admin_auth_headers)
assert r.status_code == 200
data = r.json()
assert data["id"] == default_org_id
assert data["name"]
users = data["users"]
assert users
for _, value in users.items():
assert value["name"]
assert value["email"]
assert value["role"]
def test_get_org_crawler(crawler_auth_headers, default_org_id):
"""non-owners should *not* receive details on users."""
r = requests.get(
f"{API_PREFIX}/orgs/{default_org_id}", headers=crawler_auth_headers
)
assert r.status_code == 200
data = r.json()
assert data["id"] == default_org_id
assert data["name"]
assert data.get("users") is None
def test_rename_org(admin_auth_headers, default_org_id):
UPDATED_NAME = "updated org name"
rename_data = {"name": UPDATED_NAME}
@ -83,3 +111,44 @@ def test_remove_user_from_org(admin_auth_headers, default_org_id):
assert r.status_code == 200
data = r.json()
assert data["removed"]
def test_get_pending_org_invites(
admin_auth_headers, default_org_id, non_default_org_id
):
# Invite user to non-default org
INVITE_EMAIL = "non-default-invite@example.com"
r = requests.post(
f"{API_PREFIX}/orgs/{non_default_org_id}/invite",
headers=admin_auth_headers,
json={"email": INVITE_EMAIL, "role": 20},
)
assert r.status_code == 200
data = r.json()
assert data["invited"] == "new_user"
# Invite user to default org
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/invite",
headers=admin_auth_headers,
json={"email": "default-invite@example.com", "role": 10},
)
assert r.status_code == 200
data = r.json()
assert data["invited"] == "new_user"
# Check that only invite to non-default org is returned
r = requests.get(
f"{API_PREFIX}/orgs/{non_default_org_id}/invites",
headers=admin_auth_headers,
)
assert r.status_code == 200
data = r.json()
invites = data["pending_invites"]
assert len(invites) == 1
invite = invites[0]
assert invite["_id"]
assert invite["email"] == INVITE_EMAIL
assert invite["oid"] == non_default_org_id
assert invite["created"]
assert invite["role"]