コード例 #1
0
    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)
コード例 #2
0
    def _get_oidc_provider_and_client_info(
            self, provider_id: str,
            client_id: Union[str, None], client_secret: Union[str, None]
    ) -> Tuple[str, OidcClientInfo]:
        """
        Resolve provider_id and client info (as given or from config)

        :param provider_id: id of OIDC provider as specified by backend (/credentials/oidc).
            Can be None if there is just one provider.

        :return: (client_id, client_secret)
        """
        provider_id, provider = self._get_oidc_provider(provider_id)

        if client_id is None:
            client_id, client_secret = self._get_auth_config().get_oidc_client_configs(
                backend=self._orig_url, provider_id=provider_id
            )
            _log.info("Using client_id {c!r} from config (provider {p!r})".format(c=client_id, p=provider_id))
            if client_id is None:
                raise OpenEoClientException("No client ID found.")

        client_info = OidcClientInfo(client_id=client_id, client_secret=client_secret, provider=provider)

        return provider_id, client_info
コード例 #3
0
    def authenticate_oidc_refresh_token(
            self,
            client_id: str,
            refresh_token: str = None,
            client_secret: str = None,
            provider_id: str = None) -> 'Connection':
        """
        OpenId Connect Refresh Token

        WARNING: this API is in experimental phase
        """
        provider_id, provider = self._get_oidc_provider(provider_id)
        if refresh_token is None:
            store = RefreshTokenStore()
            # TODO: allow client_id/secret to be None and fetch it from config/cache?
            refresh_token = store.get(issuer=provider.issuer,
                                      client_id=client_id)
            if refresh_token is None:
                raise OpenEoClientException("No refresh token")

        client_info = OidcClientInfo(client_id=client_id,
                                     provider=provider,
                                     client_secret=client_secret)
        authenticator = OidcRefreshTokenAuthenticator(
            client_info=client_info, refresh_token=refresh_token)
        return self._authenticate_oidc(authenticator, provider_id=provider_id)
コード例 #4
0
def test_oidc_resource_owner_password_credentials_flow(requests_mock):
    client_id = "myclient"
    client_secret = "$3cr3t"
    oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration"
    username, password = "******", "j0hn"
    oidc_mock = OidcMock(
        requests_mock=requests_mock,
        expected_grant_type="password",
        expected_client_id=client_id,
        expected_fields={
            "username": username,
            "password": password,
            "scope": "openid testpwd",
            "client_secret": client_secret
        },
        oidc_discovery_url=oidc_discovery_url,
        scopes_supported=["openid", "testpwd"],
    )
    provider = OidcProviderInfo(discovery_url=oidc_discovery_url,
                                scopes=["testpwd"])

    authenticator = OidcResourceOwnerPasswordAuthenticator(
        client_info=OidcClientInfo(client_id=client_id,
                                   provider=provider,
                                   client_secret=client_secret),
        username=username,
        password=password,
    )
    tokens = authenticator.get_tokens()
    assert oidc_mock.state["access_token"] == tokens.access_token
コード例 #5
0
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)
コード例 #6
0
def test_oidc_client_info_uess_device_flow_pkce_support(requests_mock):
    oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration"
    oidc_mock = OidcMock(
        requests_mock=requests_mock,
        oidc_discovery_url=oidc_discovery_url,
        expected_grant_type=None,
    )
    provider = OidcProviderInfo(
        discovery_url=oidc_discovery_url,
        default_clients=[
            {
                "id": "c1",
                "grant_types": ["authorization_code+pkce"]
            },
            {
                "id": "c2",
                "grant_types":
                ["urn:ietf:params:oauth:grant-type:device_code"]
            },
            {
                "id":
                "c3",
                "grant_types":
                ["urn:ietf:params:oauth:grant-type:device_code+pkce"]
            },
            {
                "id":
                "c4",
                "grant_types": [
                    "refresh_token",
                    "urn:ietf:params:oauth:grant-type:device_code+pkce"
                ]
            },
        ])

    for client_id, expected in [("c1", False), ("c2", False), ("c3", True),
                                ("c4", True), ("foo", False)]:
        client_info = OidcClientInfo(client_id=client_id, provider=provider)
        assert client_info.guess_device_flow_pkce_support() is expected
コード例 #7
0
def test_oidc_client_credentials_flow(requests_mock):
    client_id = "myclient"
    oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration"
    client_secret = "$3cr3t"
    oidc_mock = OidcMock(requests_mock=requests_mock,
                         expected_grant_type="client_credentials",
                         expected_client_id=client_id,
                         expected_fields={"client_secret": client_secret},
                         oidc_discovery_url=oidc_discovery_url)

    provider = OidcProviderInfo(discovery_url=oidc_discovery_url)
    authenticator = OidcClientCredentialsAuthenticator(
        client_info=OidcClientInfo(client_id=client_id,
                                   provider=provider,
                                   client_secret=client_secret))
    tokens = authenticator.get_tokens()
    assert oidc_mock.state["access_token"] == tokens.access_token
コード例 #8
0
    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)
