Coverage for /usr/local/lib/python3.10/site-packages/opal_server-0.0.0-py3.10.egg/opal_server/data/api.py: 73%
48 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from typing import Optional
3from fastapi import APIRouter, Depends, Header, HTTPException, status
4from fastapi.responses import RedirectResponse
5from opal_common.authentication.authz import (
6 require_peer_type,
7 restrict_optional_topics_to_publish,
8)
9from opal_common.authentication.deps import JWTAuthenticator, get_token_from_header
10from opal_common.authentication.types import JWTClaims
11from opal_common.authentication.verifier import Unauthorized
12from opal_common.logger import logger
13from opal_common.schemas.data import (
14 DataSourceConfig,
15 DataUpdate,
16 DataUpdateReport,
17 ServerDataSourceConfig,
18)
19from opal_common.schemas.security import PeerType
20from opal_common.urls import set_url_query_param
21from opal_server.config import opal_server_config
22from opal_server.data.data_update_publisher import DataUpdatePublisher
25def init_data_updates_router(
26 data_update_publisher: DataUpdatePublisher,
27 data_sources_config: ServerDataSourceConfig,
28 authenticator: JWTAuthenticator,
29):
30 router = APIRouter()
32 @router.get(opal_server_config.ALL_DATA_ROUTE)
33 async def default_all_data():
34 """A fake data source configured to be fetched by the default data
35 source config.
37 If the user deploying OPAL did not set DATA_CONFIG_SOURCES
38 properly, OPAL clients will be hitting this route, which will
39 return an empty dataset (empty dict).
40 """
41 logger.warning(
42 "Serving default all-data route, meaning DATA_CONFIG_SOURCES was not configured!"
43 )
44 return {}
46 @router.post(
47 opal_server_config.DATA_CALLBACK_DEFAULT_ROUTE,
48 dependencies=[Depends(authenticator)],
49 )
50 async def log_client_update_report(report: DataUpdateReport):
51 """A data update callback to be called by the OPAL client after
52 completing an update.
54 If the user deploying OPAL-client did not set
55 OPAL_DEFAULT_UPDATE_CALLBACKS properly, this method will be
56 called as the default callback (will simply log the report).
57 """
58 logger.info(
59 "Received update report: {report}",
60 report=report.dict(
61 exclude={"reports": {"__all__": {"entry": {"config", "data"}}}}
62 ),
63 )
64 return {} # simply returns 200
66 @router.get(
67 opal_server_config.DATA_CONFIG_ROUTE,
68 response_model=DataSourceConfig,
69 responses={
70 307: {
71 "description": "The data source configuration is available at another location (redirect)"
72 },
73 },
74 dependencies=[Depends(authenticator)],
75 )
76 async def get_data_sources_config(authorization: Optional[str] = Header(None)):
77 """Provides OPAL clients with their base data config, meaning from
78 where they should fetch a *complete* picture of the policy data they
79 need.
81 Clients will use this config to pull all data when they
82 initially load and when they are reconnected to server after a
83 period of disconnection (in which they cannot receive
84 incremental updates).
85 """
86 token = get_token_from_header(authorization)
87 if data_sources_config.config is not None: 87 ↛ 90line 87 didn't jump to line 90 because the condition on line 87 was always true
88 logger.info("Serving source configuration")
89 return data_sources_config.config
90 elif data_sources_config.external_source_url is not None:
91 url = str(data_sources_config.external_source_url)
92 short_token = token[:5] + "..." + token[-5:]
93 logger.info(
94 "Source configuration is available at '{url}', redirecting with token={token} (abbrv.)",
95 url=url,
96 token=short_token,
97 )
98 redirect_url = set_url_query_param(url, "token", token)
99 return RedirectResponse(url=redirect_url)
100 else:
101 logger.error(
102 "data source configuration is invalid: neither an inline config "
103 "nor an external_source_url was provided"
104 )
105 raise HTTPException(
106 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
107 detail="Did not find a data source configuration!",
108 )
110 @router.post(opal_server_config.DATA_CONFIG_ROUTE)
111 async def publish_data_update_event(
112 update: DataUpdate, claims: JWTClaims = Depends(authenticator)
113 ):
114 """Provides data providers (i.e: one of the backend services owned by
115 whomever deployed OPAL) with the ability to push incremental policy
116 data updates to OPAL clients.
118 Each update contains instructions on:
119 - how to fetch the data
120 - where should OPAL client store the data in OPA document hierarchy
121 - what clients should receive the update (through topics, only clients subscribed to provided topics will be notified)
122 """
123 try:
124 require_peer_type(
125 authenticator, claims, PeerType.datasource
126 ) # may throw Unauthorized
127 restrict_optional_topics_to_publish(
128 authenticator, claims, update
129 ) # may throw Unauthorized
130 except Unauthorized as e:
131 logger.error(f"Unauthorized to publish update: {repr(e)}")
132 raise
134 await data_update_publisher.publish_data_updates(update)
135 return {"status": "ok"}
137 return router