Skip to content

Commit

Permalink
Merge pull request #181 from gkaur793/newEndpoint
Browse files Browse the repository at this point in the history
Adding new endpoint
  • Loading branch information
nstojcevich committed Oct 21, 2022
2 parents a7133d6 + 8161647 commit 8acc599
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -12,6 +12,9 @@

**Accounts** - https://www.duosecurity.com/docs/accountsapi

**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
* 3.7
Expand Down
107 changes: 107 additions & 0 deletions duo_client/admin.py
Expand Up @@ -177,6 +177,7 @@
import warnings
import time
import base64
from datetime import datetime, timedelta

USER_STATUS_ACTIVE = 'active'
USER_STATUS_BYPASS = 'bypass'
Expand All @@ -203,6 +204,14 @@
'api_version'
]

VALID_ACTIVITY_REQUEST_PARAMS = [
'mintime',
'maxtime',
'limit',
'sort',
'next_offset'
]


class Admin(client.Client):
account_id = None
Expand Down Expand Up @@ -519,6 +528,104 @@ def get_authentication_log(self, api_version=1, **kwargs):
row['host'] = self.host
return response

def get_activity_logs(self, **kwargs):
"""
Returns activity log events.
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 - 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
Returns:
{
"items": [
{
"access_device": {
"browser": <str:browser>,
"browser_version": <str:browser version>,
"ip": {
"address": <str:ip address>
},
"location": {
"city": <str:city>,
"country": <str:country>,
"state": <str:state>
},
"os": <str:os name>,
"os_version": <str:os_version>
},
"action": <str:action>,
"activity_id": <str:activity id>,
"actor": {
"details": <str:json for actor details>
"key" : <str:actor's key>,
"name": <str:actor's name>,
"type": <str:actor type>
},
"akey": <str:akey>,
"application": {
"key": <str:application's key>,
"name": <str:application's name>,
"type": <str:application's type>
},
"target": {
"details": <str:target's details if available> ,
"key" : <str:target's key>,
"name": <str:target's name>,
"type": <str:target's type>
},
"ts": <str:timestamp captured for activity>
},
],
"metadata": {
"next_offset": <str: comma seperated ts and offset value>
"total_objects": {
"relation" : <str: relational operator>
"value" : <int: total objects in the time range>
}
}
},
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' 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',
params,
)
for row in response['items']:
row['eventtype'] = 'activity'
row['host'] = self.host
return response

def get_telephony_log(self,
mintime=0):
"""
Expand Down
67 changes: 67 additions & 0 deletions examples/report_activity.py
@@ -0,0 +1,67 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import print_function
import sys
import csv
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=get_next_arg('Admin API integration key ("DI..."): '),
skey=get_next_arg('integration secret key: '),
host=get_next_arg('API hostname ("api-....duosecurity.com"): '),
)
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:
kwargs['limit'] = limit_arg

next_offset_arg = get_next_arg('Next_offset: ')
if next_offset_arg:
kwargs['next_offset'] = next_offset_arg

sort_arg = get_next_arg('Sort (Default - ts:desc) :')
if sort_arg:
kwargs['sort'] = sort_arg

reporter = csv.writer(sys.stdout)

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']
action = log['action']
actor_name = log.get('actor', {}).get('name', None)
target_name = log.get('target', {}).get('name', None)
reporter.writerow([
activity,
ts,
action,
actor_name,
target_name,
])

print("==============================")
1 change: 1 addition & 0 deletions requirements-dev.txt
Expand Up @@ -3,3 +3,4 @@ flake8
pytz>=2022.1
mock
dlint
freezegun
6 changes: 6 additions & 0 deletions tests/admin/base.py
Expand Up @@ -43,6 +43,12 @@ def setUp(self):
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')
self.client_activity.account_id = 'DA012345678901234567'
self.client_activity._connect = \
lambda: util.MockHTTPConnection(data_response_from_get_items=True)


if __name__ == '__main__':
unittest.main()
37 changes: 37 additions & 0 deletions tests/admin/test_activity.py
@@ -0,0 +1,37 @@
from .. import util
from .base import TestAdmin
from datetime import datetime, timedelta
from freezegun import freeze_time
import pytz


class TestEndpoints(TestAdmin):

def test_get_activity_log(self):
""" Test to get activities log.
"""
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_activity.account_id])

@freeze_time('2022-10-01')
def test_get_activity_log_with_no_args(self):
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(
param_dict['mintime'],
[expected_mintime])
self.assertEqual(
param_dict['maxtime'],
[expected_maxtime])
5 changes: 5 additions & 0 deletions tests/util.py
Expand Up @@ -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
Expand All @@ -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"}]

Expand Down

0 comments on commit 8acc599

Please sign in to comment.