Skip to content

Commit 1484824

Browse files
authored
fix: Skip authz if admin is requesting data connectors (#887)
For reprovisioning it is necessary to get all entities. When selecting data connectors from the db, there was always a call to authz to first get all identifiers of resources the requesting user has read access to. For internal service admins, this doesn't work, because they are not modelled in authz. For admins in general the call can be prevented, because admins are defined to see everything. This change skips the authz call if the requesting user is an admin. Additionally the search reprovisioning code uses an internal service admin for doing its work when triggered internally (and not by an endpoint).
1 parent 012086d commit 1484824

8 files changed

Lines changed: 41 additions & 34 deletions

File tree

bases/renku_data_services/data_api/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import renku_data_services.solr.entity_schema as entity_schema
1818
from renku_data_services.app_config import logging
1919
from renku_data_services.authz.admin_sync import sync_admins_from_keycloak
20-
from renku_data_services.base_models.core import APIUser
20+
from renku_data_services.base_models.core import InternalServiceAdmin, ServiceAdminId
2121
from renku_data_services.data_api.app import register_all_handlers
2222
from renku_data_services.data_api.dependencies import DependencyManager
2323
from renku_data_services.data_api.prometheus import setup_app_metrics, setup_prometheus
@@ -46,7 +46,7 @@ async def _solr_reindex(app: Sanic) -> None:
4646
"""
4747
config = DependencyManager.from_env()
4848
reprovision = config.search_reprovisioning
49-
admin = APIUser(is_admin=True)
49+
admin = InternalServiceAdmin(id=ServiceAdminId.search_reprovision)
5050
await reprovision.run_reprovision(admin)
5151

5252

components/renku_data_services/base_models/core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class ServiceAdminId(StrEnum):
7878
migrations = "migrations"
7979
secrets_rotation = "secrets_rotation"
8080
k8s_watcher = "k8s_watcher"
81+
search_reprovision = "search_reprovision"
8182

8283

8384
@dataclass(kw_only=True, frozen=True)

components/renku_data_services/data_connectors/db.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
DataConnectorInProjectPath,
2121
DataConnectorPath,
2222
DataConnectorSlug,
23+
InternalServiceAdmin,
2324
NamespacePath,
2425
NamespaceSlug,
2526
ProjectPath,
@@ -68,29 +69,27 @@ async def get_data_connectors(
6869
namespace: ProjectPath | NamespacePath | None = None,
6970
) -> tuple[list[models.DataConnector | models.GlobalDataConnector], int]:
7071
"""Get multiple data connectors from the database."""
71-
data_connector_ids = await self.authz.resources_with_permission(
72-
user, user.id, ResourceType.data_connector, Scope.READ
73-
)
7472

75-
async with self.session_maker() as session:
76-
stmt = (
77-
select(schemas.DataConnectorORM)
78-
.where(schemas.DataConnectorORM.id.in_(data_connector_ids))
79-
.options(
80-
joinedload(schemas.DataConnectorORM.slug)
81-
.joinedload(ns_schemas.EntitySlugORM.project)
82-
.joinedload(ProjectORM.slug)
73+
async def restrict_by_read(stmt: Select) -> Select:
74+
if isinstance(user, InternalServiceAdmin):
75+
return stmt
76+
else:
77+
dc_ids = await self.authz.resources_with_permission(
78+
user, user.id, ResourceType.data_connector, Scope.READ
8379
)
80+
return stmt.where(schemas.DataConnectorORM.id.in_(dc_ids))
81+
82+
async with self.session_maker() as session:
83+
stmt = (await restrict_by_read(select(schemas.DataConnectorORM))).options(
84+
joinedload(schemas.DataConnectorORM.slug)
85+
.joinedload(ns_schemas.EntitySlugORM.project)
86+
.joinedload(ProjectORM.slug)
8487
)
8588
if namespace:
8689
stmt = _filter_by_namespace_slug(stmt, namespace)
8790
stmt = stmt.limit(pagination.per_page).offset(pagination.offset)
8891
stmt = stmt.order_by(schemas.DataConnectorORM.id.desc())
89-
stmt_count = (
90-
select(func.count())
91-
.select_from(schemas.DataConnectorORM)
92-
.where(schemas.DataConnectorORM.id.in_(data_connector_ids))
93-
)
92+
stmt_count = await restrict_by_read(select(func.count()).select_from(schemas.DataConnectorORM))
9493
if namespace:
9594
stmt_count = _filter_by_namespace_slug(stmt_count, namespace)
9695
results = await session.scalars(stmt), await session.scalar(stmt_count)

components/renku_data_services/search/blueprints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def _post(request: Request, user: base_models.APIUser) -> HTTPResponse | J
4040
reprovisioning = await self.search_reprovision.acquire_reprovision()
4141

4242
request.app.add_task(
43-
self.search_reprovision.init_reprovision(requested_by=user, reprovisioning=reprovisioning),
43+
self.search_reprovision.init_reprovision(user, reprovisioning=reprovisioning),
4444
name=f"reprovisioning-{reprovisioning.id}",
4545
)
4646

components/renku_data_services/search/reprovision.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from renku_data_services.base_models.core import APIUser
99
from renku_data_services.data_connectors.db import DataConnectorRepository
1010
from renku_data_services.data_connectors.models import DataConnector, GlobalDataConnector
11+
from renku_data_services.errors.errors import ForbiddenError
1112
from renku_data_services.message_queue.db import ReprovisioningRepository
1213
from renku_data_services.message_queue.models import Reprovisioning
1314
from renku_data_services.namespace.db import GroupRepository
@@ -43,10 +44,10 @@ def __init__(
4344
self._project_repo = project_repo
4445
self._data_connector_repo = data_connector_repo
4546

46-
async def run_reprovision(self, requested_by: APIUser) -> int:
47+
async def run_reprovision(self, admin: APIUser) -> int:
4748
"""Start a reprovisioning if not already running."""
4849
reprovision = await self.acquire_reprovision()
49-
return await self.init_reprovision(requested_by, reprovision)
50+
return await self.init_reprovision(admin, reprovision)
5051

5152
async def acquire_reprovision(self) -> Reprovisioning:
5253
"""Acquire a reprovisioning slot. Throws if already taken."""
@@ -74,7 +75,7 @@ async def _get_all_data_connectors(
7475
for dc in result[0]:
7576
yield dc
7677

77-
async def init_reprovision(self, requested_by: APIUser, reprovisioning: Reprovisioning) -> int:
78+
async def init_reprovision(self, admin: APIUser, reprovisioning: Reprovisioning) -> int:
7879
"""Initiates reprovisioning by inserting documents into the staging table.
7980
8081
Deletes all renku entities in the solr core. Then it goes
@@ -84,6 +85,9 @@ async def init_reprovision(self, requested_by: APIUser, reprovisioning: Reprovis
8485
solr with these entries.
8586
"""
8687

88+
if not admin.is_admin:
89+
raise ForbiddenError(message="Only Renku administrators are allowed to start search reprovisioning.")
90+
8791
def log_counter(c: int) -> None:
8892
if c % 50 == 0:
8993
logger.info(f"Inserted {c}. entities into staging table...")
@@ -96,20 +100,21 @@ def log_counter(c: int) -> None:
96100
async with DefaultSolrClient(self._solr_config) as client:
97101
await client.delete("_type:*")
98102

99-
all_users = self._user_repo.get_all_users(requested_by=requested_by)
103+
all_users = self._user_repo.get_all_users(requested_by=admin)
100104
counter = await self.__update_entities(all_users, "user", started, counter, log_counter)
101-
logger.info("Done adding user entities to search_updates table.")
105+
logger.info(f"Done adding user entities to search_updates table. Record count: {counter}.")
102106

103-
all_groups = self._group_repo.get_all_groups(requested_by=requested_by)
107+
all_groups = self._group_repo.get_all_groups(requested_by=admin)
104108
counter = await self.__update_entities(all_groups, "group", started, counter, log_counter)
105-
logger.info("Done adding group entities to search_updates table.")
109+
logger.info(f"Done adding group entities to search_updates table. Record count: {counter}")
106110

107-
all_projects = self._project_repo.get_all_projects(requested_by=requested_by)
111+
all_projects = self._project_repo.get_all_projects(requested_by=admin)
108112
counter = await self.__update_entities(all_projects, "project", started, counter, log_counter)
109-
logger.info("Done adding project entities to search_updates table.")
113+
logger.info(f"Done adding project entities to search_updates table. Record count: {counter}")
110114

111-
all_dcs = self._get_all_data_connectors(requested_by, per_page=20)
115+
all_dcs = self._get_all_data_connectors(admin, per_page=20)
112116
counter = await self.__update_entities(all_dcs, "data connector", started, counter, log_counter)
117+
logger.info(f"Done adding dataconnector entities to search_updates table. Record count: {counter}")
113118

114119
logger.info(f"Inserted {counter} entities into the staging table.")
115120
except Exception as e:

components/renku_data_services/solr/solr_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ async def get_raw(self, id: str) -> Response:
655655
async def query_raw(self, query: SolrQuery) -> Response:
656656
"""Query documents and return the http response."""
657657
try:
658+
logger.debug(f"Running solr query: {self.config.base_url}/solr/{self.config.core}")
658659
return await self.delegate.post("/query", params={"wt": "json"}, json=query.to_dict())
659660
except ConnectError as e:
660661
raise SolrClientConnectException(e) from e

test/bases/renku_data_services/data_api/conftest.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from renku_data_services.authz.admin_sync import sync_admins_from_keycloak
1616
from renku_data_services.authz.authz import _AuthzConverter
1717
from renku_data_services.base_models import Slug
18-
from renku_data_services.base_models.core import APIUser, NamespacePath
18+
from renku_data_services.base_models.core import InternalServiceAdmin, NamespacePath, ServiceAdminId
1919
from renku_data_services.data_api.app import register_all_handlers
2020
from renku_data_services.data_api.dependencies import DependencyManager
2121
from renku_data_services.migrations.core import run_migrations_for_app
@@ -261,9 +261,10 @@ async def sanic_client_with_solr(sanic_client: SanicASGITestClient, app_manager)
261261

262262
@pytest_asyncio.fixture
263263
async def search_reprovision(app_manager_instance: DependencyManager, search_push_updates):
264+
admin = InternalServiceAdmin(id=ServiceAdminId.search_reprovision)
265+
264266
async def search_reprovision_helper() -> None:
265-
user = APIUser(is_admin=True)
266-
await app_manager_instance.search_reprovisioning.run_reprovision(user)
267+
await app_manager_instance.search_reprovisioning.run_reprovision(admin)
267268
await search_push_updates(clear_index=False)
268269

269270
return search_reprovision_helper

test/components/renku_data_services/search/test_reprovision.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ async def test_get_data_connectors(app_manager_instance) -> None:
161161

162162

163163
@pytest.mark.asyncio
164-
async def test_run_reprovision(app_manager_instance, solr_search) -> None:
164+
async def test_run_reprovision(app_manager_instance, solr_search, admin_user) -> None:
165165
setup = make_setup(app_manager_instance, solr_search)
166166
dcs = await make_data_connectors(setup, 5)
167167
groups = await make_groups(setup, 4)
168168
projects = await make_projects(setup, 3)
169169
users = [item async for item in setup.user_repo.get_all_users(admin)]
170170

171-
count = await setup.search_reprovision.run_reprovision(admin)
171+
count = await setup.search_reprovision.run_reprovision(admin_user)
172172

173173
next = await setup.search_update_repo.select_next(20)
174174

0 commit comments

Comments
 (0)