Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

**Admin** - https://www.duosecurity.com/docs/adminapi

**Accounts** - https://www.duosecurity.com/docs/accountsapi
- **Subaccount access** - https://duo.com/docs/adminapi#subaccounts

**Activity** - The activity endpoint is in public preview and subject to change

Expand Down
21 changes: 19 additions & 2 deletions duo_client/accounts.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
"""
Duo Security Accounts API reference client implementation.
Duo Security Admin API subaccount management reference client implementation.

<http://www.duosecurity.com/docs/accountsapi>
<https://duo.com/docs/adminapi#subaccounts>

DEPRECATED: this module is deprecated and will be removed in a future release.
Use the equivalent methods on duo_client.Admin instead.
"""
from . import client

class Accounts(client.Client):
"""
DEPRECATED: use duo_client.Admin instead. This client will be removed in a
future release.

The subaccount methods below are duplicated on Admin and are frozen: they
will not be updated going forward. Any new subaccount API method is added
to Admin only, and fixes to these methods may not be mirrored here.
"""
child_map = {}

def get_child_accounts(self):
"""
Return a list of all child accounts of the integration's account.

DEPRECATED: use Admin.get_child_accounts instead. Not maintained.
"""
params = {}
response = self.json_api_call('POST',
Expand All @@ -27,6 +40,8 @@ def get_child_accounts(self):
def create_account(self, name):
"""
Create a new child account of the integration's account.

DEPRECATED: use Admin.create_account instead. Not maintained.
"""
params = {
'name': name,
Expand All @@ -39,6 +54,8 @@ def create_account(self, name):
def delete_account(self, account_id):
"""
Delete a child account of the integration's account.

DEPRECATED: use Admin.delete_account instead. Not maintained.
"""
params = {
'account_id': account_id,
Expand Down
237 changes: 230 additions & 7 deletions duo_client/admin.py

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions examples/Accounts/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Duo Accounts API Examples Overview
# Subaccount Management Examples Overview


## Examples

This folder contains various examples to illustrate the usage of the `Accounts` module within the
`duo_client_python` library. The Duo Accounts API is primarily intended for use by Managed Service
Partners (MSP) to assist in the automation of managing their child (customer) Duo accounts.
This folder contains various examples to illustrate the usage of the subaccount management methods of the `Admin`
module within the `duo_client_python` library. Subaccount management in Admin API is primarily intended for use by
Managed Service Partners (MSP) to assist in the automation of managing their child (customer) Duo accounts.

Use of the Duo Accounts API requires special access to be enabled. Please see the
[online documentation](https://www.duosecurity.com/docs/accountsapi) for more information.
The child account methods (`get_child_accounts`, `create_account`, `delete_account`) live on `Admin`. The `Accounts`
client also provides them, but it is deprecated and will be removed in a future release, and its copy of these
methods is frozen -- new subaccount API methods are added to `Admin` only. Use `Admin` for new code.

Subaccount management in Admin API requires special access to be enabled. Please see the
[online documentation](https://duo.com/docs/adminapi#subaccounts) for more information.

# Using

Expand Down
12 changes: 6 additions & 6 deletions examples/Accounts/create_child_account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Example of Duo Accounts API child account creation
Example of Duo Admin API child account creation
"""

import duo_client
Expand Down Expand Up @@ -27,12 +27,12 @@ def _get_next_arg(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts

:return: dictionary containing Duo Accounts API ikey, skey and hostname strings
:return: dictionary containing Duo Admin API ikey, skey and hostname strings
"""

ikey = _get_next_arg('Duo Accounts API integration key ("DI..."): ')
skey = _get_next_arg('Duo Accounts API integration secret key: ', secure=True)
host = _get_next_arg('Duo Accounts API hostname ("api-....duosecurity.com"): ')
ikey = _get_next_arg('Duo Admin API integration key ("DI..."): ')
skey = _get_next_arg('Duo Admin API integration secret key: ', secure=True)
host = _get_next_arg('Duo Admin API hostname ("api-....duosecurity.com"): ')
account_name = _get_next_arg('Name for new child account: ')

return {"IKEY": ikey, "SKEY": skey, "APIHOST": host, "ACCOUNT_NAME": account_name}
Expand All @@ -43,7 +43,7 @@ def main():

inputs = prompt_for_credentials()

account_client = duo_client.Accounts(
account_client = duo_client.Admin(
ikey=inputs['IKEY'],
skey=inputs['SKEY'],
host=inputs['APIHOST']
Expand Down
6 changes: 3 additions & 3 deletions examples/Accounts/create_integration_in_child_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ def _get_next_arg(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts

:return: dictionary containing Duo Accounts API ikey, skey and hostname strings
:return: dictionary containing Duo Admin API ikey, skey and hostname strings
"""
answers = {'ikey': _get_next_arg('Duo Accounts API integration key ("DI..."): '),
'skey': _get_next_arg('Duo Accounts API integration secret key: ', secure=True),
answers = {'ikey': _get_next_arg('Duo Admin API integration key ("DI..."): '),
'skey': _get_next_arg('Duo Admin API integration secret key: ', secure=True),
'host': _get_next_arg('Duo API hostname of child account ("api-....duosecurity.com"): '),
'account_id': _get_next_arg('Child account ID: '),
'app_name': _get_next_arg('New application name: '),
Expand Down
12 changes: 6 additions & 6 deletions examples/Accounts/delete_child_account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Example of Duo Accounts API child account deletiom
Example of Duo Admin API child account deletion
"""

import duo_client
Expand All @@ -26,12 +26,12 @@ def _get_next_arg(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts

:return: dictionary containing Duo Accounts API ikey, skey and hostname strings
:return: dictionary containing Duo Admin API ikey, skey and hostname strings
"""

ikey = _get_next_arg('Duo Accounts API integration key ("DI..."): ')
skey = _get_next_arg('Duo Accounts API integration secret key: ', secure=True)
host = _get_next_arg('Duo Accounts API hostname ("api-....duosecurity.com"): ')
ikey = _get_next_arg('Duo Admin API integration key ("DI..."): ')
skey = _get_next_arg('Duo Admin API integration secret key: ', secure=True)
host = _get_next_arg('Duo Admin API hostname ("api-....duosecurity.com"): ')
account_id = _get_next_arg('ID of child account to delete: ')

return {"IKEY": ikey, "SKEY": skey, "APIHOST": host, "ACCOUNT_ID": account_id}
Expand All @@ -42,7 +42,7 @@ def main():

inputs = prompt_for_credentials()

account_client = duo_client.Accounts(
account_client = duo_client.Admin(
ikey=inputs['IKEY'],
skey=inputs['SKEY'],
host=inputs['APIHOST']
Expand Down
8 changes: 4 additions & 4 deletions examples/Accounts/get_account_edition.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Example of Duo Accounts API get child account edition
Example of Duo Admin API get child account edition
"""

import duo_client
Expand All @@ -23,9 +23,9 @@ def _get_user_input(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts"""

ikey = _get_user_input('Duo Accounts API integration key ("DI..."): ')
skey = _get_user_input('Duo Accounts API integration secret key: ', secure=True)
host = _get_user_input('Duo Accounts API hostname ("api-....duosecurity.com"): ')
ikey = _get_user_input('Duo Admin API integration key ("DI..."): ')
skey = _get_user_input('Duo Admin API integration secret key: ', secure=True)
host = _get_user_input('Duo Admin API hostname ("api-....duosecurity.com"): ')
account_id = _get_user_input('Child account ID: ')

return {
Expand Down
8 changes: 4 additions & 4 deletions examples/Accounts/get_billing_and_telephony_credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ def get_next_input(prompt):

def main():
"""Program entry point"""
ikey=get_next_input('Accounts API integration key ("DI..."): ')
skey=get_next_input('Accounts API integration secret key: ')
host=get_next_input('Accounts API hostname ("api-....duosecurity.com"): ')
ikey=get_next_input('Admin API integration key ("DI..."): ')
skey=get_next_input('Admin API integration secret key: ')
host=get_next_input('Admin API hostname ("api-....duosecurity.com"): ')

# Configuration and information about objects to create.
accounts_api = duo_client.Accounts(
accounts_api = duo_client.Admin(
ikey=ikey,
skey=skey,
host=host,
Expand Down
10 changes: 5 additions & 5 deletions examples/Accounts/retrieve_account_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def _get_next_arg(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts

:return: dictionary containing Duo Accounts API ikey, skey and hostname strings
:return: dictionary containing Duo Admin API ikey, skey and hostname strings
"""

ikey = _get_next_arg('Duo Accounts API integration key ("DI..."): ')
skey = _get_next_arg('Duo Accounts API integration secret key: ', secure=True)
host = _get_next_arg('Duo Accounts API hostname ("api-....duosecurity.com"): ')
ikey = _get_next_arg('Duo Admin API integration key ("DI..."): ')
skey = _get_next_arg('Duo Admin API integration secret key: ', secure=True)
host = _get_next_arg('Duo Admin API hostname ("api-....duosecurity.com"): ')

return {"IKEY": ikey, "SKEY": skey, "APIHOST": host}

Expand All @@ -41,7 +41,7 @@ def main():

inputs = prompt_for_credentials()

account_client = duo_client.Accounts(
account_client = duo_client.Admin(
ikey=inputs['IKEY'],
skey=inputs['SKEY'],
host=inputs['APIHOST']
Expand Down
8 changes: 4 additions & 4 deletions examples/Accounts/retrieve_integrations_from_child_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@


parser = argparse.ArgumentParser()
duo_arg_group = parser.add_argument_group('Duo Accounts API Credentials')
duo_arg_group = parser.add_argument_group('Duo Admin API Credentials')
duo_arg_group.add_argument('--ikey',
help='Duo Accounts API IKEY',
help='Duo Admin API IKEY',
required=True
)
duo_arg_group.add_argument('--skey',
help='Duo Accounts API Secret Key',
help='Duo Admin API Secret Key',
required=True,
)
duo_arg_group.add_argument('--host',
Expand All @@ -26,7 +26,7 @@
)
args = parser.parse_args()

# It is important to note that we are using the IKEY/SKEY combination for an Accounts API integration in the
# It is important to note that we are using the IKEY/SKEY combination for an Admin API integration in the
# parent account along with the api-hostname of a child account to create a new duo_client.Admin instance
account_client = duo_client.Admin(
ikey=args.ikey,
Expand Down
8 changes: 4 additions & 4 deletions examples/Accounts/set_account_edition.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Example of Duo Accounts API set child account edition
Example of Duo Admin API set child account edition
"""

import duo_client
Expand All @@ -18,9 +18,9 @@ def _get_user_input(prompt, secure=False):
def prompt_for_credentials() -> dict:
"""Collect required API credentials from command line prompts"""

ikey = _get_user_input('Duo Accounts API integration key ("DI..."): ')
skey = _get_user_input('Duo Accounts API integration secret key: ', secure=True)
host = _get_user_input('Duo Accounts API hostname ("api-....duosecurity.com"): ')
ikey = _get_user_input('Duo Admin API integration key ("DI..."): ')
skey = _get_user_input('Duo Admin API integration secret key: ', secure=True)
host = _get_user_input('Duo Admin API hostname ("api-....duosecurity.com"): ')
account_id = _get_user_input('Child account ID: ')
account_apihost = _get_user_input('Child account api_hostname: ')
account_edition = _get_user_input('Child account edition: ')
Expand Down
11 changes: 5 additions & 6 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ The Duo Admin API provides access to endpoints that are primarily focused on Duo
- Integration management
- Policy management
- Log extractions
- Subaccount management

Subaccount management is primarily intended for use by Duo Managed Service Provider (MSP) partners.
See the `Accounts` folder for examples.

-------
### Auth API
The Duo Auth API provides access to user enrollment and authentication services and is primarily intended for use by
application developers that want to integration Duo MFA functionality into their applications.

-------
### Accounts API
The Duo Accounts API provides access to Duo account management functionality and is primarily intended for use by
Duo Managed Service Provider (MSP) partners.
application developers that want to integration Duo MFA functionality into their applications.
61 changes: 61 additions & 0 deletions tests/admin/test_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json

import duo_client.admin
from .. import util
from .base import TestAdmin


class TestAccounts(TestAdmin):
def test_get_child_accounts(self):
""" Test to get child accounts.
"""
response = self.client_list.get_child_accounts()
response = response[0]
self.assertEqual(response['method'], 'POST')
self.assertEqual(response['uri'], '/accounts/v1/account/list')
self.assertEqual(
json.loads(response['body']),
{
'account_id': self.client.account_id,
})

def test_create_account(self):
""" Test to create a child account.
"""
response = self.client.create_account('Test Account')
self.assertEqual(response['method'], 'POST')
self.assertEqual(response['uri'], '/accounts/v1/account/create')
self.assertEqual(
json.loads(response['body']),
{
'name': 'Test Account',
'account_id': self.client.account_id,
})

def test_delete_account(self):
""" Test to delete a child account.
"""
client = duo_client.admin.Admin('test_ikey', 'test_akey', 'example.com')
client._connect = lambda: util.MockHTTPConnection()

response = client.delete_account('DA099999999999999999')
self.assertEqual(response['method'], 'POST')
self.assertEqual(response['uri'], '/accounts/v1/account/delete')
self.assertEqual(
json.loads(response['body']),
{
'account_id': 'DA099999999999999999',
})

def test_delete_account_when_client_is_account_scoped(self):
""" Test that a client-level account_id overrides the delete target.

Admin.api_call sets params['account_id'] from self.account_id, so an
account-scoped client cannot delete a different account.
"""
response = self.client.delete_account('DA099999999999999999')
self.assertEqual(
json.loads(response['body']),
{
'account_id': self.client.account_id,
})
Loading