def test_oidc_device_flow_auto_detect(requests_mock, caplog, mode, use_pkce, client_secret, expected_fields): client_id = "myclient" oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration" oidc_mock = OidcMock( requests_mock=requests_mock, expected_grant_type="urn:ietf:params:oauth:grant-type:device_code", expected_client_id=client_id, oidc_discovery_url=oidc_discovery_url, expected_fields=expected_fields, state={ "device_code_callback_timeline": ["authorization_pending", "slow_down", "great success"] }, scopes_supported=["openid", "df"]) provider = OidcProviderInfo(discovery_url=oidc_discovery_url, scopes=["df"]) display = [] authenticator = OidcDeviceAuthenticator(client_info=OidcClientInfo( client_id=client_id, provider=provider, client_secret=client_secret), display=display.append, use_pkce=use_pkce) with mock.patch.object(openeo.rest.auth.oidc.time, "sleep") as sleep: with caplog.at_level(logging.INFO): tokens = authenticator.get_tokens() assert oidc_mock.state["access_token"] == tokens.access_token assert re.search( r"visit https://auth\.test/dc and enter the user code {c!r}".format( c=oidc_mock.state['user_code']), display[0]) assert display[1] == "Authorized successfully." assert sleep.mock_calls == [mock.call(2), mock.call(2), mock.call(7)] assert re.search( r"Authorization pending\..*Polling too fast, will slow down\..*Authorized successfully\.", caplog.text, flags=re.DOTALL)
def authenticate_oidc_device(self, client_id: str = None, client_secret: str = None, provider_id: str = None, store_refresh_token=False, use_pkce: Union[bool, None] = None, **kwargs) -> 'Connection': """ Authenticate with OAuth Device Authorization grant/flow :param use_pkce: Use PKCE instead of client secret. If not set explicitly to `True` (use PKCE) or `False` (use client secret), it will be attempted to detect the best mode automatically. Note that PKCE for device code is not widely supported among OIDC providers. .. versionchanged:: 0.5.1 Add :py:obj:`use_pkce` argument """ 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.DEVICE_CODE_PKCE ], ) authenticator = OidcDeviceAuthenticator(client_info=client_info, use_pkce=use_pkce, **kwargs) return self._authenticate_oidc(authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token)
def authenticate_oidc(self, provider_id: str = None, client_id: Union[str, None] = None, client_secret: Union[str, None] = None, store_refresh_token: bool = True): """ Do OpenID Connect authentication, first trying refresh tokens and falling back on device code flow. .. versionadded:: 0.6.0 """ 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.DEVICE_CODE_PKCE, DefaultOidcClientGrant.REFRESH_TOKEN ]) # Try refresh token first. refresh_token = self._get_refresh_token_store().get_refresh_token( issuer=client_info.provider.issuer, client_id=client_info.client_id) if refresh_token: try: _log.info( "Found refresh token: trying refresh token based authentication." ) authenticator = OidcRefreshTokenAuthenticator( client_info=client_info, refresh_token=refresh_token) con = self._authenticate_oidc( authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token) # TODO: pluggable/jupyter-aware display function? print("Authenticated using refresh token.") return con except OidcException as e: _log.info( "Refresh token based authentication failed: {e}.".format( e=e)) # Fall back on device code flow # TODO: make it possible to do other fallback flows too? _log.info("Trying device code flow.") authenticator = OidcDeviceAuthenticator(client_info=client_info) con = self._authenticate_oidc(authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token) print("Authenticated using device code flow.") return con
def authenticate_oidc_device( self, client_id: str=None, client_secret: str=None, provider_id: str = None, store_refresh_token=False, **kwargs ) -> 'Connection': """ Authenticate with OAuth Device Authorization grant/flow 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 = OidcDeviceAuthenticator(client_info=client_info, **kwargs) return self._authenticate_oidc(authenticator, provider_id=provider_id, store_refresh_token=store_refresh_token)
def authenticate_oidc_device(self, client_id: str, client_secret: str, provider_id: str = None, **kwargs) -> 'Connection': """ Authenticate with OAuth Device Authorization grant/flow WARNING: this API is in experimental phase """ provider_id, provider = self._get_oidc_provider(provider_id) client_info = OidcClientInfo(client_id=client_id, provider=provider, client_secret=client_secret) authenticator = OidcDeviceAuthenticator(client_info=client_info, **kwargs) return self._authenticate_oidc(authenticator, provider_id=provider_id)