From 9402b5c79fda0bad83f8cb38ac09632d041bb969 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 16 Sep 2022 16:32:27 -0500 Subject: [PATCH 01/28] Adding new endpoint --- duo_client/admin.py | 83 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/duo_client/admin.py b/duo_client/admin.py index e8155dd..8ae7d50 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -519,6 +519,89 @@ def get_authentication_log(self, api_version=1, **kwargs): row['host'] = self.host return response + def get_activity_logs(self, mintime, maxtime, limit=100, next_offset=None, sort='ts:asc',): + """ + Returns activity log events. + + mintime (required) - Unix timestamp in ms; fetch records >= mintime + maxtime (required) - Unix timestamp in ms; fetch records <= maxtime + limit - Number of results to limit to + next_offset - Used to grab the next set of results from a previous response + sort - Sort order to be applied + + Returns: + { + "items": [ + { + "access_device": { + "browser": "Chrome", + "browser_version": "1.11.3", + "ip": { + "address": "1.2.3.4" + }, + "location": { + "city": "Ann Arbor", + "country": "United States", + "state": "Michigan" + }, + "os": "Mac OS X", + "os_version": "10.14.1" + }, + "action": "phone_create", + "activity_id": "edf96844-6e03-4528-963f-1fab8d5fabaa", + "actor": { + "details": "{\"created\": \"2020-01-13T18:56:20.351346+00:00\", \"last_login\": \"2020-02-10T18:56:20.351346+00:00\", \"status\": \"Locked Out\"}", + "key" : "DUABCDECUSTOMER000001", + "name": "John Doe", + "type": "user" + }, + "akey": "DABCDECUSTOMER000000", + "application": { + "key": "DIY231J8BR23QK4UKBY8", + "name": "AdminAPI", + "type": "adminapi" + }, + "target": { + "details": "{\"number\": \"+19876543210\", \"extension\": \"\"}", + "key" : "DPFZRS9FB0D46QFTM899", + "name": "987-654-3210", + "type": "phone" + }, + "ts": "2020-02-13T18:56:20.351346+00:00" + }, + ], + "metadata": { + "next_offset": [ + "1532951895000", + "af0ba235-0b33-23c8-bc23-a31aa0231de8" + ], + "total_objects": 1 + } + }, + + Raises RuntimeError on error. + """ + # Sanity check mintime as unix timestamp, then transform to string + mintime = str(int(mintime)) + maxtime = str(int(maxtime)) + params = { + 'mintime': mintime, + 'maxtime': maxtime, + 'limit': limit, + 'next_offset': next_offset, + 'sort': sort, + } + response = self.json_api_call( + 'GET', + '/admin/v2/logs/activity', + params, + ) + print(f'response: {response}') + for row in response['items']: + row['eventtype'] = 'activity' + row['host'] = self.host + return response + def get_telephony_log(self, mintime=0): """ From 5fea22453befa824175564f629ea453e88729f7d Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 19 Sep 2022 16:01:58 -0500 Subject: [PATCH 02/28] updating sort default --- duo_client/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 8ae7d50..6ac1a0a 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -519,7 +519,7 @@ def get_authentication_log(self, api_version=1, **kwargs): row['host'] = self.host return response - def get_activity_logs(self, mintime, maxtime, limit=100, next_offset=None, sort='ts:asc',): + def get_activity_logs(self, mintime, maxtime, limit=100, next_offset=None, sort='ts:desc',): """ Returns activity log events. From 1cadfe4c8bf1a91e0cfca049004c6cb583c3ed66 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 20 Sep 2022 12:22:40 -0500 Subject: [PATCH 03/28] Test for new endpoint --- duo_client/admin.py | 38 +++++++++++++++++++++++++----------- tests/admin/base.py | 7 +++++++ tests/admin/test_activity.py | 16 +++++++++++++++ tests/util.py | 5 +++++ 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 tests/admin/test_activity.py diff --git a/duo_client/admin.py b/duo_client/admin.py index 6ac1a0a..bbfd69c 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -203,6 +203,17 @@ 'api_version' ] +VALID_ACTIVITY_REQUEST_PARAMS = [ + 'mintime', + 'maxtime', + 'limit', + 'sort', + 'next_offset' +] + +DEFAULT_LIMIT = 100 +DEFAULT_SORT = 'ts:desc' + class Admin(client.Client): account_id = None @@ -519,7 +530,7 @@ def get_authentication_log(self, api_version=1, **kwargs): row['host'] = self.host return response - def get_activity_logs(self, mintime, maxtime, limit=100, next_offset=None, sort='ts:desc',): + def get_activity_logs(self, **kwargs): """ Returns activity log events. @@ -581,16 +592,21 @@ def get_activity_logs(self, mintime, maxtime, limit=100, next_offset=None, sort= Raises RuntimeError on error. """ - # Sanity check mintime as unix timestamp, then transform to string - mintime = str(int(mintime)) - maxtime = str(int(maxtime)) - params = { - 'mintime': mintime, - 'maxtime': maxtime, - 'limit': limit, - 'next_offset': next_offset, - 'sort': sort, - } + params = {} + + for k in kwargs: + if kwargs[k] is not None and k in VALID_ACTIVITY_REQUEST_PARAMS: + params[k] = kwargs[k] + + if 'mintime' in params: + params['mintime'] = str(int(params['mintime'])) + if 'maxtime' in params: + params['maxtime'] = str(int(params['maxtime'])) + if 'limit' not in params: + params['limit'] = '{:d}'.format(DEFAULT_LIMIT) + if 'sort' not in params: + params['sort'] = DEFAULT_SORT + response = self.json_api_call( 'GET', '/admin/v2/logs/activity', diff --git a/tests/admin/base.py b/tests/admin/base.py index b640029..cc87bee 100644 --- a/tests/admin/base.py +++ b/tests/admin/base.py @@ -39,6 +39,13 @@ def setUp(self): 'test_akey', 'example.com', ) + + self.client_activity = duo_client.admin.Admin( + 'test_ikey', 'test_akey', 'example.com') + self.client_activity.account_id = 'DA012345678901234567' + self.client_activity._connect = \ + lambda: util.MockHTTPConnection(data_response_from_get_items=True) + self.client_dtm.account_id = 'DA012345678901234567' self.client_dtm._connect = \ lambda: util.MockHTTPConnection(data_response_from_get_dtm_events=True) diff --git a/tests/admin/test_activity.py b/tests/admin/test_activity.py new file mode 100644 index 0000000..b715dbf --- /dev/null +++ b/tests/admin/test_activity.py @@ -0,0 +1,16 @@ +from .. import util +from .base import TestAdmin + + +class TestEndpoints(TestAdmin): + def test_get_activity_log(self): + """ Test to get authentication log on version 1 api. + """ + response = self.client_activity.get_activity_logs(maxtime='1663131599000', mintime='1662958799000') + uri, args = response['uri'].split('?') + + self.assertEqual(response['method'], 'GET') + self.assertEqual(uri, '/admin/v2/logs/activity') + self.assertEqual( + util.params_to_dict(args)['account_id'], + [self.client_list.account_id]) \ No newline at end of file diff --git a/tests/util.py b/tests/util.py index 68d6af5..00f2ba6 100644 --- a/tests/util.py +++ b/tests/util.py @@ -30,12 +30,14 @@ def __init__( data_response_should_be_list=False, data_response_from_get_authlog=False, data_response_from_get_dtm_events=False, + data_response_from_get_items=False, ): # if a response object should be a list rather than # a dict, then set this flag to true self.data_response_should_be_list = data_response_should_be_list self.data_response_from_get_authlog = data_response_from_get_authlog self.data_response_from_get_dtm_events = data_response_from_get_dtm_events + self.data_response_from_get_items = data_response_from_get_items def dummy(self): return self @@ -51,6 +53,9 @@ def read(self): if self.data_response_from_get_authlog: response['authlogs'] = [] + if self.data_response_from_get_items: + response['items'] = [] + if self.data_response_from_get_dtm_events: response['events'] = [{"foo": "bar"}, {"bar": "foo"}] From d9f6beebe21a3a5e8a97a8b8de170b7640a19ed4 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 20 Sep 2022 12:27:03 -0500 Subject: [PATCH 04/28] updating test for activity endpoint --- tests/admin/test_activity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/admin/test_activity.py b/tests/admin/test_activity.py index b715dbf..7ea1ca9 100644 --- a/tests/admin/test_activity.py +++ b/tests/admin/test_activity.py @@ -3,6 +3,7 @@ class TestEndpoints(TestAdmin): + def test_get_activity_log(self): """ Test to get authentication log on version 1 api. """ @@ -13,4 +14,4 @@ def test_get_activity_log(self): self.assertEqual(uri, '/admin/v2/logs/activity') self.assertEqual( util.params_to_dict(args)['account_id'], - [self.client_list.account_id]) \ No newline at end of file + [self.client_activity.account_id]) \ No newline at end of file From 14906b33220f5e452184a5d84ac1f7c46ad4afb0 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 20 Sep 2022 12:45:15 -0500 Subject: [PATCH 05/28] updating test for activity endpoint --- tests/admin/test_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/admin/test_activity.py b/tests/admin/test_activity.py index 7ea1ca9..a0b3be0 100644 --- a/tests/admin/test_activity.py +++ b/tests/admin/test_activity.py @@ -5,7 +5,7 @@ class TestEndpoints(TestAdmin): def test_get_activity_log(self): - """ Test to get authentication log on version 1 api. + """ Test to get activities log. """ response = self.client_activity.get_activity_logs(maxtime='1663131599000', mintime='1662958799000') uri, args = response['uri'].split('?') From 8cd457ac3ffe70294cbde48f1a7129d2597caa50 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 20 Sep 2022 15:02:26 -0500 Subject: [PATCH 06/28] Adding script to test activity endpoint --- examples/report_activity.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 examples/report_activity.py diff --git a/examples/report_activity.py b/examples/report_activity.py new file mode 100755 index 0000000..0976230 --- /dev/null +++ b/examples/report_activity.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +from __future__ import absolute_import +from __future__ import print_function +import csv +import sys + +import duo_client +from six.moves import input + +argv_iter = iter(sys.argv[1:]) +def get_next_arg(prompt): + try: + return next(argv_iter) + except StopIteration: + return input(prompt) + +# Configuration and information about objects to create. +admin_api = duo_client.Admin( + ikey="DIRDJHGRLETC0XHV6W4F", + skey="MR5HDUWTyTymtU2BIfaeGkFpUmeI2RBqaffZ3ZMW", + host="api-dataeng.trustedpath.info", +) + +# Retrieve user info from API: +mintime = '1659380517000' +maxtime = '1663700517000' +logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime) +print(logs) From 1e2c6ab6f52d7347134133d818585aa5962bffe7 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 20 Sep 2022 15:02:39 -0500 Subject: [PATCH 07/28] Adding script to test activity endpoint --- examples/report_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index 0976230..bb8abee 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -17,7 +17,7 @@ def get_next_arg(prompt): # Configuration and information about objects to create. admin_api = duo_client.Admin( ikey="DIRDJHGRLETC0XHV6W4F", - skey="MR5HDUWTyTymtU2BIfaeGkFpUmeI2RBqaffZ3ZMW", + skey="", host="api-dataeng.trustedpath.info", ) From ad206fc27cc8802267703be8d50eb393ab84b33b Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Wed, 21 Sep 2022 18:18:11 -0500 Subject: [PATCH 08/28] Updating script --- examples/report_activity.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index bb8abee..2c0556e 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -1,7 +1,6 @@ #!/usr/bin/env python from __future__ import absolute_import from __future__ import print_function -import csv import sys import duo_client @@ -16,13 +15,26 @@ def get_next_arg(prompt): # Configuration and information about objects to create. admin_api = duo_client.Admin( - ikey="DIRDJHGRLETC0XHV6W4F", - skey="", - host="api-dataeng.trustedpath.info", + ikey=get_next_arg('Admin API integration key ("DI..."): '), + skey=get_next_arg('integration secret key: '), + host=get_next_arg('API hostname ("api-....duosecurity.com"): '), ) -# Retrieve user info from API: -mintime = '1659380517000' -maxtime = '1663700517000' -logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime) -print(logs) +next_offset = None +limit = '100' +sort = 'ts:desc' +mintime = get_next_arg('Mintime: ') +maxtime = get_next_arg('Maxtime: ') +limit_arg = get_next_arg('Limit (Default = 100, Max = 1000): ') +if limit_arg: + limit = limit_arg +next_offset_arg = get_next_arg('Next_offset: ') +if next_offset_arg: + next_offset = next_offset_arg + +sort_arg = get_next_arg('Sort (Default - ts:desc) :') +if sort_arg: + sort = sort_arg + +logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) +print(logs) \ No newline at end of file From 5e5309098c6522fe07f6ad6d2314b3f7a48ff617 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Wed, 21 Sep 2022 22:43:42 -0500 Subject: [PATCH 09/28] Clean up --- duo_client/admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index bbfd69c..773e0bf 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -612,7 +612,6 @@ def get_activity_logs(self, **kwargs): '/admin/v2/logs/activity', params, ) - print(f'response: {response}') for row in response['items']: row['eventtype'] = 'activity' row['host'] = self.host From 3e4008175a48b3f45a135d398821d61e30a34cd7 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 23 Sep 2022 11:50:59 -0500 Subject: [PATCH 10/28] Clean up --- duo_client/admin.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 773e0bf..269e0f0 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -211,9 +211,6 @@ 'next_offset' ] -DEFAULT_LIMIT = 100 -DEFAULT_SORT = 'ts:desc' - class Admin(client.Client): account_id = None @@ -602,10 +599,6 @@ def get_activity_logs(self, **kwargs): params['mintime'] = str(int(params['mintime'])) if 'maxtime' in params: params['maxtime'] = str(int(params['maxtime'])) - if 'limit' not in params: - params['limit'] = '{:d}'.format(DEFAULT_LIMIT) - if 'sort' not in params: - params['sort'] = DEFAULT_SORT response = self.json_api_call( 'GET', From f8b2b805d245e5deb4327e41a02d05e6744e766e Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 23 Sep 2022 13:35:46 -0500 Subject: [PATCH 11/28] Disclaimer for endpoint accessibilty --- duo_client/admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/duo_client/admin.py b/duo_client/admin.py index 269e0f0..adcc4ed 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -531,6 +531,10 @@ def get_activity_logs(self, **kwargs): """ Returns activity log events. + As of now, this endpoint is not in general availability and restricted to few customers for private preview. + To access this endpoint,the customer needs to be enabled by agents manually. + + mintime (required) - Unix timestamp in ms; fetch records >= mintime maxtime (required) - Unix timestamp in ms; fetch records <= maxtime limit - Number of results to limit to From 06c5afd146ebc2644fdfde956b74e7e1ab189ec4 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 23 Sep 2022 13:42:48 -0500 Subject: [PATCH 12/28] Updating comments --- duo_client/admin.py | 46 ++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index adcc4ed..cf47ffa 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -546,40 +546,40 @@ def get_activity_logs(self, **kwargs): "items": [ { "access_device": { - "browser": "Chrome", - "browser_version": "1.11.3", + "browser": , + "browser_version": , "ip": { - "address": "1.2.3.4" + "address": }, "location": { - "city": "Ann Arbor", - "country": "United States", - "state": "Michigan" + "city": , + "country": , + "state": }, - "os": "Mac OS X", - "os_version": "10.14.1" + "os": , + "os_version": }, - "action": "phone_create", - "activity_id": "edf96844-6e03-4528-963f-1fab8d5fabaa", + "action": , + "activity_id": , "actor": { - "details": "{\"created\": \"2020-01-13T18:56:20.351346+00:00\", \"last_login\": \"2020-02-10T18:56:20.351346+00:00\", \"status\": \"Locked Out\"}", - "key" : "DUABCDECUSTOMER000001", - "name": "John Doe", - "type": "user" + "details": + "key" : , + "name": , + "type": }, - "akey": "DABCDECUSTOMER000000", + "akey": , "application": { - "key": "DIY231J8BR23QK4UKBY8", - "name": "AdminAPI", - "type": "adminapi" + "key": , + "name": , + "type": }, "target": { - "details": "{\"number\": \"+19876543210\", \"extension\": \"\"}", - "key" : "DPFZRS9FB0D46QFTM899", - "name": "987-654-3210", - "type": "phone" + "details": , + "key" : , + "name": , + "type": }, - "ts": "2020-02-13T18:56:20.351346+00:00" + "ts": }, ], "metadata": { From 783528c7fc5aa6a340f9e1d62cdbca8b166767bc Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 23 Sep 2022 13:45:09 -0500 Subject: [PATCH 13/28] Readme updates --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e2d33f4..ef47db9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ **Accounts** - https://www.duosecurity.com/docs/accountsapi +**Activity** - TBD (Available for private preview only) + ## Tested Against Python Versions * 3.6 * 3.7 From b69cc82ed220c9bc96685f0c1851dfc0ba4c3561 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 23 Sep 2022 14:02:00 -0500 Subject: [PATCH 14/28] Sanity check for limit --- duo_client/admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/duo_client/admin.py b/duo_client/admin.py index cf47ffa..e0ea42a 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -603,6 +603,8 @@ def get_activity_logs(self, **kwargs): params['mintime'] = str(int(params['mintime'])) if 'maxtime' in params: params['maxtime'] = str(int(params['maxtime'])) + if 'limit' in params: + params['limit'] = str(int(params['limit'])) response = self.json_api_call( 'GET', From cf00abb81434aeb375444cee253426e3c886d433 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 09:00:54 -0500 Subject: [PATCH 15/28] Updating instructions --- README.md | 3 ++- duo_client/admin.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef47db9..2792b45 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ **Accounts** - https://www.duosecurity.com/docs/accountsapi -**Activity** - TBD (Available for private preview only) +**Activity** - TBD (As of now, the activity endpoint is not in general availability and is restricted to a few customers for private preview. +If you have any questions or need more information, feel free to reach out to support for guidance.) ## Tested Against Python Versions * 3.6 diff --git a/duo_client/admin.py b/duo_client/admin.py index e0ea42a..c5738c8 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -531,8 +531,8 @@ def get_activity_logs(self, **kwargs): """ Returns activity log events. - As of now, this endpoint is not in general availability and restricted to few customers for private preview. - To access this endpoint,the customer needs to be enabled by agents manually. + As of now, the activity endpoint is not in general availability and is restricted to a few customers for private preview. + If you have any questions or need more information, feel free to reach out to support for guidance. mintime (required) - Unix timestamp in ms; fetch records >= mintime From a0f60b8ed8c8cc5b2ac7c23f2e85afd9dade0d97 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 09:25:59 -0500 Subject: [PATCH 16/28] Added csv for example --- examples/report_activity.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index 2c0556e..ff21587 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from __future__ import print_function import sys - +import csv import duo_client from six.moves import input @@ -36,5 +36,12 @@ def get_next_arg(prompt): if sort_arg: sort = sort_arg +reporter = csv.writer(sys.stdout) logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) -print(logs) \ No newline at end of file + +reporter.writerow(('activity_id', 'ts')) +for log in logs['items']: + reporter.writerow([ + log['activity_id'], + log['ts'], + ]) \ No newline at end of file From 1fb14d6b5dd60a9b45b3ec96534c8ff982bf16e9 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 10:54:31 -0500 Subject: [PATCH 17/28] Updated csv for example --- examples/report_activity.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index ff21587..74760da 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -38,10 +38,25 @@ def get_next_arg(prompt): reporter = csv.writer(sys.stdout) logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) - -reporter.writerow(('activity_id', 'ts')) +print(logs) +reporter.writerow(('activity_id', 'ts', 'action', 'actor_name', 'target_name')) for log in logs['items']: - reporter.writerow([ - log['activity_id'], - log['ts'], - ]) \ No newline at end of file + activity = log['activity_id'], + ts = log['ts'] + action = log['action'] + if log['actor'] is not None and log['actor']['name'] is not None: + actor_name = str(log['actor']['name']) + else: + actor_name = None + if log['target'] is not None and log['target']['name'] is not None: + for target in log['target']: + target_name =str(log['target']['name']) + else: + target_name = None + reporter.writerow([ + activity, + ts, + action, + actor_name, + target_name, + ]) \ No newline at end of file From 09d8928e2a806fff40f8c179f5b70b51bf3aa97d Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 10:59:20 -0500 Subject: [PATCH 18/28] Updated csv --- examples/report_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index 74760da..8c4878a 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -38,7 +38,7 @@ def get_next_arg(prompt): reporter = csv.writer(sys.stdout) logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) -print(logs) + reporter.writerow(('activity_id', 'ts', 'action', 'actor_name', 'target_name')) for log in logs['items']: activity = log['activity_id'], From 4cbc83ade0c6bf0fc227f092ef19577e478040b5 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 11:05:07 -0500 Subject: [PATCH 19/28] Updated csv --- examples/report_activity.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/examples/report_activity.py b/examples/report_activity.py index 8c4878a..1868d6d 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -44,15 +44,8 @@ def get_next_arg(prompt): activity = log['activity_id'], ts = log['ts'] action = log['action'] - if log['actor'] is not None and log['actor']['name'] is not None: - actor_name = str(log['actor']['name']) - else: - actor_name = None - if log['target'] is not None and log['target']['name'] is not None: - for target in log['target']: - target_name =str(log['target']['name']) - else: - target_name = None + actor_name = log.get('actor', {}).get('name', None) + target_name = log.get('target', {}).get('name', None) reporter.writerow([ activity, ts, From bee12c012c5382bc0425232b975bdd3534569675 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 11:32:47 -0500 Subject: [PATCH 20/28] Updated documentation --- duo_client/admin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index c5738c8..883e7a0 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -583,11 +583,11 @@ def get_activity_logs(self, **kwargs): }, ], "metadata": { - "next_offset": [ - "1532951895000", - "af0ba235-0b33-23c8-bc23-a31aa0231de8" - ], - "total_objects": 1 + "next_offset": + "value" : + } } }, From 38bd7ce5fb931e4466b6a47c5636eeb7604f0196 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Tue, 27 Sep 2022 11:37:22 -0500 Subject: [PATCH 21/28] Updated comments --- duo_client/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 883e7a0..a1eab6b 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -583,7 +583,7 @@ def get_activity_logs(self, **kwargs): }, ], "metadata": { - "next_offset": "total_objects": { "relation" : "value" : From 8f794a1081dea3ccc5b1a40adaf7999e456e4a0d Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Fri, 30 Sep 2022 10:48:54 -0500 Subject: [PATCH 22/28] Updated comments --- examples/report_activity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/report_activity.py b/examples/report_activity.py index 1868d6d..4658f4f 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -39,6 +39,7 @@ def get_next_arg(prompt): reporter = csv.writer(sys.stdout) logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) +print("Next offset from response : ", logs.get('metadata').get('next_offset')) reporter.writerow(('activity_id', 'ts', 'action', 'actor_name', 'target_name')) for log in logs['items']: activity = log['activity_id'], From b902b394fff3eb027456b8cc1a979b226a788784 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 13:03:12 -0500 Subject: [PATCH 23/28] Defaulted mintime and maxtime based on requirements --- duo_client/admin.py | 20 ++++++++++++++++---- examples/report_activity.py | 29 ++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index a1eab6b..c48fa65 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -177,6 +177,7 @@ import warnings import time import base64 +from datetime import datetime, timedelta USER_STATUS_ACTIVE = 'active' USER_STATUS_BYPASS = 'bypass' @@ -594,18 +595,29 @@ def get_activity_logs(self, **kwargs): Raises RuntimeError on error. """ params = {} + today = datetime.utcnow() + default_maxtime = int(today.timestamp() * 1000) + default_mintime = int((today - timedelta(days=180)).timestamp() * 1000) for k in kwargs: if kwargs[k] is not None and k in VALID_ACTIVITY_REQUEST_PARAMS: params[k] = kwargs[k] - if 'mintime' in params: - params['mintime'] = str(int(params['mintime'])) - if 'maxtime' in params: - params['maxtime'] = str(int(params['maxtime'])) + + + if 'mintime' not in params: + # If mintime is not provided, the script defaults it to 180 days in past + params['mintime'] = default_mintime + params['mintime'] = str(int(params['mintime'])) + if 'maxtime' not in params: + #if maxtime is not provided, the script defaults it to now + params['maxtime'] = default_maxtime + params['maxtime'] = str(int(params['maxtime'])) if 'limit' in params: params['limit'] = str(int(params['limit'])) + + response = self.json_api_call( 'GET', '/admin/v2/logs/activity', diff --git a/examples/report_activity.py b/examples/report_activity.py index 4658f4f..03598bb 100755 --- a/examples/report_activity.py +++ b/examples/report_activity.py @@ -19,28 +19,37 @@ def get_next_arg(prompt): skey=get_next_arg('integration secret key: '), host=get_next_arg('API hostname ("api-....duosecurity.com"): '), ) - -next_offset = None -limit = '100' -sort = 'ts:desc' +kwargs = {} +#get arguments mintime = get_next_arg('Mintime: ') +if mintime: + kwargs['mintime'] = mintime + maxtime = get_next_arg('Maxtime: ') +if maxtime: + kwargs['maxtime'] = maxtime + limit_arg = get_next_arg('Limit (Default = 100, Max = 1000): ') if limit_arg: - limit = limit_arg + kwargs['limit'] = limit_arg + next_offset_arg = get_next_arg('Next_offset: ') if next_offset_arg: - next_offset = next_offset_arg + kwargs['next_offset'] = next_offset_arg sort_arg = get_next_arg('Sort (Default - ts:desc) :') if sort_arg: - sort = sort_arg + kwargs['sort'] = sort_arg reporter = csv.writer(sys.stdout) -logs = admin_api.get_activity_logs(mintime = mintime, maxtime = maxtime, limit = limit, next_offset = next_offset, sort = sort) +logs = admin_api.get_activity_logs(**kwargs) + +print("==============================") print("Next offset from response : ", logs.get('metadata').get('next_offset')) + reporter.writerow(('activity_id', 'ts', 'action', 'actor_name', 'target_name')) + for log in logs['items']: activity = log['activity_id'], ts = log['ts'] @@ -53,4 +62,6 @@ def get_next_arg(prompt): action, actor_name, target_name, - ]) \ No newline at end of file + ]) + +print("==============================") \ No newline at end of file From 3bdb29b3c4b1a6987d3893e92be4a06197d9d159 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 13:16:38 -0500 Subject: [PATCH 24/28] Updating comments --- duo_client/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index c48fa65..c3dc319 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -536,8 +536,8 @@ def get_activity_logs(self, **kwargs): If you have any questions or need more information, feel free to reach out to support for guidance. - mintime (required) - Unix timestamp in ms; fetch records >= mintime - maxtime (required) - Unix timestamp in ms; fetch records <= maxtime + mintime - Unix timestamp in ms; fetch records >= mintime + maxtime - Unix timestamp in ms; fetch records <= maxtime limit - Number of results to limit to next_offset - Used to grab the next set of results from a previous response sort - Sort order to be applied From e4fab3a6eaf860e23de107682dd98b5e2024cae7 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 13:20:13 -0500 Subject: [PATCH 25/28] Added test --- tests/admin/test_activity.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/admin/test_activity.py b/tests/admin/test_activity.py index a0b3be0..572de74 100644 --- a/tests/admin/test_activity.py +++ b/tests/admin/test_activity.py @@ -14,4 +14,17 @@ def test_get_activity_log(self): self.assertEqual(uri, '/admin/v2/logs/activity') self.assertEqual( util.params_to_dict(args)['account_id'], - [self.client_activity.account_id]) \ No newline at end of file + [self.client_activity.account_id]) + + def test_get_activity_log_with_no_args(self): + """ Test to get activities log. + """ + response = self.client_activity.get_activity_logs() + uri, args = response['uri'].split('?') + + self.assertEqual(response['method'], 'GET') + self.assertEqual(uri, '/admin/v2/logs/activity') + self.assertEqual( + util.params_to_dict(args)['account_id'], + [self.client_activity.account_id]) + From 1f7e9392c8fcce9f0a1bc20b0db424b8c9284d14 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 15:41:54 -0500 Subject: [PATCH 26/28] Fixed existing test --- duo_client/admin.py | 3 +-- tests/admin/base.py | 7 +++---- tests/admin/test_activity.py | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index c3dc319..12dc9ea 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -603,8 +603,6 @@ def get_activity_logs(self, **kwargs): if kwargs[k] is not None and k in VALID_ACTIVITY_REQUEST_PARAMS: params[k] = kwargs[k] - - if 'mintime' not in params: # If mintime is not provided, the script defaults it to 180 days in past params['mintime'] = default_mintime @@ -623,6 +621,7 @@ def get_activity_logs(self, **kwargs): '/admin/v2/logs/activity', params, ) + print(response) for row in response['items']: row['eventtype'] = 'activity' row['host'] = self.host diff --git a/tests/admin/base.py b/tests/admin/base.py index cc87bee..edd53e5 100644 --- a/tests/admin/base.py +++ b/tests/admin/base.py @@ -39,6 +39,9 @@ def setUp(self): 'test_akey', 'example.com', ) + self.client_dtm.account_id = 'DA012345678901234567' + self.client_dtm._connect = \ + lambda: util.MockHTTPConnection(data_response_from_get_dtm_events=True) self.client_activity = duo_client.admin.Admin( 'test_ikey', 'test_akey', 'example.com') @@ -46,10 +49,6 @@ def setUp(self): self.client_activity._connect = \ lambda: util.MockHTTPConnection(data_response_from_get_items=True) - self.client_dtm.account_id = 'DA012345678901234567' - self.client_dtm._connect = \ - lambda: util.MockHTTPConnection(data_response_from_get_dtm_events=True) - if __name__ == '__main__': unittest.main() diff --git a/tests/admin/test_activity.py b/tests/admin/test_activity.py index 572de74..a6bf3e0 100644 --- a/tests/admin/test_activity.py +++ b/tests/admin/test_activity.py @@ -1,5 +1,8 @@ from .. import util from .base import TestAdmin +from datetime import datetime, timedelta +from freezegun import freeze_time +import pytz class TestEndpoints(TestAdmin): @@ -16,15 +19,19 @@ def test_get_activity_log(self): util.params_to_dict(args)['account_id'], [self.client_activity.account_id]) + @freeze_time('2022-10-01') def test_get_activity_log_with_no_args(self): - """ Test to get activities log. - """ + freezed_time = datetime(2022,10,1,0,0,0, tzinfo=pytz.utc) + expected_mintime = str(int((freezed_time-timedelta(days=180)).timestamp()*1000)) + expected_maxtime = str(int(freezed_time.timestamp() * 1000)) response = self.client_activity.get_activity_logs() uri, args = response['uri'].split('?') - + param_dict = util.params_to_dict(args) self.assertEqual(response['method'], 'GET') self.assertEqual(uri, '/admin/v2/logs/activity') self.assertEqual( - util.params_to_dict(args)['account_id'], - [self.client_activity.account_id]) - + param_dict['mintime'], + [expected_mintime]) + self.assertEqual( + param_dict['maxtime'], + [expected_maxtime]) From 17820555cd69b5f220dd5de4324d9d4cd40accf2 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 15:54:00 -0500 Subject: [PATCH 27/28] adding freezegun in requirements --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5f98a90..6955113 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ flake8 pytz>=2022.1 mock dlint +freezegun From 816164797e94efc1a4d433768582dc9233abf9af Mon Sep 17 00:00:00 2001 From: Gurleen Kaur Date: Mon, 3 Oct 2022 16:04:07 -0500 Subject: [PATCH 28/28] remove print statement --- duo_client/admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 12dc9ea..ce38413 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -621,7 +621,6 @@ def get_activity_logs(self, **kwargs): '/admin/v2/logs/activity', params, ) - print(response) for row in response['items']: row['eventtype'] = 'activity' row['host'] = self.host