Skip to content

Commit

Permalink
Merge pull request #23 from melissahardware/oomnitza-connector
Browse files Browse the repository at this point in the history
Oomnitza activities connector
  • Loading branch information
melissahardware authored May 16, 2023
2 parents ea57f05 + 74103ed commit 1b8c359
Show file tree
Hide file tree
Showing 11 changed files with 2,950 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ isn't listed here, support can be added by creating a custom connector!
* GSuite alerts
* GSuite activity logs
* Okta system logs
* Oomnitza activity logs
* 1Password sign-in attempt logs
* 1Password item usage event logs
* 1Password audit logs
* PagerDuty audit records
* SalesForce Cloud event logs
* SalesForce Marketing Cloud audit event logs
Expand Down
2 changes: 1 addition & 1 deletion grove/__about__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Grove metadata."""

__version__ = "1.0.0rc3"
__version__ = "1.0.0rc4"
__title__ = "grove"
__author__ = "HashiCorp Security (TDR)"
__license__ = "Mozilla Public License 2.0"
Expand Down
4 changes: 4 additions & 0 deletions grove/connectors/oomnitza/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

"""Oomnitza connectors for Grove."""
56 changes: 56 additions & 0 deletions grove/connectors/oomnitza/activities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

"""Oomnitza Activities connector for Grove."""
import time
from datetime import datetime, timedelta

from grove.connectors import BaseConnector
from grove.connectors.oomnitza.api import Client
from grove.constants import REVERSE_CHRONOLOGICAL
from grove.exceptions import NotFoundException


class Connector(BaseConnector):
NAME = "oomnitza_activities"
POINTER_PATH = "timestamp"
LOG_ORDER = REVERSE_CHRONOLOGICAL

def collect(self):
"""Collects Oomnitza activities from the Oomnitza API.
This will first check whether there are any pointers cached to indicate previous
collections. If not, the last week of data will be collected.
"""
client = Client(token=self.key, identity=self.identity)

# Set cursor
cursor = 0

# If no pointer is stored then a previous run hasn't been performed, so set the
# pointer to 2 days ago. In the case of the Oomnitza activities API the pointer is
# the value of the "timestamp" field from the latest record retrieved from
# the API - which is in epoch. The Oomnitza API doesnt account for milliseconds.
now = datetime.fromtimestamp(time.time()).strftime("%s")

try:
_ = self.pointer
except NotFoundException:
self.pointer = (
datetime.fromtimestamp(time.time()) - timedelta(days=2)
).strftime("%s")

# Get log data from the upstream API. A "start_date" and "end_date" datetime query
# parameters are required.
while True:
log = client.get_activites(
start_date=self.pointer, end_date=now, cursor=cursor
)

# Save this batch of log entries.
self.save(log.entries)

# Check if we need to continue paging.
cursor = log.cursor # type: ignore
if not cursor:
break
94 changes: 94 additions & 0 deletions grove/connectors/oomnitza/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

"""Oomnitza API client."""

import logging
from typing import Dict, Optional

import requests
from grove.exceptions import RequestFailedException
from grove.types import AuditLogEntries, HTTPResponse

API_BASE_URI = "https://{identity}.oomnitza.com"
API_PAGE_SIZE = 200


class Client:
def __init__(
self,
identity: Optional[str] = None,
token: Optional[str] = None,
):
"""Setup a new client.
:param identity: The name of the Oomnitza organisation.
:param token: The Oomnitza API token.
"""
self.logger = logging.getLogger(__name__)
self.headers = {
"content-type": "application/json",
}
if token:
self.headers["Authorization2"] = token

# We need to push the identity into the URI, so we'll keep track of this.
self._api_base_uri = API_BASE_URI.format(identity=identity)

def _get(
self,
url: str,
params: Optional[Dict[str, Optional[str]]] = None,
) -> HTTPResponse:
"""A GET wrapper to handle retries for the caller.
:param url: URL to perform the HTTP GET against.
:param params: HTTP parameters to add to the request.
:raises RequestFailedException: An HTTP request failed.
:return: HTTP Response object containing the headers and body of a response.
"""
try:
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
except requests.exceptions.RequestException as err:
raise RequestFailedException(err)

return HTTPResponse(headers=response.headers, body=response.json())

def get_activites(
self,
cursor: int = 0,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
) -> AuditLogEntries:
"""Fetches a list of signing attempt logs.
:param cursor: Cursor to use when fetching results.
:param start_date: The earliest date an event represented as an epoch value.
:param end_date: The latest date an event represented as an epoch value.
:return: AuditLogEntries object containing a pagination cursor, and log entries.
"""
url = f"{self._api_base_uri}/api/v3/activities"

result = self._get(
url,
params={
"start_date": start_date,
"end_date": end_date,
"limit": str(API_PAGE_SIZE),
"skip": str(cursor),
},
)
# Keep paging until we run out of results.
data = result.body

if len(data) == API_PAGE_SIZE:
cursor += API_PAGE_SIZE
else:
cursor = 0

# Return the cursor and the results to allow the caller to page as required.
return AuditLogEntries(cursor=cursor, entries=data) # type: ignore
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"workday_activity_logging = grove.connectors.workday.activity_logging:Connector",
"zoom_activities = grove.connectors.zoom.activities:Connector",
"zoom_operationlogs = grove.connectors.zoom.operationlogs:Connector",
"oomnitza_activities = grove.connectors.oomnitza.activities:Connector",
],
"grove.caches": [
"aws_dynamodb = grove.caches.aws_dynamodb:Handler",
Expand Down
6 changes: 6 additions & 0 deletions templates/configuration/oomnitza/activities.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"key": "TOKEN_HERE",
"identity": "ORGANIZATION_ID_HERE",
"name": "oomnitza-activities",
"connector": "oomnitza-activities"
}
67 changes: 67 additions & 0 deletions tests/fixtures/oomnitza/activities/001.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[
{
"id": 605452,
"user_id": "0038389812878318237129898",
"username": "[email protected]",
"timestamp": 1680895957,
"event_source_id": "TESTER",
"event_type_id": "EDIT",
"module_id": "TEST",
"oomnitza_id": "[email protected]",
"sub_module_id": null,
"extra_json": null,
"full_name": "test"
},
{
"id": 605449,
"user_id": "0038389812878318237129898",
"username": "[email protected]",
"timestamp": 1680895948,
"event_source_id": "TESTER",
"event_type_id": "EDIT",
"module_id": "TEST",
"oomnitza_id": "[email protected]",
"sub_module_id": null,
"extra_json": null,
"full_name": "test"
},
{
"id": 605448,
"user_id": "0038389812878318237129898",
"username": "[email protected]",
"timestamp": 1680895947,
"event_source_id": "TESTER",
"event_type_id": "EDIT",
"module_id": "TEST",
"oomnitza_id": "[email protected]",
"sub_module_id": null,
"extra_json": null,
"full_name": "test"
},
{
"id": 605447,
"user_id": "0038389812878318237129898",
"username": "[email protected]",
"timestamp": 1680895947,
"event_source_id": "TESTER",
"event_type_id": "EDIT",
"module_id": "TEST",
"oomnitza_id": "[email protected]",
"sub_module_id": null,
"extra_json": null,
"full_name": "test"
},
{
"id": 605446,
"user_id": "0038389812878318237129898",
"username": "[email protected]",
"timestamp": 1680895937,
"event_source_id": "TESTER",
"event_type_id": "EDIT",
"module_id": "TEST",
"oomnitza_id": "[email protected]",
"sub_module_id": null,
"extra_json": null,
"full_name": "test"
}
]
Loading

0 comments on commit 1b8c359

Please sign in to comment.