diff --git a/integrations/dropbox/manifest.yaml b/integrations/dropbox/manifest.yaml new file mode 100644 index 0000000..19baf70 --- /dev/null +++ b/integrations/dropbox/manifest.yaml @@ -0,0 +1,63 @@ +id: dropbox +name: Dropbox +version: 1.0.0 +description: "Dropbox (API v2) — evidence and file handling: search files, get file/folder metadata, list a folder, and create a shared link. Bearer-token authentication; stdlib-only, no extra Python dependencies." +changelog: "1.0.0 — Initial release: search, get metadata, list folder, create shared link." +category: productivity + +# Per-instance configuration. Auth header 'Authorization: Bearer '. +config_schema: + properties: + access_token: + type: string + description: "Dropbox access token" + x-soar-sensitive: true + required: + - access_token + +commands: + - id: search + name: dropbox-search + description: "Search files and folders by keyword." + risk: read + inputs_schema: + properties: + query: { type: string, description: "Search query" } + max_results: { type: number, description: "Max results (default 20)" } + required: [query] + outputs_schema: { properties: {} } + - id: get_metadata + name: dropbox-get-metadata + description: "Get metadata for a file or folder by path." + risk: read + inputs_schema: + properties: + path: { type: string, description: "Path (e.g. /Evidence/report.pdf)" } + required: [path] + outputs_schema: { properties: {} } + - id: list_folder + name: dropbox-list-folder + description: "List the contents of a folder." + risk: read + inputs_schema: + properties: + path: { type: string, description: "Folder path ('' or '/' for root)" } + required: [] + outputs_schema: { properties: {} } + - id: create_shared_link + name: dropbox-create-shared-link + description: "Create a shared link for a file or folder." + inputs_schema: + properties: + path: { type: string, description: "Path to share" } + required: [path] + outputs_schema: { properties: {} } + + - id: test_connection + name: dropbox-test-connection + description: "Verify the access token (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/dropbox/scripts/create_shared_link.py b/integrations/dropbox/scripts/create_shared_link.py new file mode 100644 index 0000000..cc1572b --- /dev/null +++ b/integrations/dropbox/scripts/create_shared_link.py @@ -0,0 +1,45 @@ +import json, os, sys, urllib.request, urllib.error + +API = "https://api.dropboxapi.com/2" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def rpc(path, cfg, body): + data = json.dumps(body).encode("utf-8") + headers = { + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + "Accept": "application/json", + } + req = urllib.request.Request(API + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + path = inputs.get("path") + if not path: + raise Exception("path is required") + return rpc("/sharing/create_shared_link_with_settings", cfg, {"path": path}) + + +_run(main) diff --git a/integrations/dropbox/scripts/get_metadata.py b/integrations/dropbox/scripts/get_metadata.py new file mode 100644 index 0000000..586c24a --- /dev/null +++ b/integrations/dropbox/scripts/get_metadata.py @@ -0,0 +1,45 @@ +import json, os, sys, urllib.request, urllib.error + +API = "https://api.dropboxapi.com/2" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def rpc(path, cfg, body): + data = json.dumps(body).encode("utf-8") + headers = { + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + "Accept": "application/json", + } + req = urllib.request.Request(API + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + path = inputs.get("path") + if not path: + raise Exception("path is required") + return rpc("/files/get_metadata", cfg, {"path": path}) + + +_run(main) diff --git a/integrations/dropbox/scripts/list_folder.py b/integrations/dropbox/scripts/list_folder.py new file mode 100644 index 0000000..0fab5d1 --- /dev/null +++ b/integrations/dropbox/scripts/list_folder.py @@ -0,0 +1,44 @@ +import json, os, sys, urllib.request, urllib.error + +API = "https://api.dropboxapi.com/2" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def rpc(path, cfg, body): + data = json.dumps(body).encode("utf-8") + headers = { + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + "Accept": "application/json", + } + req = urllib.request.Request(API + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + # Dropbox root is the empty string "", not "/". + path = inputs.get("path") or "" + return rpc("/files/list_folder", cfg, {"path": path}) + + +_run(main) diff --git a/integrations/dropbox/scripts/search.py b/integrations/dropbox/scripts/search.py new file mode 100644 index 0000000..91b4dc4 --- /dev/null +++ b/integrations/dropbox/scripts/search.py @@ -0,0 +1,47 @@ +import json, os, sys, urllib.request, urllib.error + +API = "https://api.dropboxapi.com/2" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def rpc(path, cfg, body): + data = json.dumps(body).encode("utf-8") + headers = { + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + "Accept": "application/json", + } + req = urllib.request.Request(API + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + query = inputs.get("query") + if not query: + raise Exception("query is required") + max_results = inputs.get("max_results") + max_results = int(max_results or 20) + return rpc("/files/search_v2", cfg, {"query": query, "options": {"max_results": max_results}}) + + +_run(main) diff --git a/integrations/dropbox/scripts/test_connection.py b/integrations/dropbox/scripts/test_connection.py new file mode 100644 index 0000000..530c9ba --- /dev/null +++ b/integrations/dropbox/scripts/test_connection.py @@ -0,0 +1,54 @@ +import json, os, sys, urllib.request, urllib.error + +API = "https://api.dropboxapi.com/2" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def rpc(path, cfg, body): + data = json.dumps(body).encode("utf-8") + headers = { + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + "Accept": "application/json", + } + req = urllib.request.Request(API + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + # get_current_account needs a JSON null body, not an empty dict. + req = urllib.request.Request( + API + "/users/get_current_account", + data=b"null", + headers={ + "Authorization": "Bearer " + str(cfg.get("access_token", "")), + "Content-Type": "application/json", + }, + method="POST", + ) + with urllib.request.urlopen(req, timeout=60) as r: + r.read() + return {"ok": True} + + +_run(main)