Skip to content

Commit a7a9404

Browse files
author
Ondrej Scecina
committed
Enable OAuth2 authentication.
1 parent de929af commit a7a9404

File tree

8 files changed

+61
-30
lines changed

8 files changed

+61
-30
lines changed

packages/opal-client/opal_client/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ def _configure_api_routes(self, app: FastAPI):
259259
policy_router = init_policy_router(policy_updater=self.policy_updater)
260260
data_router = init_data_router(data_updater=self.data_updater)
261261
policy_store_router = init_policy_store_router(self.authenticator)
262-
callbacks_router = init_callbacks_api(self.authenticator, self._callbacks_register)
262+
callbacks_router = init_callbacks_api(
263+
self.authenticator, self._callbacks_register
264+
)
263265

264266
# mount the api routes on the app object
265267
app.include_router(policy_router, tags=["Policy Updater"])
Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
1+
from urllib.parse import parse_qs, urlencode, urlparse
2+
13
import aiohttp
24
from aiohttp.client import ClientSession
35
from opal_client.logger import logger
4-
from urllib.parse import urlencode, urlparse, parse_qs
56

67
from .updater import DefaultDataUpdater
78

89

910
class OAuth2DataUpdater(DefaultDataUpdater):
10-
async def _load_policy_data_config(self, url: str, headers) -> aiohttp.ClientResponse:
11+
async def _load_policy_data_config(
12+
self, url: str, headers
13+
) -> aiohttp.ClientResponse:
1114
await self._authenticator.authenticate(headers)
1215

1316
async with ClientSession(headers=headers) as session:
14-
response = await session.get(url, **self._ssl_context_kwargs, allow_redirects=False)
17+
response = await session.get(
18+
url, **self._ssl_context_kwargs, allow_redirects=False
19+
)
1520

1621
if response.status == 307:
17-
return await self._load_redirected_policy_data_config(response.headers['location'], headers)
22+
return await self._load_redirected_policy_data_config(
23+
response.headers['location'], headers
24+
)
1825
else:
1926
return response
2027

2128
async def _load_redirected_policy_data_config(self, url: str, headers):
2229
redirect_url = self.__redirect_url(url)
2330

24-
logger.info("Redirecting to data-sources configuration '{source}'", source=redirect_url)
31+
logger.info(
32+
"Redirecting to data-sources configuration '{source}'", source=redirect_url
33+
)
2534

2635
async with ClientSession(headers=headers) as session:
27-
return await session.get(redirect_url, **self._ssl_context_kwargs, allow_redirects=False)
36+
return await session.get(
37+
redirect_url, **self._ssl_context_kwargs, allow_redirects=False
38+
)
2839

2940
def __redirect_url(self, url: str) -> str:
3041
u = urlparse(url)
3142
query = parse_qs(u.query, keep_blank_values=True)
32-
query.pop('token', None)
43+
query.pop("token", None)
3344
u = u._replace(query=urlencode(query, True))
3445

35-
return u.geturl()
46+
return u.geturl()

packages/opal-client/opal_client/data/updater.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ async def get_policy_data_config(self, url: str = None) -> DataSourceConfig:
241241
headers = {}
242242
if self._extra_headers is not None:
243243
headers = self._extra_headers.copy()
244-
headers['Accept'] = "application/json"
244+
headers["Accept"] = "application/json"
245245

246246
try:
247247
response = await self._load_policy_data_config(url, headers)
@@ -257,7 +257,9 @@ async def get_policy_data_config(self, url: str = None) -> DataSourceConfig:
257257
logger.exception(f"Failed to load data sources config")
258258
raise
259259

260-
async def _load_policy_data_config(self, url: str, headers) -> aiohttp.ClientResponse:
260+
async def _load_policy_data_config(
261+
self, url: str, headers
262+
) -> aiohttp.ClientResponse:
261263
async with ClientSession(headers=headers) as session:
262264
return await session.get(url, **self._ssl_context_kwargs)
263265

@@ -527,7 +529,9 @@ async def _store_fetched_update(self, update_item):
527529
policy_data = result
528530
# Create a report on the data-fetching
529531
report = DataEntryReport(
530-
entry=entry, hash=DataUpdater.calc_hash(policy_data), fetched=True
532+
entry=entry,
533+
hash=DataUpdater.calc_hash(policy_data),
534+
fetched=True
531535
)
532536

533537
try:

packages/opal-client/opal_client/tests/data_updater_test.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121

2222
from opal_client.config import opal_client_config
2323
from opal_client.data.rpc import TenantAwareRpcEventClientMethods
24-
from opal_client.data.updater import DataSourceEntry, DataUpdate, DataUpdater, DefaultDataUpdater
24+
from opal_client.data.updater import (
25+
DataSourceEntry,
26+
DataUpdate,
27+
DataUpdater,
28+
DefaultDataUpdater,
29+
)
2530
from opal_client.policy_store.policy_store_client_factory import (
2631
PolicyStoreClientFactory,
2732
)

packages/opal-common/opal_common/authentication/jwk.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import jwt
21
import httpx
32

