Skip to content

Commit

Permalink
EP-3759 Streamline OIDC usage: when backend lists multiple OIDC provi…
Browse files Browse the repository at this point in the history
…ders, but user config just one: use that one

related: EP-3700/EP-3759/#192
  • Loading branch information
soxofaan committed Mar 25, 2021
1 parent 2e32779 commit 025b916
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 4 deletions.
13 changes: 10 additions & 3 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,16 @@ def _get_oidc_provider(self, provider_id: Union[str, None] = None) -> Tuple[str,
# No provider id given, but there is only one anyway: we can handle that.
provider_id, provider = providers.popitem()
else:
raise OpenEoClientException("No provider_id given. Available: {p!r}.".format(
p=list(providers.keys()))
)
# Check if there is a single provider in the config to use.
provider_configs = self._get_auth_config().get_oidc_provider_configs(backend=self._orig_url)
intersection = set(provider_configs.keys()).intersection(providers.keys())
if len(intersection) == 1:
provider_id = intersection.pop()
provider = providers[provider_id]
else:
raise OpenEoClientException("No provider_id given but multiple to choose from: {p!r}.".format(
p=list(providers.keys()))
)
provider = OidcProviderInfo.from_dict(provider)
else:
# Per spec: '/credentials/oidc' will redirect to OpenID Connect discovery document
Expand Down
95 changes: 94 additions & 1 deletion tests/rest/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ def test_authenticate_oidc_100_multiple_no_id(requests_mock):
# With all this set up, kick off the openid connect flow
conn = Connection(API_URL)
assert isinstance(conn.auth, NullAuth)
match = r"No provider_id given. Available: \[('fauth', 'bauth'|'bauth', 'fauth')\]\."
match = r"No provider_id given but multiple to choose from: \[('fauth', 'bauth'|'bauth', 'fauth')\]\."
with pytest.raises(OpenEoClientException, match=match):
conn.authenticate_OIDC(client_id=client_id, webbrowser_open=pytest.fail)

Expand Down Expand Up @@ -795,6 +795,99 @@ def test_authenticate_oidc_device_flow_client_from_config(requests_mock, auth_co
assert refresh_token_store.mock_calls == []


def test_authenticate_oidc_device_flow_multiple_providers_no_given(requests_mock, auth_config):
"""OIDC device flow with multiple OIDC providers and none specified to use."""
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
client_id = "myclient"
requests_mock.get(API_URL + 'credentials/oidc', json={
"providers": [
{"id": "fauth", "issuer": "https://fauth.test", "title": "Foo Auth", "scopes": ["openid", "w"]},
{"id": "bauth", "issuer": "https://bauth.test", "title": "Bar Auth", "scopes": ["openid", "w"]},
]
})
assert auth_config.load() == {}

# With all this set up, kick off the openid connect flow
conn = Connection(API_URL)
assert isinstance(conn.auth, NullAuth)
match = r"No provider_id given but multiple to choose from: \[('fauth', 'bauth'|'bauth', 'fauth')\]\."
with pytest.raises(OpenEoClientException, match=match):
conn.authenticate_oidc_device(client_id=client_id)


def test_authenticate_oidc_device_flow_multiple_provider_one_config_no_given(requests_mock, auth_config):
"""OIDC device flow + PKCE with multiple OIDC providers, one in config and none specified to use."""
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
client_id = "myclient"
requests_mock.get(API_URL + 'credentials/oidc', json={
"providers": [
{"id": "fauth", "issuer": "https://fauth.test", "title": "Foo", "scopes": ["openid"]},
{"id": "bauth", "issuer": "https://bauth.test", "title": "Bar", "scopes": ["openid"]},
]
})
oidc_mock = OidcMock(
requests_mock=requests_mock,
expected_grant_type="urn:ietf:params:oauth:grant-type:device_code",
expected_client_id=client_id,
expected_fields={
"scope": "openid", "code_verifier": True, "code_challenge": True
},
scopes_supported=["openid"],
oidc_discovery_url="https://fauth.test/.well-known/openid-configuration",
)
assert auth_config.load() == {}
auth_config.set_oidc_client_config(backend=API_URL, provider_id="fauth", client_id=client_id)

# With all this set up, kick off the openid connect flow
refresh_token_store = mock.Mock()
conn = Connection(API_URL, refresh_token_store=refresh_token_store)
assert isinstance(conn.auth, NullAuth)
oidc_mock.state["device_code_callback_timeline"] = ["great success"]
conn.authenticate_oidc_device()
assert isinstance(conn.auth, BearerAuth)
assert conn.auth.bearer == 'oidc/fauth/' + oidc_mock.state["access_token"]
assert refresh_token_store.mock_calls == []


def test_authenticate_oidc_device_flow_multiple_provider_one_config_no_given_default_client(requests_mock, auth_config):
"""
OIDC device flow + default_client + PKCE with multiple OIDC providers, one in config and none specified to use.
"""
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
default_client_id = "dadefaultklient"
requests_mock.get(API_URL + 'credentials/oidc', json={
"providers": [
{"id": "fauth", "issuer": "https://fauth.test", "title": "Foo", "scopes": ["openid"]},
{
"id": "bauth", "issuer": "https://bauth.test", "title": "Bar", "scopes": ["openid"],
"default_client": {"id": default_client_id}
},
]
})
oidc_mock = OidcMock(
requests_mock=requests_mock,
expected_grant_type="urn:ietf:params:oauth:grant-type:device_code",
expected_client_id=default_client_id,
expected_fields={
"scope": "openid", "code_verifier": True, "code_challenge": True
},
scopes_supported=["openid"],
oidc_discovery_url="https://bauth.test/.well-known/openid-configuration",
)
assert auth_config.load() == {}
auth_config.set_oidc_client_config(backend=API_URL, provider_id="bauth", client_id=None)

# With all this set up, kick off the openid connect flow
refresh_token_store = mock.Mock()
conn = Connection(API_URL, refresh_token_store=refresh_token_store)
assert isinstance(conn.auth, NullAuth)
oidc_mock.state["device_code_callback_timeline"] = ["great success"]
conn.authenticate_oidc_device()
assert isinstance(conn.auth, BearerAuth)
assert conn.auth.bearer == 'oidc/bauth/' + oidc_mock.state["access_token"]
assert refresh_token_store.mock_calls == []


def test_load_collection_arguments_040(requests_mock):
requests_mock.get(API_URL, json={"api_version": "0.4.0"})
conn = Connection(API_URL)
Expand Down

0 comments on commit 025b916

Please sign in to comment.