-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8219921
commit f251970
Showing
18 changed files
with
433 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
|
||
import abc | ||
import asyncio | ||
from dataclasses import dataclass | ||
|
||
from httpx import AsyncClient, Client | ||
from jwskate import Jwt | ||
from datetime import datetime | ||
|
||
|
||
@dataclass | ||
class AuthenticationResult: | ||
success: bool | ||
error: str = "" | ||
payload: dict | None = None | ||
|
||
|
||
def find_jwk(jwks, jwt): | ||
jwk_key = None | ||
jwks_keys = jwks["keys"] | ||
for key in jwks_keys: | ||
if key["kid"] == jwt.headers["kid"]: | ||
jwk_key = { | ||
"kty": key["kty"], | ||
"kid": key["kid"], | ||
"use": key["use"], | ||
"alg": key["alg"], | ||
"n": key["n"], | ||
"e": key["e"], | ||
} | ||
break | ||
return jwk_key | ||
|
||
|
||
class IHttpServiceGet(abc.ABC): | ||
@abc.abstractmethod | ||
async def get_async(self, url: str) -> dict: | ||
pass | ||
|
||
class XHttpServiceGet(IHttpServiceGet): | ||
def __init__(self, | ||
http_client: Client, | ||
http_async_client: AsyncClient): | ||
self.http_async_client = http_async_client | ||
self.http_client = http_client | ||
|
||
async def get_async(self, url: str) -> dict: | ||
reponse = await self.http_async_client.get(url) | ||
return reponse.json() | ||
|
||
|
||
class Authentication: | ||
def __init__(self, | ||
issuer: str | None, | ||
scopes: list[str], | ||
api_audience: str | None, | ||
service: IHttpServiceGet): | ||
self.service = service | ||
self.issuer = issuer | ||
self.api_audience = api_audience | ||
self.algorithms = ["RS256"] | ||
self.scopes = scopes | ||
self.cache_timestamp = 0 | ||
self.cache_jwks = None | ||
self.cache_token_endpoint = None | ||
|
||
async def _get_jwks_async(self, service: IHttpServiceGet, issuer: str) -> dict: | ||
timestamp = datetime.timestamp(datetime.now()) | ||
one_day = 86400 | ||
if self.cache_timestamp + one_day < timestamp: | ||
wellknowurl = await service.get_async(issuer + "/.well-known/openid-configuration") | ||
self.cache_jwks = await service.get_async(wellknowurl["jwks_uri"]) | ||
self.cache_token_endpoint = wellknowurl["token_endpoint"] | ||
self.cache_timestamp = timestamp | ||
|
||
return self.cache_jwks | ||
|
||
async def get_token_endpoint_async(self) -> str: | ||
await self._get_jwks_async(self.service, self.issuer) | ||
return self.cache_token_endpoint | ||
|
||
async def validate_async(self, token: str) -> AuthenticationResult: | ||
try: | ||
jwt = Jwt(token) | ||
if jwt.headers["alg"].upper() not in self.algorithms: | ||
return AuthenticationResult(success=False, error="wrong algorithm used") | ||
jwks = await self._get_jwks_async(self.service, self.issuer) | ||
jwk_key = find_jwk(jwks, jwt) | ||
if jwk_key is None: | ||
return AuthenticationResult(success=False, error="JWK key not found") | ||
|
||
payload = jwt.claims | ||
scopes = payload["scope"].split(" ") | ||
for scope in self.scopes: | ||
if scope not in scopes: | ||
return AuthenticationResult(success=False, error="scope not found") | ||
|
||
if not self.api_audience: | ||
if jwt.validate(jwk_key, issuer=self.issuer): | ||
return AuthenticationResult(success=False, error="signature verification failed") | ||
|
||
if jwt.validate(jwk_key, issuer=self.issuer, audience=self.api_audience): | ||
return AuthenticationResult(success=False, error="signature verification failed") | ||
|
||
return AuthenticationResult(success=True, payload=payload) | ||
|
||
except Exception as e: | ||
exception_message = str(e) | ||
return AuthenticationResult(success=False, error=exception_message) |
37 changes: 37 additions & 0 deletions
37
production/ia-worker/app/oidc/authentication_middleware.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from http import HTTPStatus | ||
|
||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint | ||
from starlette.requests import Request | ||
from starlette.responses import Response | ||
|
||
from authentication import Authentication, IHttpServiceGet | ||
|
||
|
||
def authentication_middleware(issuer: str, | ||
scopes: list[str], | ||
api_audience: str, | ||
exclude_urls: list[str], | ||
service: IHttpServiceGet): | ||
authentication = Authentication(issuer, scopes, api_audience, service) | ||
|
||
class AuthenticateMiddleware(BaseHTTPMiddleware): | ||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint): | ||
try: | ||
if request.url.path in exclude_urls or request.method == "OPTIONS": | ||
response = await call_next(request) | ||
return response | ||
|
||
authorization = request.headers.get('Authorization') | ||
if not authorization: | ||
return Response(status_code=HTTPStatus.UNAUTHORIZED) | ||
validation_result = await authentication.validate_async(authorization.replace("Bearer ", "")) | ||
if not validation_result.success: | ||
return Response(status_code=HTTPStatus.UNAUTHORIZED) | ||
response = await call_next(request) | ||
return response | ||
except Exception as e: | ||
return Response(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, | ||
content="Internal Server Error: {0}".format( | ||
str(e))) | ||
|
||
return AuthenticateMiddleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,42 @@ | ||
import './App.css' | ||
import AudioRecorderComponent from "./AudioRecorderComponent.jsx"; | ||
import EnvironmentStarter from "./EnvironmentStarter.jsx"; | ||
import {OidcProvider, OidcSecure} from "@axa-fr/react-oidc"; | ||
|
||
const configuration = { | ||
client_id: 'interactive.public', | ||
redirect_uri: window.location.origin + '/authentication/callback', | ||
silent_redirect_uri: window.location.origin + '/authentication/silent-callback', | ||
scope: 'openid profile email api offline_access', | ||
authority: 'https://demo.duendesoftware.com', | ||
}; | ||
|
||
function App() { | ||
|
||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | ||
return ( | ||
<EnvironmentStarter> | ||
<div className="App"> | ||
<AudioRecorderComponent/> | ||
</div> | ||
</EnvironmentStarter> | ||
); | ||
} else { | ||
console.error("L'API mediaDevices n'est pas supportée par ce navigateur."); | ||
return ( | ||
<div className="App"> | ||
<p>L'API mediaDevices n'est pas supportée par ce navigateur.</p> | ||
</div> | ||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | ||
return ( | ||
<OidcProvider configuration={configuration}> | ||
<AppSecure/> | ||
</OidcProvider> | ||
); | ||
} | ||
} else { | ||
console.error("L'API mediaDevices n'est pas supportée par ce navigateur."); | ||
return ( | ||
<div className="App"> | ||
<p>L'API mediaDevices n'est pas supportée par ce navigateur.</p> | ||
</div> | ||
); | ||
} | ||
|
||
} | ||
|
||
const AppSecure = () => { | ||
return <OidcSecure> | ||
<EnvironmentStarter> | ||
<div className="App"> | ||
<AudioRecorderComponent/> | ||
</div> | ||
</EnvironmentStarter> | ||
</OidcSecure>; | ||
} | ||
|
||
export default App |
Oops, something went wrong.