Compare commits
3 Commits
9f1df6ca7a
...
27615636e3
| Author | SHA1 | Date | |
|---|---|---|---|
| 27615636e3 | |||
| 3f754d14e9 | |||
| dbb740f476 |
@@ -0,0 +1,3 @@
|
|||||||
|
name: "Gmail Message"
|
||||||
|
color: "#ea4335"
|
||||||
|
icon: "alert"
|
||||||
@@ -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" }
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user