3+
import jwt
44
from cachetools import TTLCache
55
from opal_common.authentication.verifier import Unauthorized
66

77
class JWKManager:
8-
def __init__(self, openid_configuration_url, jwt_algorithm, cache_maxsize, cache_ttl):
8+
def __init__(
9+
self, openid_configuration_url, jwt_algorithm, cache_maxsize, cache_ttl
10+
):
911
self._openid_configuration_url = openid_configuration_url
1012
self._jwt_algorithm = jwt_algorithm
1113
self._cache = TTLCache(maxsize=cache_maxsize, ttl=cache_ttl)
1214

1315
def public_key(self, token):
1416
header = jwt.get_unverified_header(token)
15-
kid = header['kid']
17+
kid = header["kid"]
1618

1719
public_key = self._cache.get(kid)
1820
if public_key is None:
@@ -40,6 +42,8 @@ def _openid_configuration(self):
4042
response = httpx.get(self._openid_configuration_url)
4143

4244
if response.status_code != httpx.codes.OK:
43-
raise Unauthorized(description=f"invalid status code {response.status_code}")
45+
raise Unauthorized(
46+
description=f"invalid status code {response.status_code}"
47+
)
4448

4549
return response.json()

packages/opal-common/opal_common/authentication/oauth2.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import asyncio
2-
import httpx
32
import time
3+
from typing import Optional
44

5-
from cachetools import cached, TTLCache
5+
import httpx
6+
from cachetools import TTLCache, cached
67
from fastapi import Header
78
from httpx import AsyncClient, BasicAuth
89
from opal_common.authentication.authenticator import Authenticator
@@ -11,13 +12,12 @@
1112
from opal_common.authentication.signer import JWTSigner
1213
from opal_common.authentication.verifier import JWTVerifier, Unauthorized
1314
from opal_common.config import opal_common_config
14-
from typing import Optional
1515

1616
class _OAuth2Authenticator(Authenticator):
1717
async def authenticate(self, headers):
1818
if "Authorization" not in headers:
1919
token = await self.token()
20-
headers['Authorization'] = f"Bearer {token}"
20+
headers["Authorization"] = f"Bearer {token}"
2121

2222

2323
class OAuth2ClientCredentialsAuthenticator(_OAuth2Authenticator):
@@ -61,7 +61,7 @@ async def token(self):
6161

6262
async with AsyncClient() as client:
6363
response = await client.post(self._token_url, auth=auth, data=data)
64-
return (response.json())['access_token']
64+
return (response.json())["access_token"]
6565

6666
def __call__(self, authorization: Optional[str] = Header(None)) -> {}:
6767
token = get_token_from_header(authorization)
@@ -79,10 +79,12 @@ def verify(self, token: str) -> {}:
7979
return claims
8080

8181
def _verify_opaque(self, token: str) -> {}:
82-
response = httpx.post(self._introspect_url, data={'token': token})
82+
response = httpx.post(self._introspect_url, data={"token": token})
8383

8484
if response.status_code != httpx.codes.OK:
85-
raise Unauthorized(description=f"invalid status code {response.status_code}")
85+
raise Unauthorized(
86+
description=f"invalid status code {response.status_code}"
87+
)
8688

8789
claims = response.json()
8890
active = claims.get("active", False)
@@ -152,13 +154,15 @@ async def token(self):
152154
claims = self._delegate.verify(token)
153155

154156
self._token = token
155-
self._exp = claims['exp']
157+
self._exp = claims["exp"]
156158

157159
return self._token
158160

159-
@cached(cache=TTLCache(
160-
maxsize=opal_common_config.OAUTH2_TOKEN_VERIFY_CACHE_MAXSIZE,
161-
ttl=opal_common_config.OAUTH2_TOKEN_VERIFY_CACHE_TTL
162-
))
161+
@cached(
162+
cache=TTLCache(
163+
maxsize=opal_common_config.OAUTH2_TOKEN_VERIFY_CACHE_MAXSIZE,
164+
ttl=opal_common_config.OAUTH2_TOKEN_VERIFY_CACHE_TTL
165+
)
166+
)
163167
def __call__(self, authorization: Optional[str] = Header(None)) -> {}:
164168
return self._delegate(authorization)

packages/opal-server/opal_server/data/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
from fastapi import APIRouter, Depends, Header, HTTPException, status
44
from fastapi.responses import RedirectResponse
5+
from opal_common.authentication.authenticator import Authenticator
56
from opal_common.authentication.authz import (
67
require_peer_type,
78
restrict_optional_topics_to_publish,
89
)
9-
from opal_common.authentication.authenticator import Authenticator
1010
from opal_common.authentication.deps import get_token_from_header
1111
from opal_common.authentication.types import JWTClaims
1212
from opal_common.authentication.verifier import Unauthorized

packages/requires.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ fastapi-utils>=0.2.1,<1
1313
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
1414
anyio>=4.4.0 # not directly required, pinned by Snyk to avoid a vulnerability
1515
starlette>=0.40.0 # not directly required, pinned by Snyk to avoid a vulnerability
16+
tls-cert-refresh-period

0 commit comments

Comments
 (0)