コード例 #9
0
    def _get_oidc_provider_and_client_info(
        self,
        provider_id: str,
        client_id: Union[str, None],
        client_secret: Union[str, None],
        default_client_grant_types: Union[None,
                                          List[DefaultOidcClientGrant]] = None
    ) -> Tuple[str, OidcClientInfo]:
        """
        Resolve provider_id and client info (as given or from config)

        :param provider_id: id of OIDC provider as specified by backend (/credentials/oidc).
            Can be None if there is just one provider.

        :return: OIDC provider id and client info
        """
        provider_id, provider = self._get_oidc_provider(provider_id)

        if client_id is None:
            _log.debug("No client_id: checking config for prefered client_id")
            client_id, client_secret = self._get_auth_config(
            ).get_oidc_client_configs(backend=self._orig_url,
                                      provider_id=provider_id)
            if client_id:
                _log.info("Using client_id {c!r} from config (provider {p!r})".
                          format(c=client_id, p=provider_id))
        if client_id is None and default_client_grant_types:
            # Try "default_client" from backend's provider info.
            _log.debug(
                "No client_id given: checking default client in backend's provider info"
            )
            client_id = provider.get_default_client_id(
                grant_types=default_client_grant_types)
            if client_id:
                _log.info(
                    "Using default client_id {c!r} from OIDC provider {p!r} info."
                    .format(c=client_id, p=provider_id))
        if client_id is None:
            raise OpenEoClientException("No client_id found.")

        client_info = OidcClientInfo(client_id=client_id,
                                     client_secret=client_secret,
                                     provider=provider)

        return provider_id, client_info
コード例 #10
0
    def authenticate_oidc_client_credentials(
        self,
        client_id: str,
        client_secret: str = None,
        provider_id: str = None,
    ) -> 'Connection':
        """
        OpenID Connect Client Credentials flow.

        WARNING: this API is in experimental phase
        """
        provider_id, provider = self._get_oidc_provider(provider_id)
        # TODO: load credentials from file/config
        client_info = OidcClientInfo(client_id=client_id,
                                     provider=provider,
                                     client_secret=client_secret)
        authenticator = OidcClientCredentialsAuthenticator(
            client_info=client_info)
        return self._authenticate_oidc(authenticator, provider_id=provider_id)
コード例 #11
0
def test_oidc_refresh_token_flow_no_secret(requests_mock, caplog):
    client_id = "myclient"
    refresh_token = "r3fr35h.d4.t0k3n.w1lly4"
    oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration"
    oidc_mock = OidcMock(requests_mock=requests_mock,
                         expected_grant_type="refresh_token",
                         expected_client_id=client_id,
                         oidc_discovery_url=oidc_discovery_url,
                         expected_fields={
                             "scope": "openid",
                             "refresh_token": refresh_token
                         },
                         scopes_supported=["openid"])
    provider = OidcProviderInfo(discovery_url=oidc_discovery_url)
    authenticator = OidcRefreshTokenAuthenticator(client_info=OidcClientInfo(
        client_id=client_id, provider=provider),
                                                  refresh_token=refresh_token)
    tokens = authenticator.get_tokens()
    assert oidc_mock.state["access_token"] == tokens.access_token
    assert oidc_mock.state["refresh_token"] == tokens.refresh_token
コード例 #12
0
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
コード例 #13
0
    def authenticate_oidc_resource_owner_password_credentials(
            self,
            client_id: str,
            username: str,
            password: str,
            client_secret: str = None,
            provider_id: str = None) -> 'Connection':
        """
        OpenId Connect Resource Owner Password Credentials

        WARNING: this API is in experimental phase
        """
        provider_id, provider = self._get_oidc_provider(provider_id)
        # TODO: load password from file/config
        client_info = OidcClientInfo(client_id=client_id,
                                     provider=provider,
                                     client_secret=client_secret)
        authenticator = OidcResourceOwnerPasswordAuthenticator(
            client_info=client_info, username=username, password=password)
        return self._authenticate_oidc(authenticator, provider_id=provider_id)
コード例 #14
0
def test_oidc_refresh_token_invalid_token(requests_mock, caplog):
    client_id = "myclient"
    refresh_token = "wr0n9.t0k3n"
    oidc_discovery_url = "http://oidc.test/.well-known/openid-configuration"
    oidc_mock = OidcMock(requests_mock=requests_mock,
                         expected_grant_type="refresh_token",
                         expected_client_id=client_id,
                         oidc_discovery_url=oidc_discovery_url,
                         expected_fields={
                             "scope": "openid",
                             "refresh_token": "c0rr3ct.t0k3n"
                         },
                         scopes_supported=["openid"])
    provider = OidcProviderInfo(discovery_url=oidc_discovery_url)
    authenticator = OidcRefreshTokenAuthenticator(client_info=OidcClientInfo(
        client_id=client_id, provider=provider),
                                                  refresh_token=refresh_token)
    with pytest.raises(
            OidcException,
            match="Failed to retrieve access token.*invalid refresh token"):
        tokens = authenticator.get_tokens()
コード例 #15
0
    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)