From 886c194a3f771eeababa4228369a1d8682e4fd82 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:50:02 -0500 Subject: [PATCH 1/2] new: Add support for Parent/Child account switching (#373) * Changes for parent/child account project * Improved test --- linode_api4/groups/account.py | 13 +++++++ linode_api4/objects/account.py | 35 ++++++++++++++++++ test/fixtures/account_child-accounts.json | 36 +++++++++++++++++++ .../account_child-accounts_123456.json | 29 +++++++++++++++ .../account_child-accounts_123456_token.json | 8 +++++ test/integration/models/test_account.py | 10 ++++++ test/unit/objects/account_test.py | 19 ++++++++++ 7 files changed, 150 insertions(+) create mode 100644 test/fixtures/account_child-accounts.json create mode 100644 test/fixtures/account_child-accounts_123456.json create mode 100644 test/fixtures/account_child-accounts_123456_token.json diff --git a/linode_api4/groups/account.py b/linode_api4/groups/account.py index 55eab9436..0f20cf311 100644 --- a/linode_api4/groups/account.py +++ b/linode_api4/groups/account.py @@ -8,6 +8,7 @@ AccountBetaProgram, AccountSettings, BetaProgram, + ChildAccount, Event, Invoice, Login, @@ -18,6 +19,7 @@ ServiceTransfer, User, ) +from linode_api4.objects.profile import PersonalAccessToken class AccountGroup(Group): @@ -496,3 +498,14 @@ def availabilities(self, *filters): :rtype: PaginatedList of AccountAvailability """ return self.client._get_and_filter(AccountAvailability, *filters) + + def child_accounts(self, *filters): + """ + Returns a list of all child accounts under the this parent account. + + API doc: TBD + + :returns: a list of all child accounts. + :rtype: PaginatedList of ChildAccount + """ + return self.client._get_and_filter(ChildAccount, *filters) diff --git a/linode_api4/objects/account.py b/linode_api4/objects/account.py index f50c4da32..69a30db79 100644 --- a/linode_api4/objects/account.py +++ b/linode_api4/objects/account.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from datetime import datetime import requests @@ -16,6 +18,7 @@ ) from linode_api4.objects.longview import LongviewClient, LongviewSubscription from linode_api4.objects.nodebalancer import NodeBalancer +from linode_api4.objects.profile import PersonalAccessToken from linode_api4.objects.support import SupportTicket @@ -53,6 +56,37 @@ class Account(Base): } +class ChildAccount(Account): + """ + A child account under a parent account. + + API Documentation: TBD + """ + + api_endpoint = "/account/child-accounts/{euuid}" + id_attribute = "euuid" + + def create_token(self, **kwargs): + """ + Create a ephemeral token for accessing the child account. + + API Documentation: TBD + """ + resp = self._client.post( + "{}/token".format(self.api_endpoint), + model=self, + data=kwargs, + ) + + if "errors" in resp: + raise UnexpectedResponseError( + "Unexpected response when creating a token for the child account!", + json=resp, + ) + + return PersonalAccessToken(self._client, resp["id"], resp) + + class ServiceTransfer(Base): """ A transfer request for transferring a service between Linode accounts. @@ -476,6 +510,7 @@ class User(Base): properties = { "email": Property(), "username": Property(identifier=True, mutable=True), + "user_type": Property(), "restricted": Property(mutable=True), "ssh_keys": Property(), "tfa_enabled": Property(), diff --git a/test/fixtures/account_child-accounts.json b/test/fixtures/account_child-accounts.json new file mode 100644 index 000000000..e7e9aca43 --- /dev/null +++ b/test/fixtures/account_child-accounts.json @@ -0,0 +1,36 @@ +{ + "data": [ + { + "active_since": "2018-01-01T00:01:01", + "address_1": "123 Main Street", + "address_2": "Suite A", + "balance": 200, + "balance_uninvoiced": 145, + "billing_source": "external", + "capabilities": [ + "Linodes", + "NodeBalancers", + "Block Storage", + "Object Storage" + ], + "city": "Philadelphia", + "company": "Linode LLC", + "country": "US", + "credit_card": { + "expiry": "11/2022", + "last_four": 1111 + }, + "email": "john.smith@linode.com", + "euuid": "E1AF5EEC-526F-487D-B317EBEB34C87D71", + "first_name": "John", + "last_name": "Smith", + "phone": "215-555-1212", + "state": "PA", + "tax_id": "ATU99999999", + "zip": "19102-1234" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} diff --git a/test/fixtures/account_child-accounts_123456.json b/test/fixtures/account_child-accounts_123456.json new file mode 100644 index 000000000..8ce264693 --- /dev/null +++ b/test/fixtures/account_child-accounts_123456.json @@ -0,0 +1,29 @@ +{ + "active_since": "2018-01-01T00:01:01", + "address_1": "123 Main Street", + "address_2": "Suite A", + "balance": 200, + "balance_uninvoiced": 145, + "billing_source": "external", + "capabilities": [ + "Linodes", + "NodeBalancers", + "Block Storage", + "Object Storage" + ], + "city": "Philadelphia", + "company": "Linode LLC", + "country": "US", + "credit_card": { + "expiry": "11/2022", + "last_four": 1111 + }, + "email": "john.smith@linode.com", + "euuid": "E1AF5EEC-526F-487D-B317EBEB34C87D71", + "first_name": "John", + "last_name": "Smith", + "phone": "215-555-1212", + "state": "PA", + "tax_id": "ATU99999999", + "zip": "19102-1234" +} \ No newline at end of file diff --git a/test/fixtures/account_child-accounts_123456_token.json b/test/fixtures/account_child-accounts_123456_token.json new file mode 100644 index 000000000..44afea72b --- /dev/null +++ b/test/fixtures/account_child-accounts_123456_token.json @@ -0,0 +1,8 @@ +{ + "created": "2024-01-01T00:01:01", + "expiry": "2024-01-01T13:46:32", + "id": 123, + "label": "cool_customer_proxy", + "scopes": "*", + "token": "abcdefghijklmnop" +} \ No newline at end of file diff --git a/test/integration/models/test_account.py b/test/integration/models/test_account.py index 3d5fa2d97..651268b82 100644 --- a/test/integration/models/test_account.py +++ b/test/integration/models/test_account.py @@ -11,6 +11,7 @@ OAuthClient, User, ) +from linode_api4.objects.account import ChildAccount @pytest.mark.smoke @@ -101,3 +102,12 @@ def test_get_user(test_linode_client): assert username == user.username assert "email" in user._raw_json assert "email" in user._raw_json + + +def test_list_child_accounts(test_linode_client): + client = test_linode_client + child_accounts = client.account.child_accounts() + if len(child_accounts) > 0: + child_account = ChildAccount(client, child_accounts[0].euuid) + child_account._api_get() + child_account.create_token() diff --git a/test/unit/objects/account_test.py b/test/unit/objects/account_test.py index 0f53240f4..1ec344a7f 100644 --- a/test/unit/objects/account_test.py +++ b/test/unit/objects/account_test.py @@ -24,6 +24,7 @@ Volume, get_obj_grants, ) +from linode_api4.objects.account import ChildAccount class InvoiceTest(ClientBaseCase): @@ -278,3 +279,21 @@ def test_account_availability_api_get(self): self.assertEqual(availability.unavailable, []) self.assertEqual(m.call_url, account_availability_url) + + +class ChildAccountTest(ClientBaseCase): + """ + Test methods of the ChildAccount + """ + + def test_child_account_api_list(self): + result = self.client.account.child_accounts() + self.assertEqual(len(result), 1) + self.assertEqual(result[0].euuid, "E1AF5EEC-526F-487D-B317EBEB34C87D71") + + def test_child_account_create_token(self): + child_account = self.client.load(ChildAccount, 123456) + with self.mock_post("/account/child-accounts/123456/token") as m: + token = child_account.create_token() + self.assertEqual(token.token, "abcdefghijklmnop") + self.assertEqual(m.call_data, {}) From ddffa753b441677f8e54076b258164713b908440 Mon Sep 17 00:00:00 2001 From: Jacob Riddle Date: Mon, 3 Jun 2024 15:30:12 -0400 Subject: [PATCH 2/2] lint --- test/integration/models/account/test_account.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/integration/models/account/test_account.py b/test/integration/models/account/test_account.py index d7ec41224..693d5bb82 100644 --- a/test/integration/models/account/test_account.py +++ b/test/integration/models/account/test_account.py @@ -3,7 +3,14 @@ import pytest -from linode_api4.objects import Account, AccountSettings, ChildAccount, Event, Login, User +from linode_api4.objects import ( + Account, + AccountSettings, + ChildAccount, + Event, + Login, + User, +) @pytest.mark.smoke