DropboxEventsCollector

Collect events from Dropbox's logs.

Analytics & SIEM · Dropbox

Details

IDDropboxEventsCollector
ProviderDropbox
CategoryAnalytics & SIEM
From Version6.8.0
Docker Imagedemisto/py3-tools:1.0.0.10120494
Supported ModulesAgentix XSIAM

README

Collect events from Dropbox’s logs.
This integration was integrated and tested with version 2 of Dropbox API

Configure Dropbox Event Collector on Cortex XSIAM

  1. Navigate to Settings > Configurations > Data Collection > Automation and Feed Integrations.
  2. Search for Dropbox Event Collector.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Server URL The endpoint from which to get the logs. True
    App Key The App key (created in the Dropbox app console). True
    App Secret The App secret (created in the Dropbox app console). True
    First fetch in timestamp format First fetch in timestamp format (<number> <time unit>, e.g., 12 hours, 7 days) False
    The maximum number of events per fetch   False
    Trust any certificate (not secure)   False
    Use system proxy settings   False
  4. Run the !dropbox-auth-start command to test the connection and the authorization process.

Commands

You can execute these commands from the Cortex XSIAM War Room, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

dropbox-auth-start


Run this command to start the authorization process and follow the instructions in the command results. This command generates a link. By clicking the link, you get a code for the dropbox-auth-complete command.

Base Command

dropbox-auth-start

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

dropbox-auth-complete


Run this command to complete the authorization process. Should be used after running the dropbox-auth-start command.

Base Command

dropbox-auth-complete

Input

Argument Name Description Required
code The code that returns from Dropbox. Required

Context Output

There is no context output for this command.

dropbox-auth-test


Run this command to test the connectivity to Dropbox.

Note: Use this command instead of the Test button in the UI.

Base Command

dropbox-auth-test

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

dropbox-auth-reset


Resets the authentication.

Base Command

dropbox-auth-reset

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

dropbox-get-events


Get events.

Base Command

dropbox-get-events

Input

Argument Name Description Required
limit The maximum events to fetch. Default is 500. Optional
should_push_events Set this argument to true to create events, otherwise the command will only display them. Possible values are: true, false. Default is false. Required
from Fetch events from this time (<number> <time unit>, e.g., 12 hours, 7 days). Default is 3 days. Optional

Context Output

There is no context output for this command.

Command example

!dropbox-get-events should_push_events='false' limit=3

Human Readable Output

Dropbox logs

Actor Context Details Event _ Category Event _ Type Involve Non Team _ Member Origin Timestamp
.tag: admin
admin: {“.tag”: “team_member”, “account_id”: “123456”, “display_name”: “John Smith”, “email”: “JohnSmith@example.com”, “team_member_id”: “111111”}
.tag: team_member
account_id: 123456
display_name: John Smith
email: JohnSmith@example.com
team_member_id: 111111
.tag: member_change_status_details
previous_value: {“.tag”: “not_joined”}
new_value: {“.tag”: “active”}
action: {“.tag”: “team_join_details”, “linked_apps”: [], “linked_devices”: [], “linked_shared_folders”: [], “has_linked_apps”: false, “has_linked_devices”: true, “has_linked_shared_folders”: false}
.tag: members .tag: member_change_status
description: Changed member status (invited, joined, suspended, etc.)
false geo_location: {“city”: “Tel Aviv”, “region”: “Tel Aviv”, “country”: “IL”, “ip_address”: “1.1.1.1”}
access_method: {“.tag”: “end_user”, “end_user”: {“.tag”: “web”, “session_id”: “222222”}}
2022-05-16T11:34:29Z
.tag: admin
admin: {“.tag”: “team_member”, “account_id”: “123456”, “display_name”: “John Smith”, “email”: “JohnSmith@example.com”, “team_member_id”: “111111”}
.tag: team_member
account_id: 123456
display_name: John Smith
email: JohnSmith@example.com
team_member_id: 111111
.tag: member_change_admin_role_details
new_value: {“.tag”: “team_admin”}
previous_value: {“.tag”: “member_only”}
.tag: members .tag: member_change_admin_role
description: Changed team member admin role
false geo_location: {“city”: “Tel Aviv”, “region”: “Tel Aviv”, “country”: “IL”, “ip_address”: “1.1.1.1”}
access_method: {“.tag”: “end_user”, “end_user”: {“.tag”: “web”, “session_id”: “222222”}}
2022-05-16T11:34:29Z
.tag: admin
admin: {“.tag”: “team_member”, “account_id”: “123456”, “display_name”: “John Smith”, “email”: “JohnSmith@example.com”, “team_member_id”: “111111”}
.tag: team .tag: member_send_invite_policy_changed_details
new_value: {“.tag”: “everyone”}
previous_value: {“.tag”: “specific_members”}
.tag: team_policies .tag: member_send_invite_policy_changed
description: Changed member send invite policy for team
false geo_location: {“city”: “Tel Aviv”, “region”: “Tel Aviv”, “country”: “IL”, “ip_address”: “1.1.1.1”}
access_method: {“.tag”: “end_user”, “end_user”: {“.tag”: “web”, “session_id”: “222222”}}
2022-05-16T11:34:33Z

