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

1from typing import Optional 

2 

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 

23 

24 

25def init_data_updates_router( 

26 data_update_publisher: DataUpdatePublisher, 

27 data_sources_config: ServerDataSourceConfig, 

28 authenticator: JWTAuthenticator, 

29): 

30 router = APIRouter() 

31 

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. 

36 

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 {} 

45 

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. 

53 

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 

65 

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. 

80 

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 ) 

109 

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. 

117 

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 

133 

134 await data_update_publisher.publish_data_updates(update) 

135 return {"status": "ok"} 

136 

137 return router