def authenticate_OIDC(self, client_id: str, webbrowser_open=None, timeout=120) -> 'Connection': """ Authenticates a user to the backend using OpenID Connect. :param client_id: Client id to use for OpenID Connect authentication :param webbrowser_open: optional handler for the initial OAuth authentication request (opens a webbrowser by default) :param timeout: number of seconds after which to abort the authentication procedure """ # Local import to avoid importing the whole OpenID Connect dependency chain. TODO: just do global import? from openeo.rest.auth.oidc import OidcAuthCodePkceAuthenticator # Per spec: '/credentials/oidc' will redirect to OpenID Connect discovery document oidc_discovery_url = self.build_url('/credentials/oidc') authenticator = OidcAuthCodePkceAuthenticator( client_id=client_id, oidc_discovery_url=oidc_discovery_url, webbrowser_open=webbrowser_open, timeout=timeout, ) # Do the Oauth/OpenID Connect flow and use the access token as bearer token. tokens = authenticator.get_tokens() # TODO: ability to refresh the token when expired? self.auth = BearerAuth(bearer=tokens.access_token) return self
def test_oidc_flow(oidc_test_setup): # see test/rest/conftest.py for `oidc_test_setup` fixture client_id = "myclient" oidc_discovery_url = "http://oidc.example.com/.well-known/openid-configuration" state, webbrowser_open = oidc_test_setup( client_id=client_id, oidc_discovery_url=oidc_discovery_url) authenticator = OidcAuthCodePkceAuthenticator( client_id=client_id, oidc_discovery_url=oidc_discovery_url, webbrowser_open=webbrowser_open) # Do the Oauth/OpenID Connect flow tokens = authenticator.get_tokens() assert state["access_token"] == tokens.access_token
def authenticate_OIDC( self, client_id: str, provider_id: str = None, webbrowser_open=None, timeout=120, server_address: Tuple[str, int] = None ) -> 'Connection': """ Authenticates a user to the backend using OpenID Connect. :param client_id: Client id to use for OpenID Connect authentication :param webbrowser_open: optional handler for the initial OAuth authentication request (opens a webbrowser by default) :param timeout: number of seconds after which to abort the authentication procedure :param server_address: optional tuple (hostname, port_number) to serve the OAuth redirect callback on TODO: deprecated? """ # TODO: option to increase log level temporarily? provider_id, provider = self._get_oidc_provider(provider_id) client_info = OidcClientInfo(client_id=client_id, provider=provider) authenticator = OidcAuthCodePkceAuthenticator( client_info=client_info, webbrowser_open=webbrowser_open, timeout=timeout, server_address=server_address, ) return self._authenticate_oidc(authenticator, provider_id=provider_id)
def token_callback(request, context): """Fake code to token exchange by Oauth Provider""" params = _get_query_params(query=request.text) assert params["client_id"] == client_id assert params["grant_type"] == "authorization_code" assert state[ "code_challenge"] == OidcAuthCodePkceAuthenticator.hash_code_verifier( params["code_verifier"]) assert params["code"] == "6uthc0d3" assert params["redirect_uri"] == state["redirect_uri"] state["access_token"] = _jwt_encode({}, { "sub": "123", "name": "john", "nonce": state["nonce"] }) return json.dumps({ "access_token": state["access_token"], "id_token": _jwt_encode({}, { "sub": "123", "name": "john", "nonce": state["nonce"] }), "refresh_token": _jwt_encode({}, {"nonce": state["nonce"]}), })
def authenticate_oidc_authorization_code( self, client_id: str = None, client_secret: str = None, provider_id: str = None, timeout: int = None, server_address: Tuple[str, int] = None, webbrowser_open: Callable = None, store_refresh_token=False, ) -> 'Connection': """ OpenID Connect Authorization Code Flow (with PKCE). WARNING: this API is in experimental phase """ provider_id, client_info = self._get_oidc_provider_and_client_info( provider_id=provider_id, client_id=client_id, client_secret=client_secret) authenticator = OidcAuthCodePkceAuthenticator( client_info=client_info, webbrowser_open=webbrowser_open, timeout=timeout, server_address=server_address) return self._authenticate_oidc(authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token)
def authenticate_oidc_authorization_code( self, client_id: str = None, client_secret: str = None, provider_id: str = None, timeout: int = None, server_address: Tuple[str, int] = None, webbrowser_open: Callable = None, store_refresh_token=False, ) -> 'Connection': """ OpenID Connect Authorization Code Flow (with PKCE). """ provider_id, client_info = self._get_oidc_provider_and_client_info( provider_id=provider_id, client_id=client_id, client_secret=client_secret, default_client_grant_types=[DefaultOidcClientGrant.AUTH_CODE_PKCE], ) authenticator = OidcAuthCodePkceAuthenticator( client_info=client_info, webbrowser_open=webbrowser_open, timeout=timeout, server_address=server_address) return self._authenticate_oidc(authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token)
def token_callback_authorization_code( self, request: requests_mock.request._RequestObjectProxy, context): """Fake code to token exchange by Oauth Provider""" params = self._get_query_params(query=request.text) assert params["client_id"] == self.expected_client_id assert params["grant_type"] == "authorization_code" assert self.state[ "code_challenge"] == OidcAuthCodePkceAuthenticator.hash_code_verifier( params["code_verifier"]) assert params["code"] == self.expected_authorization_code assert params["redirect_uri"] == self.state["redirect_uri"] self.state["access_token"] = self._jwt_encode( {}, { "sub": "123", "name": "john", "nonce": self.state["nonce"] }) return json.dumps({ "access_token": self.state["access_token"], "id_token": self._jwt_encode({}, { "sub": "123", "name": "john", "nonce": self.state["nonce"] }), "refresh_token": self._jwt_encode({}, {}), })
def test_oidc_auth_code_pkce_flow(requests_mock): requests_mock.get("http://oidc.test/.well-known/openid-configuration", json={"scopes_supported": ["openid"]}) client_id = "myclient" oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration" oidc_mock = OidcMock(requests_mock=requests_mock, expected_grant_type="authorization_code", expected_client_id=client_id, expected_fields={"scope": "openid testpkce"}, oidc_discovery_url=oidc_discovery_url, scopes_supported=["openid", "testpkce"]) provider = OidcProviderInfo(discovery_url=oidc_discovery_url, scopes=["openid", "testpkce"]) authenticator = OidcAuthCodePkceAuthenticator( client_info=OidcClientInfo(client_id=client_id, provider=provider), webbrowser_open=oidc_mock.webbrowser_open) # Do the Oauth/OpenID Connect flow tokens = authenticator.get_tokens() assert oidc_mock.state["access_token"] == tokens.access_token
def token_callback_authorization_code( self, request: requests_mock.request._RequestObjectProxy, context): """Fake code to token exchange by Oauth Provider""" params = self._get_query_params(query=request.text) assert params["client_id"] == self.expected_client_id assert params["grant_type"] == "authorization_code" assert self.state[ "code_challenge"] == OidcAuthCodePkceAuthenticator.hash_code_verifier( params["code_verifier"]) assert params["code"] == self.expected_authorization_code assert params["redirect_uri"] == self.state["redirect_uri"] return self._build_token_response()
def authenticate_oidc_authorization_code( self, client_id: str, client_secret: str = None, provider_id: str = None, timeout: int = None, server_address: Tuple[str, int] = None, webbrowser_open: Callable = None) -> 'Connection': """ OpenID Connect Authorization Code Flow (with PKCE). WARNING: this API is in experimental phase """ provider_id, provider = self._get_oidc_provider(provider_id) # TODO: load client info and settings from config file? client_info = OidcClientInfo(client_id=client_id, client_secret=client_secret, provider=provider) authenticator = OidcAuthCodePkceAuthenticator( client_info=client_info, webbrowser_open=webbrowser_open, timeout=timeout, server_address=server_address) return self._authenticate_oidc(authenticator, provider_id=provider_id)