Configuration parameters

  • url — Server URL (required)
  • credentials — App key (required)
  • fetch_from — First fetch in timestamp format (<number> <time unit>, e.g., 12 hours, 7 days)
  • limit — The maximum number of events per fetch.
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (5)

  • dropbox-auth-complete

    Completes the authentication.

  • dropbox-auth-reset

    Resets the authentication.

  • dropbox-auth-start

    Starts the authentication.

  • dropbox-auth-test

    Tests the authentication.

  • dropbox-get-events

    Get events.

# pylint: disable=no-name-in-module
# pylint: disable=no-self-argument
import json

import urllib3
from pydantic import parse_obj_as
from SiemApiModule import *  # noqa: E402

urllib3.disable_warnings()
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
VENDOR = "dropbox"
PRODUCT = "dropbox"


class DropboxEventsRequestConfig(IntegrationHTTPRequest):
    # Endpoint: https://api.dropbox.com/2/team_log/get_events
    url: AnyUrl = parse_obj_as(AnyUrl, "https://api.dropbox.com")
    method: Method = Method.POST
    headers: dict = {"Content-Type": "application/json"}
    data: str
    verify: bool = not demisto.params().get("insecure")


class DropboxEventsClient(IntegrationEventsClient):
    request: DropboxEventsRequestConfig
    options: IntegrationOptions
    credentials: Credentials

    def __init__(
        self,
        request: DropboxEventsRequestConfig,
        options: IntegrationOptions,
        credentials: Credentials,
        session: Optional[requests.Session] = None,
    ) -> None:
        self.credentials = credentials
        self.refresh_token = demisto.getIntegrationContext().get("refresh_token")
        if session is None:
            session = requests.Session()
        super().__init__(request, options, session)

    def set_request_filter(self, cursor: str):
        if "continue" not in str(self.request.url):
            demisto.info("continue not in request url")
            self.request.url = parse_obj_as(AnyUrl, f'{str(self.request.url).removesuffix("/")}/continue')

        self.request.data = json.dumps({"cursor": cursor})

    def get_access_token(self):
        request = IntegrationHTTPRequest(
            method=Method.POST,
            url=f'{str(self.request.url).removesuffix("/")}/oauth2/token',  # type: ignore[arg-type]
            data={"grant_type": "refresh_token", "refresh_token": f"{self.refresh_token}"},
            auth=HTTPBasicAuth(self.credentials.identifier, self.credentials.password),  # type: ignore[arg-type]
            verify=self.request.verify,
        )
        response = self.call(request)
        demisto.debug(f"Send request to obtain access_token get status code: {response.status_code}")  # pragma: no cover
        self.request.headers["Authorization"] = f'Bearer {response.json()["access_token"]}'
        self.request.url = parse_obj_as(AnyUrl, f'{str(self.request.url).removesuffix("/")}/2/team_log/get_events')


class DropboxEventsGetter(IntegrationGetEvents):
    client: DropboxEventsClient

    def get_last_run(self: Any, event: dict) -> dict:  # type: ignore
        last_datetime = datetime.strptime(event.get("timestamp", ""), DATETIME_FORMAT) + timedelta(seconds=1)
        return {"start_time": datetime.strftime(last_datetime, DATETIME_FORMAT)}

    def _iter_events(self):
        self.client.get_access_token()
        # region First Call
        results = self.client.call(self.client.request).json()
        # endregion

        # region Yield Response
        while results.get("events"):  # Run as long there are logs
            yield sorted(results.get("events", []), key=lambda d: d.get("timestamp"))

            if results.get("has_more"):
                self.client.set_request_filter(results.get("cursor"))
                demisto.debug(
                    f'Setting the next request filter {results.get("cursor")}'  # pragma: no cover
                )
                results = self.client.call(self.client.request).json()
            else:
                break


