Skip to content

Commit

Permalink
[PT-4349] Drop project authentication (#591)
Browse files Browse the repository at this point in the history
* Drop project authentication in the main code and viztools.py

* Simplify settings detection

* Drop project token retriever

* Clean up and changelog entries
  • Loading branch information
javidq authored Apr 16, 2024
1 parent 060e7db commit 3ce74a3
Show file tree
Hide file tree
Showing 17 changed files with 41 additions and 216 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ You can check your current version with the following command:
```

For more information, see [UP42 Python package description](https://pypi.org/project/up42-py/).
## 1.0.0a6

**Apr 16, 2024**
- Dropped project id and api key based authentication in `main.py`, `auth.py`, `http/oauth.py` and `http/client.py`
- Adapted tests and fixtures
- Dropped viztools.py

## 1.0.0a5

**Apr 16, 2024**
Expand Down
9 changes: 2 additions & 7 deletions docs/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ def indent(input_string: Optional[str]) -> Optional[str]:
def get_methods(
c: Union[Callable, types.ModuleType],
exclude: Optional[List[str]] = None,
exclude_viztools=False,
) -> List[str]:
"""
Gets all class methods and properties, excluding specifically excluded ones.
Args:
c: The class object.
exclude: Exclude specific methods.
exclude_viztools: Exclude inherited functions from VizTools.
"""
property_methods = [name for name, value in vars(c).items() if isinstance(value, property)]
function_methods = [
Expand All @@ -49,11 +47,8 @@ def get_methods(
and not name[0].isupper()
]

# TODO: Could also return separatly for separated formatting.
function_methods = function_methods + property_methods

if exclude_viztools:
function_methods = [f for f in function_methods if f not in dir(up42.viztools.VizTools)]
if exclude:
function_methods = [f for f in function_methods if f not in exclude]

Expand Down Expand Up @@ -85,8 +80,8 @@ def format_funcs(function_methods: List[str]) -> str:
env.variables.docstring_webhooks = indent(up42.webhooks.Webhooks.__doc__)

# Class functions for reference and structure chapter
env.variables.funcs_up42 = get_methods(up42, exclude_viztools=True)
env.variables.funcs_catalog = get_methods(up42.catalog.Catalog, exclude=["plot_results", "map_results"])
env.variables.funcs_up42 = get_methods(up42)
env.variables.funcs_catalog = get_methods(up42.catalog.Catalog)
env.variables.funcs_tasking = get_methods(up42.tasking.Tasking)
env.variables.funcs_order = get_methods(up42.order.Order)
env.variables.funcs_storage = get_methods(up42.storage.Storage)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "up42-py"
version = "1.0.0a5"
version = "1.0.0a6"
description = "Python SDK for UP42, the geospatial marketplace and developer platform."
authors = ["UP42 GmbH <[email protected]>"]
license = "https://github.com/up42/up42-py/blob/master/LICENSE"
Expand Down
30 changes: 3 additions & 27 deletions tests/fixtures/fixtures_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
import requests_mock as req_mock

from up42 import auth as up42_auth
from up42 import main

from . import fixtures_globals as constants


@pytest.fixture(name="auth_project_mock")
def _auth_project_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth:
@pytest.fixture
def auth_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth:
json_get_token = {
"data": {"accessToken": constants.TOKEN},
"access_token": constants.TOKEN,
Expand All @@ -19,8 +18,6 @@ def _auth_project_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth:
url="https://api.up42.com/users/me",
json={"data": {"id": constants.WORKSPACE_ID}},
)
auth = up42_auth.Auth(project_id=constants.PROJECT_ID, project_api_key=constants.PROJECT_APIKEY)

# get_blocks
url_get_blocks = f"{constants.API_HOST}/blocks"
requests_mock.get(
Expand All @@ -33,25 +30,4 @@ def _auth_project_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth:
url=url_get_credits_balance,
json=constants.JSON_BALANCE,
)

return auth


@pytest.fixture(name="auth_account_mock")
def _auth_account_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth:
json_get_token = {
"data": {"accessToken": constants.TOKEN},
"access_token": constants.TOKEN,
"token_type": "bearer",
}
requests_mock.post("https://api.up42.com/oauth/token", json=json_get_token)
requests_mock.get(url="https://api.up42.com/users/me", json={"data": {"id": constants.WORKSPACE_ID}})
return up42_auth.Auth(username="[email protected]", password="password")


@pytest.fixture(name="auth_mock", params=["project", "account"])
def _auth_mock(request, auth_project_mock, auth_account_mock) -> up42_auth.Auth:
mocks = {"project": auth_project_mock, "account": auth_account_mock}
auth_mock = mocks[request.param]
main._auth = auth_mock # pylint: disable=protected-access
return auth_mock
return up42_auth.Auth(username=constants.USER_EMAIL, password=constants.PASSWORD)
5 changes: 2 additions & 3 deletions tests/fixtures/fixtures_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
)
STAC_ASSET_HREF = "https://api.up42.com/v2/assets/v3b3e203-346d-4f67-b79b-895c36983fb8"
STAC_ASSET_ID = "v3b3e203-346d-4f67-b79b-895c36983fb8"
PROJECT_ID = "f19e833d-e698-4d9e-a037-2e6dbd8791ef"
PROJECT_APIKEY = "project_apikey_123"

# tasking constants
QUOTATION_ID = "805b1f27-1025-43d2-90d0-0bd3416238fb"
Expand All @@ -40,7 +38,8 @@

WORKSPACE_ID = "workspace_id_123"
USER_ID = "1094497b-11d8-4fb8-9d6a-5e24a88aa825"

USER_EMAIL = "[email protected]"
PASSWORD = "<PASSWORD>"

DATA_PRODUCT_ID = "47dadb27-9532-4552-93a5-48f70a83eaef"

Expand Down
10 changes: 4 additions & 6 deletions tests/http/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from up42.http import client, config

SETTINGS = {"some": "settings"}
PROJECT_CREDENTIALS = {"project_id": "some-id", "project_api_key": "some-key"}
ACCOUNT_CREDENTIALS = {"username": "some-user", "password": "some-pass"}
EMPTY_PROJECT_CREDENTIALS = {"project_id": None, "project_api_key": None}
EMPTY_ACCOUNT_CREDENTIALS = {"username": None, "password": None}
TOKEN_URL = "some-token-url"

Expand All @@ -19,8 +17,8 @@ class TestCreate:
@pytest.mark.parametrize(
"sources, detected_settings",
[
[[EMPTY_PROJECT_CREDENTIALS, ACCOUNT_CREDENTIALS], [None, SETTINGS]],
[[PROJECT_CREDENTIALS, EMPTY_ACCOUNT_CREDENTIALS], [SETTINGS, None]],
[[EMPTY_ACCOUNT_CREDENTIALS, ACCOUNT_CREDENTIALS], [None, SETTINGS]],
[[ACCOUNT_CREDENTIALS, EMPTY_ACCOUNT_CREDENTIALS], [SETTINGS, None]],
],
)
def test_should_create_if_only_one_source_is_given(
Expand Down Expand Up @@ -57,12 +55,12 @@ def test_should_create_if_only_one_source_is_given(
"sources, settings, error",
[
[
[PROJECT_CREDENTIALS, ACCOUNT_CREDENTIALS],
[ACCOUNT_CREDENTIALS, ACCOUNT_CREDENTIALS],
SETTINGS,
client.MultipleCredentialsSources,
],
[
[EMPTY_PROJECT_CREDENTIALS, EMPTY_ACCOUNT_CREDENTIALS],
[EMPTY_ACCOUNT_CREDENTIALS, EMPTY_ACCOUNT_CREDENTIALS],
None,
client.MissingCredentials,
],
Expand Down
55 changes: 2 additions & 53 deletions tests/http/test_oauth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import base64
import dataclasses
import random
import time
from typing import Tuple

import mock
import pytest
Expand All @@ -14,57 +12,15 @@
HTTP_TIMEOUT = 10
TOKEN_VALUE = "some-token"
TOKEN_URL = "https://localhost/oauth/token"
project_credentials = config.ProjectCredentialsSettings(
project_id="some-id",
project_api_key="some-key",
)
account_credentials = config.AccountCredentialsSettings(username="some-user", password="some-pass")


def basic_auth(username, password):
token = base64.b64encode(f"{username}:{password}".encode("utf-8"))
return f'Basic {token.decode("ascii")}'


basic_client_auth = basic_auth(project_credentials.project_id, project_credentials.project_api_key)
basic_auth_headers = {"Authorization": basic_client_auth}


def match_project_authentication_request_body(request):
return request.text == "grant_type=client_credentials"


def match_account_authentication_request_body(request):
return request.text == (
"grant_type=password&" f"username={account_credentials.username}&" f"password={account_credentials.password}"
)


class TestProjectTokenRetriever:
def test_should_retrieve(self, requests_mock: req_mock.Mocker):
retrieve = oauth.ProjectTokenRetriever(project_credentials)
requests_mock.post(
TOKEN_URL,
json={"access_token": TOKEN_VALUE},
request_headers=basic_auth_headers,
additional_matcher=match_project_authentication_request_body,
)
assert retrieve(requests.Session(), TOKEN_URL, HTTP_TIMEOUT) == TOKEN_VALUE
assert requests_mock.called_once

def test_fails_to_retrieve_for_bad_response(self, requests_mock: req_mock.Mocker):
retrieve = oauth.ProjectTokenRetriever(project_credentials)
requests_mock.post(
TOKEN_URL,
status_code=random.randint(400, 599),
request_headers=basic_auth_headers,
additional_matcher=match_project_authentication_request_body,
)
with pytest.raises(oauth.WrongCredentials):
retrieve(requests.Session(), TOKEN_URL, HTTP_TIMEOUT)
assert requests_mock.called_once


class TestAccountTokenRetriever:
def test_should_retrieve(self, requests_mock: req_mock.Mocker):
retrieve = oauth.AccountTokenRetriever(account_credentials)
Expand Down Expand Up @@ -127,15 +83,11 @@ def test_should_fetch_token_when_expired(self):


class TestDetectSettings:
def test_should_detect_project_credentials(self):
assert oauth.detect_settings(dataclasses.asdict(project_credentials)) == project_credentials

def test_should_detect_account_credentials(self):
assert oauth.detect_settings(dataclasses.asdict(account_credentials)) == account_credentials

@pytest.mark.parametrize("keys", [("project_id", "project_api_key"), ("username", "password")])
def test_should_accept_empty_credentials(self, keys: Tuple[str, str]):
credentials = dict(zip(keys, [None] * 2))
def test_should_accept_empty_credentials(self):
credentials = {"username": None, "password": None}
assert not oauth.detect_settings(credentials)

def test_should_accept_missing_credentials(self):
Expand All @@ -153,9 +105,6 @@ def test_fails_if_credentials_are_invalid(self):


class TestDetectRetriever:
def test_should_detect_project_retriever(self):
assert isinstance(oauth.detect_retriever(project_credentials), oauth.ProjectTokenRetriever)

def test_should_detect_account_retriever(self):
assert isinstance(oauth.detect_retriever(account_credentials), oauth.AccountTokenRetriever)

Expand Down
27 changes: 7 additions & 20 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

from .fixtures import fixtures_globals as constants

USER_NAME = "some-username"
PASSWORD = "some-password"
CONFIG_FILE = "some-config-file"
TOKEN_ENDPOINT = constants.API_HOST + "/oauth/token"
WORKSPACE_ENDPOINT = constants.API_HOST + "/users/me"
Expand Down Expand Up @@ -50,19 +48,13 @@ def test_should_collect_credentials(self):
read_config = mock.MagicMock(return_value=config_credentials)
expected_sources = [
config_credentials,
{
"project_id": constants.PROJECT_ID,
"project_api_key": constants.PROJECT_APIKEY,
},
{"username": USER_NAME, "password": PASSWORD},
{"username": constants.USER_EMAIL, "password": constants.PASSWORD},
]
assert (
up42_auth.collect_credentials(
CONFIG_FILE,
constants.PROJECT_ID,
constants.PROJECT_APIKEY,
USER_NAME,
PASSWORD,
constants.USER_EMAIL,
constants.PASSWORD,
read_config,
)
== expected_sources
Expand All @@ -86,20 +78,16 @@ def create_auth(requests_mock: req_mock.Mocker):
)
auth = up42_auth.Auth(
cfg_file=CONFIG_FILE,
project_id=constants.PROJECT_ID,
project_api_key=constants.PROJECT_APIKEY,
username=USER_NAME,
password=PASSWORD,
username=constants.USER_EMAIL,
password=constants.PASSWORD,
get_credential_sources=get_sources,
create_client=create_client,
)

get_sources.assert_called_once_with(
CONFIG_FILE,
constants.PROJECT_ID,
constants.PROJECT_APIKEY,
USER_NAME,
PASSWORD,
constants.USER_EMAIL,
constants.PASSWORD,
)
create_client.assert_called_once_with(credential_sources, TOKEN_ENDPOINT)
return auth
Expand All @@ -108,7 +96,6 @@ def create_auth(requests_mock: req_mock.Mocker):
class TestAuth:
def test_should_authenticate_when_created(self, requests_mock: req_mock.Mocker):
auth = create_auth(requests_mock)
assert auth.project_id == constants.PROJECT_ID
assert auth.workspace_id == constants.WORKSPACE_ID
assert auth.token == constants.TOKEN
assert auth.session == session
Expand Down
10 changes: 2 additions & 8 deletions tests/test_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ def test_global_auth_initialize_objects(
order_mock,
asset_mock,
):
up42.authenticate(
project_id=constants.PROJECT_ID,
project_api_key=constants.PROJECT_APIKEY,
)
up42.authenticate(username=constants.USER_EMAIL, password=constants.PASSWORD)
catalog_obj = up42.initialize_catalog()
assert isinstance(catalog_obj, catalog.Catalog)
storage_obj = up42.initialize_storage()
Expand All @@ -48,9 +45,6 @@ def setup_workspace(requests_mock):


def test_should_initialize_tasking():
up42.authenticate(
project_id=constants.PROJECT_ID,
project_api_key=constants.PROJECT_APIKEY,
)
up42.authenticate(username=constants.USER_EMAIL, password=constants.PASSWORD)
result = up42.initialize_tasking()
assert isinstance(result, tasking.Tasking)
3 changes: 0 additions & 3 deletions up42/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
```python
catalog = up42.initialize_catalog()
```
```python
project = up42.initialize_project(project_id="your-project-ID")
```
"""

# pylint: disable=only-importing-modules-is-allowed
Expand Down
Loading

0 comments on commit 3ce74a3

Please sign in to comment.