Skip to content

Commit

Permalink
Fixes #247: nicer error message when back-end does not support basic …
Browse files Browse the repository at this point in the history
…auth
  • Loading branch information
soxofaan committed Oct 11, 2021
1 parent b3fae62 commit 13a51da
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Raise `ProcessGraphVisitException` from `ProcessGraphVisitor.resolve_from_node()` (instead of generic `ValueError`)
- `DataCube.linear_scale_range` is now a shortcut for `DataCube.apply(lambda x:x.x.linear_scale_range( input_min, input_max, output_min, output_max))`.
Instead of creating an invalid process graph that tries to invoke linear_scale_range on a datacube directly.
- Nicer error message when back-end does not support basic auth ([#247](https://github.com/Open-EO/openeo-python-client/issues/247))


### Removed
Expand Down
2 changes: 2 additions & 0 deletions openeo/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from distutils.version import LooseVersion
from typing import Union

# Is this base class (still) useful?


class Capabilities(ABC):
"""Represents capabilities of a connection / back end."""
Expand Down
2 changes: 2 additions & 0 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ def authenticate_basic(self, username: str = None, password: str = None) -> 'Con
:param username: User name
:param password: User passphrase
"""
if not self.capabilities().supports_endpoint("/credentials/basic", method="GET"):
raise OpenEoClientException("This openEO back-end does not support basic authentication.")
if username is None:
username, password = self._get_auth_config().get_basic_auth(backend=self._orig_url)
if username is None:
Expand Down
6 changes: 6 additions & 0 deletions openeo/rest/rest_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ def has_features(self, method_name):
# Field: endpoints > ... TODO
pass

def supports_endpoint(self, path: str, method="GET"):
return any(
endpoint.get("path") == path and method.upper() in endpoint.get("methods", [])
for endpoint in self.capabilities.get("endpoints", [])
)

def currency(self):
""" Get default billing currency."""
return self.capabilities.get('billing', {}).get('currency')
Expand Down
26 changes: 20 additions & 6 deletions tests/rest/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
from .auth.test_oidc import OidcMock, assert_device_code_poll_sleep, ABSENT
from .. import load_json_resource


API_URL = "https://oeo.test/"

BASIC_ENDPOINTS = [{"path": "/credentials/basic", "methods": ["GET"]}]

# Trick to avoid linting/auto-formatting tools to complain about or fix unused imports of these pytest fixtures
auth_config = auth_config
refresh_token_store = refresh_token_store
Expand Down Expand Up @@ -193,7 +196,7 @@ def test_connection_other_domain_auth_headers(requests_mock, api_version):
def debug(request: requests.Request, context):
return repr(("hello world", request.headers))

requests_mock.get(API_URL, json={"api_version": api_version})
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})
requests_mock.get(API_URL + 'credentials/basic', json={"access_token": secret})
requests_mock.get("https://evilcorp.test/download/hello.txt", text=debug)

Expand Down Expand Up @@ -300,7 +303,8 @@ def test_connection_repr(requests_mock):
requests_mock.get("https://oeo.test/.well-known/openeo", status_code=200, json={
"versions": [{"api_version": "1.0.0", "url": "https://oeo.test/openeo/1.x/", "production": True}],
})
requests_mock.get("https://oeo.test/openeo/1.x/", status_code=200, json={"api_version": "1.0.0"})
requests_mock.get("https://oeo.test/openeo/1.x/", status_code=200,
json={"api_version": "1.0.0", "endpoints": BASIC_ENDPOINTS})
requests_mock.get("https://oeo.test/openeo/1.x/credentials/basic", json={"access_token": "w3lc0m3"})

conn = connect("https://oeo.test/")
Expand Down Expand Up @@ -360,7 +364,7 @@ def test_api_error_non_json(requests_mock):

def test_create_connection_lazy_auth_config(requests_mock, api_version):
user, pwd = "john262", "J0hndo3"
requests_mock.get(API_URL, json={"api_version": api_version})
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})

def text_callback(request, context):
assert request.headers["Authorization"] == requests.auth._basic_auth_str(username=user, password=pwd)
Expand Down Expand Up @@ -413,9 +417,19 @@ def test_create_connection_lazy_refresh_token_store(requests_mock):
)


def test_authenticate_basic_no_support(requests_mock, api_version):
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": []})

conn = Connection(API_URL)
assert isinstance(conn.auth, NullAuth)
with pytest.raises(OpenEoClientException, match="does not support basic auth"):
conn.authenticate_basic(username="john", password="j0hn")
assert isinstance(conn.auth, NullAuth)


def test_authenticate_basic(requests_mock, api_version):
user, pwd = "john262", "J0hndo3"
requests_mock.get(API_URL, json={"api_version": api_version})
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})

def text_callback(request, context):
assert request.headers["Authorization"] == requests.auth._basic_auth_str(username=user, password=pwd)
Expand All @@ -435,7 +449,7 @@ def text_callback(request, context):

def test_authenticate_basic_from_config(requests_mock, api_version, auth_config):
user, pwd = "john281", "J0hndo3"
requests_mock.get(API_URL, json={"api_version": api_version})
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})

def text_callback(request, context):
assert request.headers["Authorization"] == requests.auth._basic_auth_str(username=user, password=pwd)
Expand Down Expand Up @@ -1692,7 +1706,7 @@ def test_paginate_callback(requests_mock):
lambda con: PGNode("add", x=3, y=5)
])
def test_as_curl(requests_mock, data_factory):
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
requests_mock.get(API_URL, json={"api_version": "1.0.0", "endpoints": BASIC_ENDPOINTS})
requests_mock.get(API_URL + 'credentials/basic', json={"access_token": "s3cr6t"})
con = Connection(API_URL).authenticate_basic("john", "j0hn")

Expand Down
5 changes: 4 additions & 1 deletion tests/rest/test_imagecollectionclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

@pytest.fixture
def session040(requests_mock):
requests_mock.get(API_URL + "/", json={"api_version": "0.4.0"})
requests_mock.get(API_URL + "/", json={
"api_version": "0.4.0",
"endpoints": [{"path": "/credentials/basic", "methods": ["GET"]}]
})
session = openeo.connect(API_URL)
return session

Expand Down
10 changes: 8 additions & 2 deletions tests/rest/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@

@pytest.fixture
def session040(requests_mock):
requests_mock.get(API_URL + "/", json={"api_version": "0.4.0"})
requests_mock.get(API_URL + "/", json={
"api_version": "0.4.0",
"endpoints": [{"path": "/credentials/basic", "methods": ["GET"]}]
})
session = openeo.connect(API_URL)
return session


@pytest.fixture
def con100(requests_mock):
requests_mock.get(API_URL + "/", json={"api_version": "1.0.0"})
requests_mock.get(API_URL + "/", json={
"api_version": "1.0.0",
"endpoints": [{"path": "/credentials/basic", "methods": ["GET"]}]
})
con = openeo.connect(API_URL)
return con

Expand Down
7 changes: 4 additions & 3 deletions tests/test_usecase1.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

@requests_mock.mock()
class TestUsecase1(TestCase):
_capabilities = {"api_version": "0.4.0", "endpoints": [{"path": "/credentials/basic", "methods": ["GET"]}]}

def setUp(self):
# configuration phase: define username, endpoint, parameters?
Expand All @@ -22,13 +23,13 @@ def setUp(self):
self.output_file = "/tmp/test.gtiff"

def test_user_login(self, m):
m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"})
m.get("http://localhost:8000/api/", json=self._capabilities)
m.get("http://localhost:8000/api/credentials/basic", json={"access_token": "blabla"})
con = openeo.connect(self.endpoint).authenticate_basic(username=self.auth_id, password=self.auth_pwd)
assert isinstance(con.auth, BearerAuth)

def test_viewing_list_jobs(self, m):
m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"})
m.get("http://localhost:8000/api/", json=self._capabilities)
m.get("http://localhost:8000/api/credentials/basic", json={"access_token": "blabla"})
job_info = {"job_id": "748df7caa8c84a7ff6e", "status": "running", "created": "2021-02-22T09:00:00Z"}
m.get("http://localhost:8000/api/jobs", json={"jobs": [job_info]})
Expand Down Expand Up @@ -59,7 +60,7 @@ def test_viewing_processes(self, m):
assert self.process_id in set(p["process_id"] for p in processes)

def test_job_creation(self, m):
m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"})
m.get("http://localhost:8000/api/", json=self._capabilities)
m.get("http://localhost:8000/api/credentials/basic", json={"access_token": "blabla"})
m.post("http://localhost:8000/api/jobs", status_code=201,headers={"OpenEO-Identifier": "748df7caa8c84a7ff6e"})

Expand Down

0 comments on commit 13a51da

Please sign in to comment.