# ----------------------------------------- Authentication Functions -----------------------------------------


def start_auth_command(base_url: str, app_key: str) -> CommandResults:  # pragma: no cover
    url = f"https://www.dropbox.com/oauth2/authorize?client_id={app_key}&token_access_type=offline&response_type=code"
    message = f"""### Authorization instructions
1. To sign in, use a web browser to open the page [{url}]({url})
2. Run the **!dropbox-auth-complete** command with the code returned from Dropbox in the War Room."""
    demisto.debug("start auth command")
    return CommandResults(readable_output=message)


def complete_auth_command(code: str, credentials: Credentials, base_url: str, insecure: bool) -> CommandResults:
    data = {
        "grant_type": "authorization_code",
        "code": code,
    }
    auth = (credentials.identifier or "", credentials.password)
    redable_output = ""
    response = requests.post(f"{base_url}/oauth2/token", data=data, auth=auth, verify=insecure)
    if response.ok:
        demisto.setIntegrationContext({"refresh_token": response.json()["refresh_token"]})
        readable_output = "✅ Authorization completed successfully."
    else:
        readable_output = f"❌ Authorization completed failed. {response.text}"

    demisto.debug(f"Complete auth command {readable_output=}")  # pragma: no cover
    return CommandResults(readable_output=redable_output)


def reset_auth_command() -> CommandResults:
    demisto.debug("resetting integration context to empty dict.")  # pragma: no cover
    set_integration_context({})
    message = "Authorization was reset successfully. Run **!dropbox-auth-start** to start the authentication process."
    return CommandResults(readable_output=message)


def test_connection(events_client: DropboxEventsGetter) -> str:
    events_client.run()
    return "✅ Success."


# ----------------------------------------- Main Functions -----------------------------------------


def main(command: str, demisto_params: dict):
    first_fetch = datetime.strftime(
        dateparser.parse(demisto_params.get("fetch_from", "")) or datetime.now() - timedelta(days=7), DATETIME_FORMAT
    )
    start_time = demisto_params.get("start_time", first_fetch)
    request = DropboxEventsRequestConfig(data=json.dumps({"time": {"start_time": start_time}}), **demisto_params)
    credentials = Credentials(**demisto_params.get("credentials", {}))
    options = IntegrationOptions(**demisto_params)
    client = DropboxEventsClient(request, options, credentials)
    get_events = DropboxEventsGetter(client, options)

    try:
        base_url = str(demisto_params.get("url")).removesuffix("/")
        insecure = not demisto_params.get("insecure")

        if command == "test-module":
            raise DemistoException("Please run the !dropbox-auth-test command in order to test the connection")

        # ----- Authentication Commands ----- #
        elif command == "dropbox-auth-start":
            return_results(start_auth_command(base_url, str(credentials.identifier)))

        elif command == "dropbox-auth-complete":
            return_results(complete_auth_command(str(demisto_params.get("code")), credentials, base_url, insecure))

        elif not demisto.getIntegrationContext().get("refresh_token"):
            demisto.debug("Integration getIntegrationContext.get(refresh_token) is empty run auth start.")  # pragma: no cover
            return_results(CommandResults(readable_output="Please run the **!dropbox-auth-start** command first"))

        elif command == "dropbox-auth-reset":
            return_results(reset_auth_command())

        elif command == "dropbox-auth-test":
            results = test_connection(get_events)
            return_results(CommandResults(readable_output=results))

        # ----- Fetch/Get events command ----- #
        elif command in ("fetch-events", "dropbox-get-events"):
            events = get_events.run()

            if command == "fetch-events" or argToBoolean(demisto_params.get("should_push_events")):
                # Add _time field to each event before sending to XSIAM
                for event in events:
                    event["_time"] = event.get("timestamp")
                send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)

                if events:
                    last_run = get_events.get_last_run(events[-1])
                    demisto.debug(f"Set last run to {last_run}")  # pragma: no cover.
                    demisto.setLastRun(last_run)

            if command == "dropbox-get-events":
                command_results = CommandResults(
                    readable_output=tableToMarkdown("Dropbox logs", events, removeNull=True, headerTransform=pascalToSpace),
                    raw_response=events,
                )
                return_results(command_results)

    except Exception as e:
        return_error(f"An error was returned from dropbox event collector while executing {command} command. error: {e!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    # Args is always stronger. Get getLastRun even stronger
    demisto_params_ = demisto.params() | demisto.args() | demisto.getLastRun()
    main(demisto.command(), demisto_params_)