Skip to content

Commit

Permalink
Merge pull request #35 from softwareone-platform/MPT-4912_promote_a_u…
Browse files Browse the repository at this point in the history
…ser_to_organization_admin

MPT-4912 promote user to organization manager
  • Loading branch information
ffaraone authored Jan 24, 2025
2 parents 68a7342 + abf0929 commit 45dad45
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 2 deletions.
2 changes: 2 additions & 0 deletions app/api_clients/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OPT_RESOURCE_TYPE_ORGANIZATION = 2
OPT_ROLE_ORGANIZATION_ADMIN = 3
7 changes: 7 additions & 0 deletions app/api_clients/optscale.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ async def fetch_users_for_organization(self, organization_id: UUID | str) -> htt
)
response.raise_for_status()
return response

async def fetch_user_by_id(self, user_id: UUID | str) -> httpx.Response:
response = await self.httpx_client.get(
f"/employees/{user_id}?roles=true",
)
response.raise_for_status()
return response
13 changes: 13 additions & 0 deletions app/api_clients/optscale_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from app import settings
from app.api_clients.base import APIClientError, BaseAPIClient, OptscaleClusterSecretAuth
from app.api_clients.constants import OPT_RESOURCE_TYPE_ORGANIZATION, OPT_ROLE_ORGANIZATION_ADMIN


class OptscaleAuthClientError(APIClientError):
Expand Down Expand Up @@ -33,3 +34,15 @@ async def get_existing_user_info(self, email: str) -> httpx.Response:
raise UserDoesNotExist(email)

return response

async def make_user_admin(self, organization_id: str, user_id: str) -> httpx.Response:
response = await self.httpx_client.post(
f"/users/{user_id}/assignment_register",
json={
"role_id": OPT_ROLE_ORGANIZATION_ADMIN,
"type_id": OPT_RESOURCE_TYPE_ORGANIZATION,
"resource_id": organization_id,
},
)
response.raise_for_status()
return response
26 changes: 24 additions & 2 deletions app/routers/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination.limit_offset import LimitOffsetPage

from app.api_clients import APIModifierClient
from app.api_clients.optscale import OptscaleClient
from app.api_clients import APIModifierClient, OptscaleAuthClient, OptscaleClient
from app.auth import CurrentSystem
from app.db.handlers import NotFoundError
from app.db.models import Organization
Expand Down Expand Up @@ -196,3 +195,26 @@ async def get_users_by_organization_id(
)
for user in users
]


@router.post(
"/{organization_id}/users/{user_id}/make-admin",
status_code=status.HTTP_204_NO_CONTENT,
)
async def make_organization_user_admin(
organization: Annotated[Organization, Depends(fetch_organization_or_404)],
user_id: UUID,
services: svcs.fastapi.DepContainer,
):
optscale_auth_client = await services.aget(OptscaleAuthClient)
optscale_client = await services.aget(OptscaleClient)

async with wrap_http_error_in_502("Error making user admin in FinOps for Cloud"):
# check user exists in optscale
response = await optscale_client.fetch_user_by_id(str(user_id))
user = response.json()
# assign admin role of current organization to the user
await optscale_auth_client.make_user_admin(
str(organization.organization_id),
user["auth_user_id"],
)
127 changes: 127 additions & 0 deletions tests/test_users_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,130 @@ async def test_get_users_for_organization_with_optscale_error(

assert response.status_code == 502
assert f"Error fetching users for organization {org.name}" in response.json()["detail"]


# ===============
# Make user admin
# ===============


async def test_make_user_admin(
organization_factory: ModelFactory[Organization],
authenticated_client: AsyncClient,
httpx_mock: HTTPXMock,
):
user_id = str(uuid.uuid4())
org = await organization_factory(
organization_id=str(uuid.uuid4()),
)
auth_user_id = str(uuid.uuid4())

httpx_mock.add_response(
method="GET",
url=f"{settings.opt_api_base_url}/employees/{user_id}?roles=true",
match_headers={"Secret": settings.opt_cluster_secret},
status_code=200,
json={
"deleted_at": 0,
"id": "2c2e9705-8023-437c-b09d-8cbd49f0a682",
"created_at": 1729156673,
"name": "Ciccio",
"organization_id": org.organization_id,
"auth_user_id": auth_user_id,
"default_ssh_key_id": None,
},
)

httpx_mock.add_response(
method="POST",
url=f"{settings.opt_auth_base_url}/users/{auth_user_id}/assignment_register",
match_headers={"Secret": settings.opt_cluster_secret},
status_code=200,
json={
"created_at": 1736352461,
"deleted_at": 0,
"id": "7884c0c0-be5c-4cc7-8413-dd8033b1e4a2",
"type_id": 2,
"role_id": 3,
"user_id": auth_user_id,
"resource_id": org.organization_id,
},
match_json={
"role_id": 3, # Admin
"type_id": 2, # Organization
"resource_id": org.organization_id,
},
)
response = await authenticated_client.post(
f"/organizations/{org.id}/users/{user_id}/make-admin",
)
assert response.status_code == 204


async def test_make_user_admin_not_found(
organization_factory: ModelFactory[Organization],
authenticated_client: AsyncClient,
httpx_mock: HTTPXMock,
):
user_id = str(uuid.uuid4())
org = await organization_factory(
organization_id=str(uuid.uuid4()),
)

httpx_mock.add_response(
method="GET",
url=f"{settings.opt_api_base_url}/employees/{user_id}?roles=true",
match_headers={"Secret": settings.opt_cluster_secret},
status_code=404,
)

response = await authenticated_client.post(
f"/organizations/{org.id}/users/{user_id}/make-admin",
)
assert response.status_code == 502
assert "Error making user admin in FinOps for Cloud: 404" in response.json()["detail"]


async def test_make_user_admin_error_assigning_role(
organization_factory: ModelFactory[Organization],
authenticated_client: AsyncClient,
httpx_mock: HTTPXMock,
):
user_id = str(uuid.uuid4())
org = await organization_factory(
organization_id=str(uuid.uuid4()),
)
auth_user_id = str(uuid.uuid4())

httpx_mock.add_response(
method="GET",
url=f"{settings.opt_api_base_url}/employees/{user_id}?roles=true",
match_headers={"Secret": settings.opt_cluster_secret},
status_code=200,
json={
"deleted_at": 0,
"id": "2c2e9705-8023-437c-b09d-8cbd49f0a682",
"created_at": 1729156673,
"name": "Ciccio",
"organization_id": org.organization_id,
"auth_user_id": auth_user_id,
"default_ssh_key_id": None,
},
)

httpx_mock.add_response(
method="POST",
url=f"{settings.opt_auth_base_url}/users/{auth_user_id}/assignment_register",
match_headers={"Secret": settings.opt_cluster_secret},
status_code=400,
match_json={
"role_id": 3, # Admin
"type_id": 2, # Organization
"resource_id": org.organization_id,
},
)
response = await authenticated_client.post(
f"/organizations/{org.id}/users/{user_id}/make-admin",
)
assert response.status_code == 502
assert "Error making user admin in FinOps for Cloud: 400" in response.json()["detail"]

0 comments on commit 45dad45

Please sign in to comment.