id: gmail_single_user
name: Gmail Single User
version: 1.0.0
description: "Gmail for a single mailbox over OAuth 2.0 (no service account, no domain-wide delegation) — search/read messages, send and reply (with attachments), retrieve attachments, and ingest messages (get_incidents) with an OCSF mapper. Uses a per-mailbox OAuth refresh token; stdlib-only, no extra Python dependencies."
changelog: "1.0.0 — Initial release: OAuth authorization link + code exchange, connectivity test, message ingestion with OCSF mapper, send/reply mail with attachments, and attachment retrieval."
category: email

# Per-instance configuration. This integration authenticates as ONE Gmail user
# via OAuth 2.0 (not a service account). One-time setup:
#   1. Create an OAuth client (type: Web application) in Google Cloud, authorize
#      the Gmail scopes, and note the Client ID / Client Secret. Add the redirect
#      URI below to the client's authorized redirect URIs.
#   2. Run gmail-single-user-auth-link, open the URL, consent, and copy the
#      "code" value from the redirect.
#   3. Run gmail-single-user-exchange-code with that code to get a refresh_token.
#   4. Paste the refresh_token into the config below and save.
# Every command then exchanges the refresh token for a short-lived access token.
config_schema:
  properties:
    email:
      type: string
      description: "The Gmail address of the mailbox (used as the default From address)"
    client_id:
      type: string
      description: "OAuth client ID"
    client_secret:
      type: string
      description: "OAuth client secret"
      x-soar-sensitive: true
    refresh_token:
      type: string
      description: "OAuth refresh token (obtain it once with gmail-single-user-exchange-code, then paste it here)"
      x-soar-sensitive: true
    redirect_uri:
      type: string
      description: "OAuth redirect URI registered on the client (must match at auth-link and exchange-code time)"
      default: http://localhost
    send_as:
      type: string
      description: "Optional address to send from (a verified send-as alias of the mailbox)"
  required:
    - email
    - client_id
    - client_secret

commands:
  - id: auth_link
    name: gmail-single-user-auth-link
    description: "Generate the OAuth consent URL. Open it, approve access, and copy the returned code for gmail-single-user-exchange-code."
    risk: read
    inputs_schema:
      properties: {}
      required: []
    outputs_schema: { properties: {} }
  - id: exchange_code
    name: gmail-single-user-exchange-code
    description: "Exchange an OAuth authorization code for a refresh token (paste the refresh_token into the instance config afterwards)."
    inputs_schema:
      properties:
        code: { type: string, description: "Authorization code from the consent redirect" }
        redirect_uri: { type: string, description: "Override the configured redirect URI (must match the one used for auth-link)" }
      required: [code]
    outputs_schema: { properties: {} }
  - id: get_incidents
    name: gmail-single-user-get-incidents
    description: "Fetch messages from the mailbox for ingestion. Returns {result:[...]}; use result as the alert rule results path."
    risk: read
    inputs_schema:
      properties:
        query: { type: string, description: "Extra Gmail search query (same syntax as the Gmail search box)" }
        after: { type: string, description: "Lower bound on message date, epoch (s/ms) or ISO8601 (incremental fetch watermark)" }
        subject: { type: string, description: "Filter by subject" }
        from: { type: string, description: "Filter by sender" }
        to: { type: string, description: "Filter by recipient" }
        filename: { type: string, description: "Filter by attachment filename or type" }
        label_id: { type: string, description: "Restrict to a label ID" }
        has_attachments: { type: boolean, description: "Only messages with attachments" }
        max: { type: number, description: "Maximum messages to fetch (default 50)" }
      required: []
    outputs_schema: { properties: {} }
    ingest:
      results_path: result
      dedup_key: id
      incremental_field: after
  - id: search
    name: gmail-single-user-search
    description: "Search the mailbox and return flattened messages (subject/from/to/body/attachments)."
    risk: read
    inputs_schema:
      properties:
        query: { type: string, description: "Gmail search query" }
        subject: { type: string, description: "Filter by subject" }
        from: { type: string, description: "Filter by sender" }
        to: { type: string, description: "Filter by recipient" }
        filename: { type: string, description: "Filter by attachment filename or type" }
        label_id: { type: string, description: "Restrict to a label ID" }
        has_attachments: { type: boolean, description: "Only messages with attachments" }
        max_results: { type: number, description: "Maximum messages (default 100, max 500)" }
        page_token: { type: string, description: "Page token for the next results page" }
        include_spam_trash: { type: boolean, description: "Include SPAM and TRASH" }
      required: []
    outputs_schema: { properties: {} }
  - id: get_mail
    name: gmail-single-user-get-mail
    description: "Retrieve a single message (flattened) by ID."
    risk: read
    inputs_schema:
      properties:
        message_id: { type: string, description: "Message ID" }
      required: [message_id]
    outputs_schema: { properties: {} }
  - id: send_mail
    name: gmail-single-user-send-mail
    description: "Send an email from the mailbox (optional single attachment as base64)."
    inputs_schema:
      properties:
        to: { type: string, description: "Recipient(s), comma-separated" }
        subject: { type: string, description: "Subject" }
        body: { type: string, description: "Plain-text body" }
        html_body: { type: string, description: "HTML body" }
        cc: { type: string, description: "CC recipient(s), comma-separated" }
        bcc: { type: string, description: "BCC recipient(s), comma-separated" }
        reply_to: { type: string, description: "Reply-To address" }
        attachment_name: { type: string, description: "Attachment file name (with attachment_base64)" }
        attachment_base64: { type: string, description: "Attachment content, base64-encoded" }
      required: [to, subject]
    outputs_schema: { properties: {} }
  - id: reply_mail
    name: gmail-single-user-reply-mail
    description: "Reply to a message (sets In-Reply-To/References so it threads correctly)."
    inputs_schema:
      properties:
        to: { type: string, description: "Recipient(s), comma-separated" }
        subject: { type: string, description: "Subject (keep the original for correct threading)" }
        in_reply_to: { type: string, description: "Message-ID header of the message being replied to" }
        references: { type: string, description: "References header (space-separated Message-IDs)" }
        body: { type: string, description: "Plain-text body" }
        html_body: { type: string, description: "HTML body" }
        cc: { type: string, description: "CC recipient(s), comma-separated" }
        bcc: { type: string, description: "BCC recipient(s), comma-separated" }
        reply_to: { type: string, description: "Reply-To address" }
        attachment_name: { type: string, description: "Attachment file name (with attachment_base64)" }
        attachment_base64: { type: string, description: "Attachment content, base64-encoded" }
      required: [to, subject, in_reply_to]
    outputs_schema: { properties: {} }
  - id: get_attachments
    name: gmail-single-user-get-attachments
    description: "Retrieve a message's attachments as base64."
    risk: read
    inputs_schema:
      properties:
        message_id: { type: string, description: "Message ID" }
      required: [message_id]
    outputs_schema: { properties: {} }

  - id: test_connection
    name: gmail-single-user-test-connection
    description: "Verify the OAuth refresh token by reading the mailbox 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 Single User Message"
