Compare commits

...

3 Commits

Author SHA1 Message Date
Guillaume BOURGEOIS 27615636e3 feat(gmail): new Gmail integration
Gmail API + Directory API (27 commands): mailbox search/read/send/
trash/label/move, attachments retrieval, vacation auto-reply, filters,
forwarding addresses, delegates, and Workspace user administration;
message ingestion (get_incidents) with a bundled OCSF mapper.
Service-account auth with domain-wide delegation (JWT RS256), runs on
a remote engine (requires PyJWT + cryptography).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:18:07 +02:00
Guillaume BOURGEOIS 3f754d14e9 feat(google-drive): new Google Drive integration
Drive API v3 (25 commands): shared drive management, change tracking,
Drive activity queries, file search/get/create/upload/download/copy/
move/delete, permission list/create/update/delete, and Drive labels.
Service-account auth with domain-wide delegation (JWT RS256), runs on
a remote engine (requires PyJWT + cryptography).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:17:58 +02:00
Guillaume BOURGEOIS dbb740f476 feat(google-calendar): new Google Calendar integration
Calendar API v3 ACL management: create access control rules (grant a
role to a user/group/domain/public) and list a calendar's ACL rules.
Service-account auth with domain-wide delegation (JWT RS256), runs on
a remote engine (requires PyJWT + cryptography).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:17:50 +02:00
60 changed files with 5871 additions and 0 deletions
@@ -0,0 +1,3 @@
name: "Gmail Message"
color: "#ea4335"
icon: "alert"
+339
View File
@@ -0,0 +1,339 @@
id: gmail
name: Gmail
version: 1.0.0
description: "Gmail API + Directory API — mailbox search/read/send/trash/labels, attachments retrieval, vacation auto-reply, filters, forwarding addresses, delegates, and Workspace user administration; message ingestion (get_incidents) with an OCSF mapper; service-account auth with domain-wide delegation. Runs on a remote engine. Requires the Python 'PyJWT' and 'cryptography' libraries on the engine host (pip install pyjwt cryptography)."
changelog: "1.0.0 — Initial release: mailbox commands (search, get mail/thread, send with attachments, trash/permanent delete, label moves, attachments download, labels list), vacation auto-reply get/set, mail filters (add/list/remove), forwarding addresses and auto-forwarding, delegates, Workspace user administration (list/get/create/delete, password reset, directory visibility), and message ingestion with a bundled OCSF mapper."
category: email
# Authentication: Google service account with domain-wide delegation.
# Every command builds a JWT (RS256, signed locally with the service account
# private key from service_account_json), sets `sub` to the mailbox being
# impersonated, exchanges it at https://oauth2.googleapis.com/token
# (grant_type=jwt-bearer) for an access token, then calls the Gmail /
# Admin SDK Directory REST APIs with that bearer token.
# The service account's client ID must be granted the Gmail scopes (and the
# Directory scopes for user-administration commands) in the Google Workspace
# Admin console: Security → API controls → Domain-wide delegation.
config_schema:
properties:
service_account_json:
type: string
description: "Google service account key file content (full JSON) — the account must have domain-wide delegation enabled"
x-soar-sensitive: true
user_id:
type: string
description: "Default mailbox to impersonate — also the admin account for user-administration commands"
required:
- service_account_json
- user_id
commands:
# ── Ingestion ─────────────────────────────────────────────────────────────
- id: get_incidents
name: gmail-get-incidents
description: "Fetch messages from a mailbox for ingestion (Gmail search query + time watermark). Returns {result:[flattened messages]}; use result as the alert rule results path."
risk: read
inputs_schema:
properties:
query: { type: string, description: "Gmail search query (e.g. is:unread in:inbox has:attachment)" }
after: { type: string, description: "Lower time bound — epoch seconds, epoch ms or ISO8601 (incremental fetch watermark, appended as after:<epoch>)" }
max: { type: number, description: "Maximum messages to fetch (default 50)" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
ingest:
results_path: result
dedup_key: id
incremental_field: after
# ── Mailbox ───────────────────────────────────────────────────────────────
- id: search
name: gmail-search
description: "Search messages with a Gmail query and return them flattened (headers, bodies, attachment metadata) plus a next_page_token for pagination."
risk: read
inputs_schema:
properties:
query: { type: string, description: "Gmail search query (e.g. from:alice@example.com subject:invoice newer_than:7d)" }
max_results: { type: number, description: "Maximum messages to return (default 100, capped at 500)" }
page_token: { type: string, description: "Page token from a previous call (next_page_token)" }
label_id: { type: string, description: "Restrict to a single label ID (e.g. INBOX, SPAM or a user label ID from gmail-list-labels)" }
include_spam_trash: { type: boolean, description: "Include messages from SPAM and TRASH" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
- id: get_mail
name: gmail-get-mail
description: "Retrieve a single message by ID, flattened (subject/from/to/cc, text and HTML bodies, attachment metadata)."
risk: read
inputs_schema:
properties:
message_id: { type: string, description: "Gmail message ID" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [message_id]
outputs_schema: { properties: {} }
- id: get_thread
name: gmail-get-thread
description: "Retrieve a conversation thread by ID with every message flattened."
risk: read
inputs_schema:
properties:
thread_id: { type: string, description: "Gmail thread ID" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [thread_id]
outputs_schema: { properties: {} }
- id: send_mail
name: gmail-send-mail
description: "Send an email from the impersonated mailbox — plain text and/or HTML body, optional single attachment (base64), reply threading via In-Reply-To/References."
inputs_schema:
properties:
to: { type: string, description: "Recipients, comma-separated" }
cc: { type: string, description: "Cc recipients, comma-separated" }
bcc: { type: string, description: "Bcc recipients, comma-separated" }
subject: { type: string, description: "Message subject" }
body: { type: string, description: "Plain-text body" }
html_body: { type: string, description: "HTML body (sent alongside the plain-text part when both are given)" }
reply_to: { type: string, description: "Reply-To header" }
in_reply_to: { type: string, description: "In-Reply-To header (Message-ID of the message being answered — see message_id_header)" }
references: { type: string, description: "References header (thread Message-ID chain)" }
attachment_name: { type: string, description: "Attachment file name (with attachment_base64)" }
attachment_base64: { type: string, description: "Attachment content, base64-encoded" }
user_id: { type: string, description: "Sender mailbox to impersonate (default from instance config)" }
required: [to, subject]
outputs_schema: { properties: {} }
- id: delete_mail
name: gmail-delete-mail
description: "Move a message to Trash, or delete it permanently (bypasses Trash) when permanent is true."
inputs_schema:
properties:
message_id: { type: string, description: "Gmail message ID" }
permanent: { type: boolean, description: "Permanently delete instead of moving to Trash (irreversible)" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [message_id]
outputs_schema: { properties: {} }
- id: move_mail
name: gmail-move-mail
description: "Add and/or remove labels on a message (move between folders — e.g. remove INBOX, add SPAM or a quarantine label)."
inputs_schema:
properties:
message_id: { type: string, description: "Gmail message ID" }
add_labels: { type: string, description: "Label IDs to add, comma-separated" }
remove_labels: { type: string, description: "Label IDs to remove, comma-separated" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [message_id]
outputs_schema: { properties: {} }
- id: get_attachments
name: gmail-get-attachments
description: "Download every attachment of a message; returns name, MIME type, size and base64 content for each."
risk: read
inputs_schema:
properties:
message_id: { type: string, description: "Gmail message ID" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [message_id]
outputs_schema: { properties: {} }
- id: list_labels
name: gmail-list-labels
description: "List the mailbox's labels (system and user labels with their IDs)."
risk: read
inputs_schema:
properties:
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
# ── Auto-reply (vacation responder) ───────────────────────────────────────
- id: autoreply_get
name: gmail-autoreply-get
description: "Get the mailbox's vacation auto-reply settings."
risk: read
inputs_schema:
properties:
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
- id: autoreply_set
name: gmail-autoreply-set
description: "Enable, update or disable the mailbox's vacation auto-reply."
inputs_schema:
properties:
enable: { type: boolean, description: "Enable the auto-reply (default true; false disables it)" }
response_subject: { type: string, description: "Auto-reply subject" }
response_body: { type: string, description: "Auto-reply plain-text body" }
response_body_html: { type: string, description: "Auto-reply HTML body" }
contacts_only: { type: boolean, description: "Only reply to senders in the user's contacts" }
domain_only: { type: boolean, description: "Only reply to senders in the same domain" }
start_time: { type: string, description: "Start time — epoch ms or ISO8601" }
end_time: { type: string, description: "End time — epoch ms or ISO8601" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
# ── Filters ───────────────────────────────────────────────────────────────
- id: add_filter
name: gmail-add-filter
description: "Create a mail filter on the mailbox (criteria: from/to/subject/query/has_attachment; actions: add/remove labels, forward)."
inputs_schema:
properties:
from: { type: string, description: "Criteria — sender address" }
to: { type: string, description: "Criteria — recipient address" }
subject: { type: string, description: "Criteria — subject contains" }
query: { type: string, description: "Criteria — Gmail search query" }
has_attachment: { type: boolean, description: "Criteria — only messages with attachments" }
add_labels: { type: string, description: "Action — label IDs to add, comma-separated" }
remove_labels: { type: string, description: "Action — label IDs to remove, comma-separated (e.g. INBOX to archive)" }
forward: { type: string, description: "Action — forward to this address (must be a verified forwarding address)" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
- id: list_filters
name: gmail-list-filters
description: "List the mailbox's mail filters."
risk: read
inputs_schema:
properties:
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
- id: remove_filter
name: gmail-remove-filter
description: "Delete a mail filter by ID."
inputs_schema:
properties:
filter_id: { type: string, description: "Filter ID (from gmail-list-filters)" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [filter_id]
outputs_schema: { properties: {} }
# ── Forwarding ────────────────────────────────────────────────────────────
- id: forwarding_add
name: gmail-forwarding-add
description: "Register a forwarding address on the mailbox (Google may require verification before it becomes usable)."
inputs_schema:
properties:
forwarding_email: { type: string, description: "Forwarding address to register" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [forwarding_email]
outputs_schema: { properties: {} }
- id: forwarding_list
name: gmail-forwarding-list
description: "List the mailbox's registered forwarding addresses and their verification status."
risk: read
inputs_schema:
properties:
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: []
outputs_schema: { properties: {} }
- id: forwarding_remove
name: gmail-forwarding-remove
description: "Delete a forwarding address from the mailbox."
inputs_schema:
properties:
forwarding_email: { type: string, description: "Forwarding address to remove" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [forwarding_email]
outputs_schema: { properties: {} }
- id: forwarding_update
name: gmail-forwarding-update
description: "Enable auto-forwarding of the mailbox to a verified forwarding address, with a disposition for the forwarded copy."
inputs_schema:
properties:
forwarding_email: { type: string, description: "Verified forwarding address to auto-forward to" }
disposition: { type: string, description: "What happens to the forwarded message in the mailbox: leaveInInbox, archive, trash or markRead" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [forwarding_email]
outputs_schema: { properties: {} }
# ── Delegates ─────────────────────────────────────────────────────────────
- id: delegate_add
name: gmail-delegate-add
description: "Grant a delegate access to the mailbox (delegate can read, send and delete on the owner's behalf)."
inputs_schema:
properties:
delegate_email: { type: string, description: "Delegate's email address (same Workspace domain)" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [delegate_email]
outputs_schema: { properties: {} }
- id: delegate_remove
name: gmail-delegate-remove
description: "Revoke a delegate's access to the mailbox."
inputs_schema:
properties:
delegate_email: { type: string, description: "Delegate's email address to revoke" }
user_id: { type: string, description: "Mailbox to impersonate (default from instance config)" }
required: [delegate_email]
outputs_schema: { properties: {} }
# ── Workspace user administration (Directory API — runs as the admin) ────
- id: list_users
name: gmail-list-users
description: "List Workspace users in a domain (Directory API, impersonates the configured admin account)."
risk: read
inputs_schema:
properties:
domain: { type: string, description: "Domain to list (defaults to the admin account's domain)" }
query: { type: string, description: "Directory search query (e.g. email:john* or name:'Jane Doe')" }
max_results: { type: number, description: "Maximum users per page (default 100, max 500)" }
page_token: { type: string, description: "Page token from a previous call" }
show_deleted: { type: boolean, description: "List recently deleted users instead of active ones" }
required: []
outputs_schema: { properties: {} }
- id: get_user
name: gmail-get-user
description: "Get a Workspace user by primary email, alias or unique ID (Directory API)."
risk: read
inputs_schema:
properties:
user_key: { type: string, description: "User's primary email, alias email or unique ID" }
required: [user_key]
outputs_schema: { properties: {} }
- id: create_user
name: gmail-create-user
description: "Create a Workspace user (Directory API)."
inputs_schema:
properties:
email: { type: string, description: "New user's primary email address" }
first_name: { type: string, description: "Given name" }
last_name: { type: string, description: "Family name" }
password: { type: string, description: "Initial password" }
required: [email, first_name, last_name, password]
outputs_schema: { properties: {} }
- id: delete_user
name: gmail-delete-user
description: "Delete a Workspace user (Directory API)."
inputs_schema:
properties:
user_key: { type: string, description: "User's primary email, alias email or unique ID" }
required: [user_key]
outputs_schema: { properties: {} }
- id: set_password
name: gmail-set-password
description: "Reset a Workspace user's password (Directory API) — a common containment step for a compromised account."
inputs_schema:
properties:
user_key: { type: string, description: "User's primary email, alias email or unique ID" }
password: { type: string, description: "New password" }
required: [user_key, password]
outputs_schema: { properties: {} }
- id: hide_user
name: gmail-hide-user
description: "Set a Workspace user's global directory visibility (Directory API)."
inputs_schema:
properties:
user_key: { type: string, description: "User's primary email, alias email or unique ID" }
visible: { type: boolean, description: "true to show the user in the global directory, false to hide" }
required: [user_key]
outputs_schema: { properties: {} }
- id: test_connection
name: gmail-test-connection
description: "Verify the service account credentials and delegation by fetching the configured mailbox's profile (used by the Test button)."
risk: read
inputs_schema:
properties: {}
required: []
outputs_schema: { properties: {} }
ingestion:
command: get_incidents
mapper: get_incidents
default_incident_type: "Gmail Message"
@@ -0,0 +1,15 @@
name: "Gmail Messages → OCSF"
description: "Maps a Gmail message (get_incidents, results_path = result) to OCSF finding fields. Messages are flattened by the fetch script (subject/from/to/date extracted from headers)."
field_mappings:
title: "subject"
severity: "2"
description: "snippet"
ocsf:
- { source_path: "id", ocsf_field: "finding_info.uid" }
- { source_path: "thread_id", ocsf_field: "finding_info.uid_alt" }
- { source_path: "subject", ocsf_field: "finding_info.title" }
- { source_path: "snippet", ocsf_field: "finding_info.desc" }
- { source_path: "date", ocsf_field: "finding_info.created_time" }
- { source_path: "labels", ocsf_field: "finding_info.types" }
- { source_path: "from", ocsf_field: "actor.user.name" }
- { source_path: "to", ocsf_field: "user.name" }
+102
View File
@@ -0,0 +1,102 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
criteria = {}
if inputs.get("from"):
criteria["from"] = inputs.get("from")
if inputs.get("to"):
criteria["to"] = inputs.get("to")
if inputs.get("subject"):
criteria["subject"] = inputs.get("subject")
if inputs.get("query"):
criteria["query"] = inputs.get("query")
if inputs.get("has_attachment"):
criteria["hasAttachment"] = True
if not criteria:
raise Exception("no criteria given")
action = {}
add_labels = [s.strip() for s in str(inputs.get("add_labels") or "").split(",") if s.strip()]
if add_labels:
action["addLabelIds"] = add_labels
remove_labels = [s.strip() for s in str(inputs.get("remove_labels") or "").split(",") if s.strip()]
if remove_labels:
action["removeLabelIds"] = remove_labels
if inputs.get("forward"):
action["forward"] = inputs.get("forward")
result = request("POST", base + "/settings/filters", SCOPES,
body={"criteria": criteria, "action": action}, subject=mailbox)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/settings/vacation", SCOPES, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
+112
View File
@@ -0,0 +1,112 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
from datetime import datetime
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def _bool(value, default=False):
if value in (None, ""):
return default
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def _to_epoch_ms(value):
s = str(value).strip()
if s.isdigit():
n = int(s)
if n < 10**12:
n *= 1000
return n
return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp() * 1000)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
body = {"enableAutoReply": _bool(inputs.get("enable"), default=True)}
if inputs.get("response_subject"):
body["responseSubject"] = str(inputs["response_subject"])
if inputs.get("response_body"):
body["responseBodyPlainText"] = str(inputs["response_body"])
if inputs.get("response_body_html"):
body["responseBodyHtml"] = str(inputs["response_body_html"])
if inputs.get("contacts_only") not in (None, ""):
body["restrictToContacts"] = _bool(inputs.get("contacts_only"))
if inputs.get("domain_only") not in (None, ""):
body["restrictToDomain"] = _bool(inputs.get("domain_only"))
if inputs.get("start_time") not in (None, ""):
body["startTime"] = _to_epoch_ms(inputs["start_time"])
if inputs.get("end_time") not in (None, ""):
body["endTime"] = _to_epoch_ms(inputs["end_time"])
res = request("PUT", base + "/settings/vacation", SCOPES, body=body, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
+97
View File
@@ -0,0 +1,97 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
email = str(inputs.get("email") or "").strip()
first_name = str(inputs.get("first_name") or "").strip()
last_name = str(inputs.get("last_name") or "").strip()
password = str(inputs.get("password") or "")
if not email:
raise Exception("email is required")
if not first_name:
raise Exception("first_name is required")
if not last_name:
raise Exception("last_name is required")
if len(password) < 8 or len(password) > 100:
raise Exception("password must be between 8 and 100 characters")
body = {
"primaryEmail": email,
"name": {
"givenName": first_name,
"familyName": last_name,
"fullName": first_name + " " + last_name,
},
"password": password,
}
result = request("POST", ADMIN_BASE + "/users", SCOPES, body=body)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
delegate_email = str(inputs.get("delegate_email") or "").strip()
if not delegate_email:
raise Exception("delegate_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("POST", base + "/settings/delegates", SCOPES,
body={"delegateEmail": delegate_email}, subject=mailbox)
if not result:
result = {"ok": True, "delegate_email": delegate_email}
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
delegate_email = str(inputs.get("delegate_email") or "").strip()
if not delegate_email:
raise Exception("delegate_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE",
base + "/settings/delegates/" + urllib.parse.quote(delegate_email, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "delegate_email": delegate_email}
print(json.dumps(result))
try:
main()
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)
+93
View File
@@ -0,0 +1,93 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://mail.google.com/"]
def _bool(value):
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
if _bool(inputs.get("permanent")):
request("DELETE", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, subject=mailbox)
print(json.dumps({"ok": True, "message_id": message_id}))
else:
res = request("POST", base + "/messages/" + urllib.parse.quote(message_id, safe="") + "/trash",
SCOPES, body={}, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
+81
View File
@@ -0,0 +1,81 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("DELETE", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES)
if not result:
result = {"ok": True, "user_key": user_key}
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,81 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("POST", base + "/settings/forwardingAddresses", SCOPES,
body={"forwardingEmail": forwarding_email}, subject=mailbox)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("GET", base + "/settings/forwardingAddresses", SCOPES, subject=mailbox)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE",
base + "/settings/forwardingAddresses/" + urllib.parse.quote(forwarding_email, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "forwarding_email": forwarding_email}
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
disposition = str(inputs.get("disposition") or "leaveInInbox")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("PUT", base + "/settings/autoForwarding", SCOPES,
body={"emailAddress": forwarding_email, "enabled": True, "disposition": disposition},
subject=mailbox)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,144 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
msg = request("GET", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
flat = flatten_message(msg)
attachments = []
for att in flat["attachments"]:
att_id = att.get("id", "")
if not att_id:
continue
res = request("GET",
base + "/messages/" + urllib.parse.quote(message_id, safe="")
+ "/attachments/" + urllib.parse.quote(att_id, safe=""),
SCOPES, subject=mailbox)
data = str(res.get("data") or "")
raw = base64.urlsafe_b64decode(data + "=" * (-len(data) % 4)) if data else b""
attachments.append({
"name": att.get("name", ""),
"mime_type": att.get("mime_type", ""),
"size": res.get("size", len(raw)),
"content_base64": base64.b64encode(raw).decode("ascii"),
})
print(json.dumps({"message_id": message_id, "attachments": attachments}))
try:
main()
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)
+143
View File
@@ -0,0 +1,143 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
from datetime import datetime
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def _to_epoch_seconds(value):
s = str(value).strip()
if s.isdigit():
n = int(s)
if n > 10**12:
n //= 1000
return n
return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp())
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
q = str(inputs.get("query") or "").strip()
after = inputs.get("after")
if after not in (None, ""):
q = (q + " " if q else "") + "after:" + str(_to_epoch_seconds(after))
max_results = int(inputs.get("max") or 50)
res = request("GET", base + "/messages", SCOPES,
params={"q": q, "maxResults": max_results}, subject=mailbox)
out = []
for m in res.get("messages", []) or []:
full = request("GET", base + "/messages/" + urllib.parse.quote(str(m.get("id", "")), safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
out.append(flatten_message(full))
print(json.dumps({"result": out}))
try:
main()
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)
+125
View File
@@ -0,0 +1,125 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
msg = request("GET", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
print(json.dumps(flatten_message(msg)))
try:
main()
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)
+126
View File
@@ -0,0 +1,126 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
thread_id = str(inputs.get("thread_id") or "").strip()
if not thread_id:
raise Exception("thread_id is required")
thread = request("GET", base + "/threads/" + urllib.parse.quote(thread_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
messages = [flatten_message(m) for m in thread.get("messages", []) or []]
print(json.dumps({"id": thread_id, "messages": messages}))
try:
main()
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)
+79
View File
@@ -0,0 +1,79 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("GET", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES)
print(json.dumps(result))
try:
main()
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)
+80
View File
@@ -0,0 +1,80 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("PUT", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES,
body={"includeInGlobalAddressList": bool(inputs.get("visible"))})
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("GET", base + "/settings/filters", SCOPES, subject=mailbox)
print(json.dumps(result))
try:
main()
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)
+77
View File
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/labels", SCOPES, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
+96
View File
@@ -0,0 +1,96 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
max_results = int(inputs.get("max_results") or 100)
if max_results > 500:
max_results = 500
domain = str(inputs.get("domain") or "").strip()
customer = ""
if not domain:
default_user = str(_cfg().get("user_id") or "")
if "@" in default_user:
domain = default_user.split("@", 1)[1]
else:
customer = "my_customer"
params = {
"domain": domain,
"query": inputs.get("query"),
"maxResults": max_results,
"pageToken": inputs.get("page_token"),
"showDeleted": "true" if inputs.get("show_deleted") else None,
"customer": customer,
}
result = request("GET", ADMIN_BASE + "/users", SCOPES, params=params)
print(json.dumps(result))
try:
main()
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)
+96
View File
@@ -0,0 +1,96 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.modify"]
def _csv(value):
return [x.strip() for x in str(value or "").split(",") if x.strip()]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
add_labels = _csv(inputs.get("add_labels"))
remove_labels = _csv(inputs.get("remove_labels"))
body = {}
if add_labels:
body["addLabelIds"] = add_labels
if remove_labels:
body["removeLabelIds"] = remove_labels
if not body:
raise Exception("Provide add_labels and/or remove_labels")
res = request("POST", base + "/messages/" + urllib.parse.quote(message_id, safe="") + "/modify",
SCOPES, body=body, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
filter_id = str(inputs.get("filter_id") or "").strip()
if not filter_id:
raise Exception("filter_id is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE", base + "/settings/filters/" + urllib.parse.quote(filter_id, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "filter_id": filter_id}
print(json.dumps(result))
try:
main()
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)
+143
View File
@@ -0,0 +1,143 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def _bool(value):
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
max_results = int(inputs.get("max_results") or 100)
if max_results > 500:
max_results = 500
params = {
"q": inputs.get("query"),
"maxResults": max_results,
"pageToken": inputs.get("page_token"),
"labelIds": inputs.get("label_id"),
}
if inputs.get("include_spam_trash") not in (None, ""):
params["includeSpamTrash"] = "true" if _bool(inputs.get("include_spam_trash")) else "false"
res = request("GET", base + "/messages", SCOPES, params=params, subject=mailbox)
out = []
for m in res.get("messages", []) or []:
full = request("GET", base + "/messages/" + urllib.parse.quote(str(m.get("id", "")), safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
out.append(flatten_message(full))
print(json.dumps({"result": out, "next_page_token": res.get("nextPageToken", "")}))
try:
main()
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)
+133
View File
@@ -0,0 +1,133 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
import mimetypes
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = [
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send",
]
def _build_message(inputs, mailbox):
body = str(inputs.get("body") or "")
html_body = str(inputs.get("html_body") or "")
att_name = str(inputs.get("attachment_name") or "")
att_b64 = str(inputs.get("attachment_base64") or "")
if html_body or att_b64:
message = MIMEMultipart("mixed" if att_b64 else "alternative")
if body:
message.attach(MIMEText(body, "plain", "utf-8"))
if html_body:
message.attach(MIMEText(html_body, "html", "utf-8"))
if att_b64:
name = att_name or "attachment.bin"
ctype = mimetypes.guess_type(name)[0] or "application/octet-stream"
main_type, sub_type = ctype.split("/", 1)
part = MIMEBase(main_type, sub_type)
part.set_payload(base64.b64decode(att_b64))
encoders.encode_base64(part)
part.add_header("Content-Disposition", "attachment", filename=name)
message.attach(part)
else:
message = MIMEText(body, "plain", "utf-8")
message["From"] = mailbox
message["To"] = str(inputs.get("to") or "")
if inputs.get("cc"):
message["Cc"] = str(inputs["cc"])
if inputs.get("bcc"):
message["Bcc"] = str(inputs["bcc"])
message["Subject"] = str(inputs.get("subject") or "")
if inputs.get("reply_to"):
message["Reply-To"] = str(inputs["reply_to"])
if inputs.get("in_reply_to"):
message["In-Reply-To"] = str(inputs["in_reply_to"])
if inputs.get("references"):
message["References"] = str(inputs["references"])
return message
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
if not str(inputs.get("to") or "").strip():
raise Exception("to is required")
if not str(inputs.get("subject") or "").strip():
raise Exception("subject is required")
message = _build_message(inputs, mailbox)
raw = base64.urlsafe_b64encode(message.as_bytes()).decode("ascii")
res = request("POST", base + "/messages/send", SCOPES, body={"raw": raw}, subject=mailbox)
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
password = str(inputs.get("password") or "")
if not password:
raise Exception("password is required")
result = request("PUT", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES,
body={"password": password})
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,78 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def main():
mailbox = str(_cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/profile", SCOPES, subject=mailbox)
if not res.get("emailAddress"):
raise Exception("Profile response missing emailAddress: " + json.dumps(res))
print(json.dumps({"ok": True, "email": res["emailAddress"]}))
try:
main()
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)
@@ -0,0 +1,62 @@
id: google_calendar
name: Google Calendar
version: 1.0.0
description: "Google Calendar (Calendar API v3) — manage calendar access control lists: grant a role on a calendar to a user, group, domain or the public, and list the existing ACL rules. Service-account authentication with domain-wide delegation. Runs on a remote engine. Requires the Python 'PyJWT' and 'cryptography' libraries on the engine host (pip install pyjwt cryptography)."
changelog: "1.0.0 — Initial release: ACL rule creation and listing on user calendars."
category: productivity
# Per-instance configuration. Create a Google Cloud service account with
# domain-wide delegation, authorize the https://www.googleapis.com/auth/calendar
# scope in the Workspace admin console, and paste the service account JSON key.
# The scripts sign a JWT (RS256) with the key and exchange it for an access
# token that impersonates user_id (or a per-command user override).
config_schema:
properties:
service_account_json:
type: string
description: "Service account key JSON (full file contents) with domain-wide delegation"
x-soar-sensitive: true
user_id:
type: string
description: "Default user to impersonate (primary email address)"
required:
- service_account_json
- user_id
commands:
- id: acl_add
name: google-calendar-acl-add
description: "Create an access control rule on a calendar (grant a role to a user, group, domain or the public)."
inputs_schema:
properties:
calendar_id: { type: string, description: "Calendar identifier — use 'primary' for the impersonated user's main calendar" }
role: { type: string, description: "Role: none, freeBusyReader, reader, writer or owner" }
scope_type: { type: string, description: "Grantee type: default (public), user, group or domain" }
scope_value: { type: string, description: "Email of the user/group or domain name (omit for scope_type=default)" }
send_notifications: { type: boolean, description: "Send notifications about the sharing change (default true)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [calendar_id, role, scope_type]
outputs_schema: { properties: {} }
- id: acl_list
name: google-calendar-acl-list
description: "List the access control rules of a calendar."
risk: read
inputs_schema:
properties:
calendar_id: { type: string, description: "Calendar identifier — use 'primary' for the impersonated user's main calendar" }
max_results: { type: number, description: "Maximum entries per page (default 100, max 250)" }
page_token: { type: string, description: "Token of the results page to return" }
show_deleted: { type: boolean, description: "Include deleted ACL rules (role 'none')" }
sync_token: { type: string, description: "nextSyncToken from a previous listing — returns only entries changed since" }
user_id: { type: string, description: "Override the impersonated user" }
required: [calendar_id]
outputs_schema: { properties: {} }
- id: test_connection
name: google-calendar-test-connection
description: "Verify the service account key and delegation (used by the Test button)."
risk: read
inputs_schema:
properties: {}
required: []
outputs_schema: { properties: {} }
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/calendar"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
calendar_id = str(inputs.get("calendar_id") or "")
role = str(inputs.get("role") or "")
scope_type = str(inputs.get("scope_type") or "")
if not calendar_id or not role or not scope_type:
raise Exception("calendar_id, role and scope_type are required")
body = {"role": role, "scope": {"type": scope_type}}
if inputs.get("scope_value"):
body["scope"]["value"] = inputs["scope_value"]
url = "https://www.googleapis.com/calendar/v3/calendars/" + urllib.parse.quote(calendar_id) + "/acl"
send = inputs.get("send_notifications")
params = {"sendNotifications": "false" if send is False else "true"}
print(json.dumps(request("POST", url, SCOPES, params=params, body=body, subject=inputs.get("user_id"))))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/calendar"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
calendar_id = str(inputs.get("calendar_id") or "")
if not calendar_id:
raise Exception("calendar_id is required")
url = "https://www.googleapis.com/calendar/v3/calendars/" + urllib.parse.quote(calendar_id) + "/acl"
params = {
"maxResults": int(inputs.get("max_results") or 100),
"pageToken": inputs.get("page_token"),
"showDeleted": "true" if inputs.get("show_deleted") else None,
"syncToken": inputs.get("sync_token"),
}
print(json.dumps(request("GET", url, SCOPES, params=params, subject=inputs.get("user_id"))))
try:
main()
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)
@@ -0,0 +1,76 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/calendar"]
def main():
res = request("GET", "https://www.googleapis.com/calendar/v3/users/me/calendarList", SCOPES,
params={"maxResults": 1})
if "items" not in res and "kind" not in res:
raise Exception("Unexpected response: " + json.dumps(res))
print(json.dumps({"ok": True, "impersonated": _cfg().get("user_id", "")}))
try:
main()
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)
+316
View File
@@ -0,0 +1,316 @@
id: google_drive
name: Google Drive
version: 1.0.0
description: "Google Drive (Drive API v3) — manage shared drives (create/list/get/delete), track file changes (start page token + change lists), query Drive activity, search and read files, create folders and metadata, upload/replace/download file content, copy, move and delete files, manage permissions (list/create/update/delete) and work with Drive labels. Authenticates with a Google service account (JWT bearer flow with domain-wide delegation to impersonate a Workspace user). Runs on a remote engine. Requires the Python 'PyJWT' and 'cryptography' libraries on the engine host (pip install pyjwt cryptography)."
changelog: "1.0.0 — Initial release: shared drive management, changes tracking, Drive activity queries, file search/get/create, content upload/replace/download, copy/move/delete, parents, permissions management, Drive labels, test connection."
category: productivity
# Per-instance configuration. Authentication uses the Google service-account
# JWT bearer flow: the scripts build a signed RS256 assertion from the service
# account's private_key/client_email, exchange it at the token endpoint for an
# access token, and set the JWT "sub" claim to the impersonated user (domain-wide
# delegation must be granted to the service account's client ID in the Google
# Workspace admin console, with the Drive scopes). user_id is the default user
# to impersonate; every command also accepts a per-call user_id override.
config_schema:
properties:
service_account_json:
type: string
description: "Full service account key JSON (as downloaded from Google Cloud IAM — must contain client_email and private_key)"
x-soar-sensitive: true
user_id:
type: string
description: "Default user email to impersonate via domain-wide delegation (e.g. admin@company.com)"
required:
- service_account_json
- user_id
commands:
- id: drive_create
name: google-drive-drive-create
description: "Create a new shared drive."
inputs_schema:
properties:
name: { type: string, description: "Name of the shared drive to create" }
hidden: { type: boolean, description: "Create the shared drive hidden from the default view" }
user_id: { type: string, description: "Override the impersonated user" }
required: [name]
outputs_schema: { properties: {} }
- id: drives_list
name: google-drive-drives-list
description: "List the user's shared drives, optionally filtered by a query."
risk: read
inputs_schema:
properties:
query: { type: string, description: "Search query for shared drives (e.g. name contains 'IR')" }
page_size: { type: number, description: "Maximum shared drives per page (default 100)" }
page_token: { type: string, description: "Page token from a previous list call" }
use_domain_admin_access: { type: boolean, description: "Issue the request as a domain administrator (returns all shared drives of the domain)" }
user_id: { type: string, description: "Override the impersonated user" }
required: []
outputs_schema: { properties: {} }
- id: drive_get
name: google-drive-drive-get
description: "Get a shared drive's metadata by ID."
risk: read
inputs_schema:
properties:
drive_id: { type: string, description: "ID of the shared drive" }
use_domain_admin_access: { type: boolean, description: "Issue the request as a domain administrator" }
user_id: { type: string, description: "Override the impersonated user" }
required: [drive_id]
outputs_schema: { properties: {} }
- id: drive_delete
name: google-drive-drive-delete
description: "Permanently delete a shared drive."
inputs_schema:
properties:
drive_id: { type: string, description: "ID of the shared drive to delete" }
use_domain_admin_access: { type: boolean, description: "Issue the request as a domain administrator" }
allow_item_deletion: { type: boolean, description: "Also delete items inside the shared drive (requires use_domain_admin_access)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [drive_id]
outputs_schema: { properties: {} }
- id: changes_start_token
name: google-drive-changes-start-token
description: "Get the starting page token for listing future changes (per user or per shared drive)."
risk: read
inputs_schema:
properties:
drive_id: { type: string, description: "Shared drive ID to get the start token for (omit for the user's own changes)" }
user_id: { type: string, description: "Override the impersonated user" }
required: []
outputs_schema: { properties: {} }
- id: changes_list
name: google-drive-changes-list
description: "List the changes for a user or shared drive from a given page token."
risk: read
inputs_schema:
properties:
page_token: { type: string, description: "Token from a previous changes list or from changes-start-token" }
drive_id: { type: string, description: "Shared drive ID to list changes from (omit for the user's own changes)" }
page_size: { type: number, description: "Maximum changes per page (default 100)" }
include_removed: { type: boolean, description: "Include changes for removed/trashed files (default true)" }
include_items_from_all_drives: { type: boolean, description: "Include changes from both My Drive and shared drive items" }
restrict_to_my_drive: { type: boolean, description: "Restrict results to files inside My Drive" }
spaces: { type: string, description: "Comma-separated spaces to query: drive and/or appDataFolder" }
fields: { type: string, description: "Response detail: basic (default fields) or advance (all fields)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [page_token]
outputs_schema: { properties: {} }
- id: activity_list
name: google-drive-activity-list
description: "Query Drive activity (Drive Activity API v2) for an item or a folder subtree."
risk: read
inputs_schema:
properties:
item_name: { type: string, description: "Activity for a single item, format items/ITEM_ID" }
folder_name: { type: string, description: "Activity for everything under a folder, format items/FOLDER_ID (sent as ancestorName)" }
filter: { type: string, description: "Activity filter (e.g. time > \"2026-01-01T00:00:00Z\" detail.action_detail_case:RENAME)" }
page_token: { type: string, description: "Page token from a previous activity query" }
page_size: { type: number, description: "Maximum activities per page" }
user_id: { type: string, description: "Override the impersonated user" }
required: []
outputs_schema: { properties: {} }
- id: files_list
name: google-drive-files-list
description: "Search and list files, optionally scoped to a shared drive or corpora."
risk: read
inputs_schema:
properties:
query: { type: string, description: "Search query (e.g. name contains 'report' and trashed = false)" }
page_size: { type: number, description: "Maximum files per page (default 100)" }
page_token: { type: string, description: "Page token from a previous list call" }
drive_id: { type: string, description: "Shared drive ID to search in (forces corpora=drive)" }
corpora: { type: string, description: "Bodies of items to query: user, domain, drive or allDrives (default user)" }
include_items_from_all_drives: { type: boolean, description: "Include items from both My Drive and shared drives" }
user_id: { type: string, description: "Override the impersonated user" }
required: []
outputs_schema: { properties: {} }
- id: file_get
name: google-drive-file-get
description: "Get a file's metadata by ID."
risk: read
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file" }
fields: { type: string, description: "Fields to return (default * — all fields)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: file_create
name: google-drive-file-create
description: "Create a folder or an empty file (metadata only — use file-upload for content)."
inputs_schema:
properties:
file_name: { type: string, description: "Name of the file or folder to create" }
mime_type: { type: string, description: "MIME type (default application/vnd.google-apps.folder — a folder)" }
parent: { type: string, description: "ID of the parent folder or shared drive" }
description: { type: string, description: "Short description of the file" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_name]
outputs_schema: { properties: {} }
- id: file_upload
name: google-drive-file-upload
description: "Upload a new file with content (base64-encoded)."
inputs_schema:
properties:
file_name: { type: string, description: "Name of the file to create" }
content_base64: { type: string, description: "File content, base64-encoded" }
parent: { type: string, description: "ID of the parent folder or shared drive" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_name, content_base64]
outputs_schema: { properties: {} }
- id: file_replace
name: google-drive-file-replace
description: "Replace an existing file's content (base64-encoded)."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file to update" }
content_base64: { type: string, description: "New file content, base64-encoded" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id, content_base64]
outputs_schema: { properties: {} }
- id: file_download
name: google-drive-file-download
description: "Download a file's content — returns file_name, mime_type, size and content_base64."
risk: read
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file to download" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: file_copy
name: google-drive-file-copy
description: "Copy a file, optionally with a new title."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file to copy" }
copy_title: { type: string, description: "Name of the copy (defaults to the original name)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: file_move
name: google-drive-file-move
description: "Move a file between folders (add one parent, remove another)."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file to move" }
add_parent_id: { type: string, description: "ID of the destination folder to add" }
remove_parent_id: { type: string, description: "ID of the current folder to remove" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id, add_parent_id, remove_parent_id]
outputs_schema: { properties: {} }
- id: file_delete
name: google-drive-file-delete
description: "Delete a file — permanently, or move it to the trash with soft_delete."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file to delete" }
soft_delete: { type: boolean, description: "Move the file to the trash instead of deleting it permanently" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: file_get_parents
name: google-drive-file-get-parents
description: "Get the parent folder IDs of a file."
risk: read
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: permissions_list
name: google-drive-permissions-list
description: "List the permissions of a file or shared drive."
risk: read
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file or shared drive" }
page_size: { type: number, description: "Maximum permissions per page" }
page_token: { type: string, description: "Page token from a previous list call" }
use_domain_admin_access: { type: boolean, description: "Issue the request as a domain administrator" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: permission_create
name: google-drive-permission-create
description: "Grant a permission on a file or shared drive (share with a user, group, domain or anyone)."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file or shared drive" }
role: { type: string, description: "Role to grant: reader, commenter, writer, fileOrganizer, organizer or owner (default reader)" }
type: { type: string, description: "Grantee type: user, group, domain or anyone (default user)" }
email_address: { type: string, description: "Email address of the user or group (type user/group)" }
domain: { type: string, description: "Domain name (type domain)" }
send_notification_email: { type: boolean, description: "Send a notification email to the grantee" }
transfer_ownership: { type: boolean, description: "Transfer ownership to the grantee (role owner)" }
move_to_new_owners_root: { type: boolean, description: "Move the file to the new owner's My Drive root on ownership transfer" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: permission_update
name: google-drive-permission-update
description: "Update a permission's role or expiration time."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file or shared drive" }
permission_id: { type: string, description: "ID of the permission to update" }
role: { type: string, description: "New role: reader, commenter, writer, fileOrganizer, organizer or owner" }
expiration_time: { type: string, description: "Expiration time (RFC 3339, e.g. 2026-12-31T23:59:59Z)" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id, permission_id]
outputs_schema: { properties: {} }
- id: permission_delete
name: google-drive-permission-delete
description: "Revoke a permission from a file or shared drive."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file or shared drive" }
permission_id: { type: string, description: "ID of the permission to delete" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id, permission_id]
outputs_schema: { properties: {} }
- id: get_labels
name: google-drive-get-labels
description: "List the Drive label definitions available to the user."
risk: read
inputs_schema:
properties:
user_id: { type: string, description: "Override the impersonated user" }
required: []
outputs_schema: { properties: {} }
- id: get_file_labels
name: google-drive-get-file-labels
description: "List the labels applied to a file."
risk: read
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id]
outputs_schema: { properties: {} }
- id: modify_label
name: google-drive-modify-label
description: "Apply, update or remove a Drive label on a file."
inputs_schema:
properties:
file_id: { type: string, description: "ID of the file" }
label_id: { type: string, description: "ID of the label to modify" }
field_id: { type: string, description: "ID of the label field to set (selection fields)" }
selection_label_id: { type: string, description: "Selection choice ID to set on the field" }
remove_label: { type: boolean, description: "Remove the label from the file instead of applying it" }
user_id: { type: string, description: "Override the impersonated user" }
required: [file_id, label_id]
outputs_schema: { properties: {} }
- id: test_connection
name: google-drive-test-connection
description: "Verify service-account credentials and delegation (used by the Test button)."
risk: read
inputs_schema:
properties: {}
required: []
outputs_schema: { properties: {} }
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive.activity.readonly"]
ACTIVITY_URL = "https://driveactivity.googleapis.com/v2/activity:query"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
body = {}
for key, field in (("item_name", "itemName"),
("folder_name", "ancestorName"),
("filter", "filter"),
("page_token", "pageToken"),
("page_size", "pageSize")):
val = inputs.get(key)
if val not in (None, ""):
body[field] = val
res = request("POST", ACTIVITY_URL, SCOPES, body=body, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,97 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
page_token = inputs.get("page_token")
if not page_token:
raise Exception("page_token is required")
params = {
"pageToken": page_token,
"driveId": inputs.get("drive_id"),
"pageSize": inputs.get("page_size") or 100,
"supportsAllDrives": "true",
"spaces": inputs.get("spaces"),
}
include_removed = inputs.get("include_removed")
if include_removed in (None, ""):
params["includeRemoved"] = "true"
else:
params["includeRemoved"] = "true" if str(include_removed).lower() in ("1", "true", "yes") else "false"
for key, param in (("include_items_from_all_drives", "includeItemsFromAllDrives"),
("restrict_to_my_drive", "restrictToMyDrive")):
val = inputs.get(key)
if val not in (None, ""):
params[param] = "true" if str(val).lower() in ("1", "true", "yes") else "false"
if inputs.get("fields") == "advance":
params["fields"] = "*"
res = request("GET", BASE + "/changes", SCOPES, params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,80 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
params = {
"driveId": inputs.get("drive_id"),
"supportsAllDrives": "true",
}
res = request("GET", BASE + "/changes/startPageToken", SCOPES,
params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,86 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import uuid
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
name = inputs.get("name")
if not name:
raise Exception("name is required")
body = {"name": name}
hidden = inputs.get("hidden")
if hidden not in (None, ""):
body["hidden"] = str(hidden).lower() in ("1", "true", "yes")
res = request("POST", BASE + "/drives", SCOPES,
params={"requestId": str(uuid.uuid4())},
body=body, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,87 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
drive_id = inputs.get("drive_id")
if not drive_id:
raise Exception("drive_id is required")
params = {}
for key, param in (("use_domain_admin_access", "useDomainAdminAccess"),
("allow_item_deletion", "allowItemDeletion")):
val = inputs.get(key)
if val not in (None, ""):
params[param] = "true" if str(val).lower() in ("1", "true", "yes") else "false"
res = request("DELETE", BASE + "/drives/" + urllib.parse.quote(str(drive_id), safe=""), SCOPES,
params=params, subject=inputs.get("user_id"))
if not res:
res = {"ok": True, "drive_id": drive_id}
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,82 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
drive_id = inputs.get("drive_id")
if not drive_id:
raise Exception("drive_id is required")
params = {"fields": "*"}
if str(inputs.get("use_domain_admin_access", "")).lower() in ("1", "true", "yes"):
params["useDomainAdminAccess"] = "true"
res = request("GET", BASE + "/drives/" + urllib.parse.quote(str(drive_id), safe=""), SCOPES,
params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
params = {
"q": inputs.get("query"),
"pageSize": inputs.get("page_size") or 100,
"pageToken": inputs.get("page_token"),
"fields": "*",
}
if str(inputs.get("use_domain_admin_access", "")).lower() in ("1", "true", "yes"):
params["useDomainAdminAccess"] = "true"
res = request("GET", BASE + "/drives", SCOPES, params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,90 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
copy_title = inputs.get("copy_title")
body = {"name": copy_title} if copy_title else {}
result = request(
"POST",
BASE + "/files/" + urllib.parse.quote(file_id, safe="") + "/copy",
SCOPES,
params={"supportsAllDrives": "true", "fields": "*"},
body=body,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,88 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_name = inputs.get("file_name")
if not file_name:
raise Exception("file_name is required")
body = {
"name": file_name,
"mimeType": inputs.get("mime_type") or "application/vnd.google-apps.folder",
}
if inputs.get("parent"):
body["parents"] = [inputs["parent"]]
if inputs.get("description"):
body["description"] = inputs["description"]
params = {"supportsAllDrives": "true", "fields": "*"}
res = request("POST", BASE + "/files", SCOPES, params=params, body=body,
subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,98 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
if inputs.get("soft_delete"):
result = request(
"PATCH",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={"supportsAllDrives": "true", "fields": "id,name,trashed,trashedTime"},
body={"trashed": True},
subject=subject,
)
print(json.dumps(result))
return
request(
"DELETE",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={"supportsAllDrives": "true"},
subject=subject,
)
print(json.dumps({"ok": True, "file_id": file_id}))
try:
main()
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)
@@ -0,0 +1,102 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
meta = request(
"GET",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={"fields": "id,name,mimeType,size", "supportsAllDrives": "true"},
subject=subject,
)
url = (BASE + "/files/" + urllib.parse.quote(file_id, safe="")
+ "?alt=media&supportsAllDrives=true")
headers = {"Authorization": "Bearer " + _token(SCOPES, subject)}
req = urllib.request.Request(url, headers=headers, method="GET")
with urllib.request.urlopen(req, timeout=300) as r:
content = r.read()
print(json.dumps({
"id": meta.get("id"),
"file_name": meta.get("name"),
"mime_type": meta.get("mimeType"),
"size": len(content),
"content_base64": base64.b64encode(content).decode("ascii"),
}))
try:
main()
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)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
params = {
"fields": inputs.get("fields") or "*",
"supportsAllDrives": "true",
}
res = request("GET", BASE + "/files/" + urllib.parse.quote(str(file_id), safe=""), SCOPES,
params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,86 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
result = request(
"GET",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={"fields": "id,parents", "supportsAllDrives": "true"},
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,98 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
add_parent_id = inputs.get("add_parent_id")
remove_parent_id = inputs.get("remove_parent_id")
if not file_id:
raise Exception("file_id is required")
if not add_parent_id:
raise Exception("add_parent_id is required")
if not remove_parent_id:
raise Exception("remove_parent_id is required")
subject = inputs.get("user_id")
result = request(
"PATCH",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={
"addParents": add_parent_id,
"removeParents": remove_parent_id,
"supportsAllDrives": "true",
"fields": "*",
},
body={},
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,96 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64, mimetypes
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
content_base64 = inputs.get("content_base64")
if not file_id:
raise Exception("file_id is required")
if not content_base64:
raise Exception("content_base64 is required")
subject = inputs.get("user_id")
content = base64.b64decode(content_base64)
content_type = mimetypes.guess_type(inputs.get("file_name") or "")[0] or "application/octet-stream"
url = ("https://www.googleapis.com/upload/drive/v3/files/"
+ urllib.parse.quote(file_id, safe="")
+ "?uploadType=media&supportsAllDrives=true&fields=*")
headers = {
"Accept": "application/json",
"Authorization": "Bearer " + _token(SCOPES, subject),
"Content-Type": content_type,
}
req = urllib.request.Request(url, data=content, headers=headers, method="PATCH")
with urllib.request.urlopen(req, timeout=120) as r:
print(json.dumps(json.loads(r.read())))
try:
main()
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)
@@ -0,0 +1,114 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64, mimetypes
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_name = inputs.get("file_name")
content_base64 = inputs.get("content_base64")
if not file_name:
raise Exception("file_name is required")
if not content_base64:
raise Exception("content_base64 is required")
subject = inputs.get("user_id")
content = base64.b64decode(content_base64)
content_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
# Step 1: upload raw content
upload_url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=media&supportsAllDrives=true"
headers = {
"Accept": "application/json",
"Authorization": "Bearer " + _token(SCOPES, subject),
"Content-Type": content_type,
}
req = urllib.request.Request(upload_url, data=content, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=120) as r:
created = json.loads(r.read())
file_id = created.get("id")
if not file_id:
raise Exception("Upload did not return a file id: " + json.dumps(created))
# Step 2: set the name (and parent, if any)
result = request(
"PATCH",
BASE + "/files/" + urllib.parse.quote(file_id, safe=""),
SCOPES,
params={
"addParents": inputs.get("parent") or None,
"supportsAllDrives": "true",
"fields": "*",
},
body={"name": file_name},
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,91 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
drive_id = inputs.get("drive_id")
corpora = inputs.get("corpora") or "user"
if drive_id:
corpora = "drive"
params = {
"q": inputs.get("query"),
"pageSize": inputs.get("page_size") or 100,
"pageToken": inputs.get("page_token"),
"driveId": drive_id,
"corpora": corpora,
"supportsAllDrives": "true",
"fields": "*",
}
val = inputs.get("include_items_from_all_drives")
if val not in (None, ""):
params["includeItemsFromAllDrives"] = "true" if str(val).lower() in ("1", "true", "yes") else "false"
res = request("GET", BASE + "/files", SCOPES, params=params, subject=inputs.get("user_id"))
print(json.dumps(res))
try:
main()
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)
@@ -0,0 +1,85 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.labels"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
result = request(
"GET",
BASE + "/files/" + urllib.parse.quote(file_id, safe="") + "/listLabels",
SCOPES,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,82 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.labels"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
subject = inputs.get("user_id")
result = request(
"GET",
"https://drivelabels.googleapis.com/v2/labels",
SCOPES,
params={"view": "LABEL_VIEW_FULL"},
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,98 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.labels"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
label_id = inputs.get("label_id")
if not file_id:
raise Exception("file_id is required")
if not label_id:
raise Exception("label_id is required")
subject = inputs.get("user_id")
mod = {"kind": "drive#labelModification", "labelId": label_id, "removeLabel": bool(inputs.get("remove_label"))}
if inputs.get("field_id"):
mod["fieldModifications"] = [{
"kind": "drive#labelFieldModification",
"fieldId": inputs["field_id"],
"setSelectionValues": [inputs.get("selection_label_id")] if inputs.get("selection_label_id") else [],
}]
body = {"kind": "drive#modifyLabelsRequest", "labelModifications": [mod]}
result = request(
"POST",
BASE + "/files/" + urllib.parse.quote(file_id, safe="") + "/modifyLabels",
SCOPES,
body=body,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,106 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
body = {}
for key, value in (
("role", inputs.get("role") or "reader"),
("type", inputs.get("type") or "user"),
("emailAddress", inputs.get("email_address")),
("domain", inputs.get("domain")),
):
if value:
body[key] = value
params = {"supportsAllDrives": "true", "fields": "*"}
for src, dst in (
("send_notification_email", "sendNotificationEmail"),
("transfer_ownership", "transferOwnership"),
("move_to_new_owners_root", "moveToNewOwnersRoot"),
):
if src in inputs and inputs[src] is not None:
params[dst] = "true" if inputs[src] else "false"
result = request(
"POST",
BASE + "/files/" + urllib.parse.quote(file_id, safe="") + "/permissions",
SCOPES,
params=params,
body=body,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,90 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
permission_id = inputs.get("permission_id")
if not file_id:
raise Exception("file_id is required")
if not permission_id:
raise Exception("permission_id is required")
subject = inputs.get("user_id")
request(
"DELETE",
BASE + "/files/" + urllib.parse.quote(file_id, safe="")
+ "/permissions/" + urllib.parse.quote(permission_id, safe=""),
SCOPES,
params={"supportsAllDrives": "true"},
subject=subject,
)
print(json.dumps({"ok": True, "permission_id": permission_id}))
try:
main()
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)
@@ -0,0 +1,99 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
permission_id = inputs.get("permission_id")
if not file_id:
raise Exception("file_id is required")
if not permission_id:
raise Exception("permission_id is required")
subject = inputs.get("user_id")
body = {}
if inputs.get("role"):
body["role"] = inputs["role"]
if inputs.get("expiration_time"):
body["expirationTime"] = inputs["expiration_time"]
if not body:
raise Exception("nothing to update")
result = request(
"PATCH",
BASE + "/files/" + urllib.parse.quote(file_id, safe="")
+ "/permissions/" + urllib.parse.quote(permission_id, safe=""),
SCOPES,
params={"supportsAllDrives": "true", "fields": "*"},
body=body,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,95 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
file_id = inputs.get("file_id")
if not file_id:
raise Exception("file_id is required")
subject = inputs.get("user_id")
params = {
"pageSize": inputs.get("page_size") or 100,
"pageToken": inputs.get("page_token"),
"supportsAllDrives": "true",
"fields": "*",
}
if inputs.get("use_domain_admin_access"):
params["useDomainAdminAccess"] = "true"
result = request(
"GET",
BASE + "/files/" + urllib.parse.quote(file_id, safe="") + "/permissions",
SCOPES,
params=params,
subject=subject,
)
print(json.dumps(result))
try:
main()
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)
@@ -0,0 +1,76 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/drive"]
BASE = "https://www.googleapis.com/drive/v3"
def main():
res = request("GET", BASE + "/about", SCOPES, params={"fields": "user"})
if "user" not in res:
raise Exception("Unexpected response from Drive API: " + json.dumps(res))
print(json.dumps({"ok": True, "user": res.get("user", {}).get("emailAddress", "")}))
try:
main()
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)