2354ab0a00
A personal access token silently outranked Basic credentials. Anything left in
pat — an email pasted into the wrong field, a token from a decommissioned Data
Center — became the Authorization header, and Atlassian answered "Client must
be authenticated to access this resource." That sentence names no field, so the
search starts at the URL, the API version, the token itself: everywhere except
the one field that caused it.
Basic authentication now wins whenever username and api_key are both set, and
pat serves the rest, so a token left behind cannot outrank credentials someone
has just filled in. A lone username, or a lone api_key, is not a pair and does
not claim the request. A 401 or 403 says which method was used and whether the
other was configured and ignored.
Three configurations that cannot work are refused with the reason rather than
sent to Jira:
- no usable credential, where an empty username/api_key pair was being
base64-encoded into a valid-looking header;
- the Cloud gateway URL with an empty cloud_id, which addresses no site, so
every path under it is a dead link and Jira replies with an HTML 404 that
explains nothing;
- a cloud_id set against any other host, where it is not an address at all,
only a path segment that does not exist.
Credentials are stripped before use: one pasted with a trailing newline
corrupts the header and reads back as a wrong password.
Error bodies are condensed instead of dumped. Jira's 404 page is four hundred
lines of AUI markup, stylesheet links and inline scripts around one sentence,
and it was going into the test modal whole; what surfaces now is the <title>
and the readable text, capped. A 2xx that is not JSON — a login page, an SSO
redirect, a proxy standing in for the API — reports what answered and the URL
the redirects landed on, in place of a JSON parser offset.
Config descriptions now say which fields pair with which deployment, and which
credential is used when both are present.
Applied to all 27 scripts; the seven shared helper blocks stay identical across
them.
368 lines
19 KiB
YAML
368 lines
19 KiB
YAML
id: jira
|
|
name: Jira
|
|
version: 1.0.1
|
|
description: "Atlassian Jira (REST API, Cloud and On-Prem/Data Center) — issue lifecycle (create/read/edit/delete, transitions, assign), comments, web + issue links, attachments (upload/download/delete), field and user lookups, agile boards/sprints/epics, plus issue ingestion (get_incidents) with an OCSF mapper. Basic (email + API token) or Personal Access Token authentication. Stdlib-only, no extra Python dependencies."
|
|
changelog: "1.0.1 — A personal access token silently outranked Basic credentials, so an unusable value left in pat turned every call into an opaque 401. Basic authentication now wins whenever username and api_key are both set, pat serves the rest, and a 401/403 names the method it used and whether the other was configured and ignored. Three configurations that cannot work are refused with the reason instead of being sent to Jira: no usable credential, the Cloud gateway URL without a cloud_id, and a cloud_id set against any other host. HTML error pages are condensed to their message rather than dumped whole, and a non-JSON body reports what answered and where. 1.0.0 — Initial release: JQL ingestion + query, full issue CRUD and transitions, comments, links, attachments, fields/users, and agile board/sprint/epic listing. Cloud (API v3, ADF bodies) and On-Prem (API v2) supported."
|
|
category: ticketing
|
|
|
|
# Per-instance configuration. For Jira Cloud set cloud_id (found at
|
|
# admin.atlassian.com/s/<cloud_id>/users) and keep the default server_url; for
|
|
# On-Prem/Data Center leave cloud_id empty and set server_url to your Jira URL.
|
|
# Authentication: Basic (username = account email + api_key = API token) OR a
|
|
# Personal Access Token (pat). Provide only one method.
|
|
config_schema:
|
|
properties:
|
|
server_url:
|
|
type: string
|
|
description: "Base URL. Jira Cloud: keep the default and set cloud_id. On-Prem/Data Center: your own Jira URL, e.g. https://jira.example.com, with cloud_id left empty."
|
|
default: https://api.atlassian.com/ex/jira
|
|
cloud_id:
|
|
type: string
|
|
description: "Jira Cloud ID, found at admin.atlassian.com/s/<cloud_id>/users. Required alongside the Cloud gateway URL; leave empty for On-Prem/Data Center."
|
|
username:
|
|
type: string
|
|
description: "Account email (Basic auth, Jira Cloud). Pairs with api_key: both must be set for Basic authentication to be used."
|
|
api_key:
|
|
type: string
|
|
description: "API token (Basic auth, Jira Cloud). Created at id.atlassian.com/manage-profile/security/api-tokens. Pairs with username."
|
|
x-soar-sensitive: true
|
|
pat:
|
|
type: string
|
|
description: "Personal Access Token (On-Prem/Data Center only). Used when username and api_key are not both set. Leave empty on Cloud."
|
|
x-soar-sensitive: true
|
|
required:
|
|
- server_url
|
|
|
|
commands:
|
|
# ── Ingestion / search ────────────────────────────────────────────────────
|
|
- id: get_incidents
|
|
name: jira-get-incidents
|
|
description: "Fetch Jira issues matching a JQL query for ingestion. Returns {result:[...flattened issues...]}; use result as the alert rule results path."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
query: { type: string, description: "JQL query (e.g. status != Done). Do not add an ORDER BY — the fetch adds one." }
|
|
after: { type: string, description: "Lower bound on created time, ISO8601 or epoch (incremental fetch watermark, applied as created >= ...)" }
|
|
max: { type: number, description: "Maximum issues to fetch (default 50)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
ingest:
|
|
results_path: result
|
|
dedup_key: id
|
|
incremental_field: after
|
|
- id: query_issues
|
|
name: jira-query-issues
|
|
description: "Run a JQL query and return flattened issues."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
query: { type: string, description: "JQL query string" }
|
|
max_results: { type: number, description: "Maximum issues (default 50)" }
|
|
next_page_token: { type: string, description: "Token for the next page (Jira Cloud only)" }
|
|
start_at: { type: number, description: "Index of the first issue (On-Prem; Cloud uses next_page_token)" }
|
|
required: [query]
|
|
outputs_schema: { properties: {} }
|
|
- id: get_issue
|
|
name: jira-get-issue
|
|
description: "Retrieve a single issue by ID or key (flattened)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key, e.g. PROJ-123 (issue_id or issue_key required)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Issue lifecycle ───────────────────────────────────────────────────────
|
|
- id: create_issue
|
|
name: jira-create-issue
|
|
description: "Create a new issue. Provide project_key (or project_name), issue_type_name (or issue_type_id) and a summary."
|
|
inputs_schema:
|
|
properties:
|
|
project_key: { type: string, description: "Project key (project_key or project_name required)" }
|
|
project_name: { type: string, description: "Project name (resolved to a key; project_key or project_name required)" }
|
|
issue_type_name: { type: string, description: "Issue type name, e.g. Task, Bug (issue_type_name or issue_type_id required)" }
|
|
issue_type_id: { type: string, description: "Issue type ID (issue_type_name or issue_type_id required)" }
|
|
summary: { type: string, description: "Issue summary (required)" }
|
|
description: { type: string, description: "Issue description" }
|
|
priority: { type: string, description: "Priority name, e.g. High, Medium" }
|
|
labels: { type: string, description: "Comma-separated labels" }
|
|
components: { type: string, description: "Comma-separated component names (must already exist)" }
|
|
assignee_id: { type: string, description: "Assignee account ID (Cloud)" }
|
|
assignee: { type: string, description: "Assignee username (On-Prem)" }
|
|
due_date: { type: string, description: "Due date (yyyy-mm-dd)" }
|
|
fields_json: { type: string, description: "Raw fields JSON merged into the request (advanced; wins on key conflicts)" }
|
|
required: [summary]
|
|
outputs_schema: { properties: {} }
|
|
- id: edit_issue
|
|
name: jira-edit-issue
|
|
description: "Edit an issue's fields, and/or move it with status/transition (see jira-list-transitions)."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
summary: { type: string, description: "New summary" }
|
|
description: { type: string, description: "New description" }
|
|
priority: { type: string, description: "Priority name" }
|
|
labels: { type: string, description: "Comma-separated labels (replaces existing)" }
|
|
components: { type: string, description: "Comma-separated component names (replaces existing)" }
|
|
assignee_id: { type: string, description: "Assignee account ID (Cloud)" }
|
|
assignee: { type: string, description: "Assignee username (On-Prem)" }
|
|
due_date: { type: string, description: "Due date (yyyy-mm-dd)" }
|
|
status: { type: string, description: "Target status name — transitions the issue (mutually exclusive with transition)" }
|
|
transition: { type: string, description: "Transition name to apply (mutually exclusive with status)" }
|
|
fields_json: { type: string, description: "Raw fields JSON merged into the request (advanced)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: delete_issue
|
|
name: jira-delete-issue
|
|
description: "Delete an issue (its sub-tasks are deleted too)."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: assign_issue
|
|
name: jira-assign-issue
|
|
description: "Assign an issue to a user (assignee_id for Cloud, assignee for On-Prem)."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
assignee_id: { type: string, description: "Assignee account ID (Cloud)" }
|
|
assignee: { type: string, description: "Assignee username (On-Prem)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: list_transitions
|
|
name: jira-list-transitions
|
|
description: "List the transitions available for an issue in its current status."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Comments ──────────────────────────────────────────────────────────────
|
|
- id: add_comment
|
|
name: jira-add-comment
|
|
description: "Add a comment to an issue."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
comment: { type: string, description: "Comment body" }
|
|
visibility: { type: string, description: "Restrict visibility to a role, e.g. Administrators" }
|
|
required: [comment]
|
|
outputs_schema: { properties: {} }
|
|
- id: get_comments
|
|
name: jira-get-comments
|
|
description: "Return the comments of an issue."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
limit: { type: number, description: "Maximum comments (default 50)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: edit_comment
|
|
name: jira-edit-comment
|
|
description: "Edit an existing comment on an issue."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
comment_id: { type: string, description: "Comment ID" }
|
|
comment: { type: string, description: "New comment body" }
|
|
visibility: { type: string, description: "Restrict visibility to a role" }
|
|
required: [comment_id, comment]
|
|
outputs_schema: { properties: {} }
|
|
- id: delete_comment
|
|
name: jira-delete-comment
|
|
description: "Delete a comment from an issue."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
comment_id: { type: string, description: "Comment ID" }
|
|
required: [comment_id]
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Links ─────────────────────────────────────────────────────────────────
|
|
- id: add_link
|
|
name: jira-add-link
|
|
description: "Add a remote web link (URL) to an issue."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
url: { type: string, description: "URL to link" }
|
|
title: { type: string, description: "Link title" }
|
|
summary: { type: string, description: "Link summary" }
|
|
relationship: { type: string, description: "Relationship to the issue, e.g. causes" }
|
|
required: [url, title]
|
|
outputs_schema: { properties: {} }
|
|
- id: link_issues
|
|
name: jira-link-issues
|
|
description: "Create a link between two issues (see jira-get-link-types for link_type names)."
|
|
inputs_schema:
|
|
properties:
|
|
inward_issue: { type: string, description: "Inward issue key or ID" }
|
|
outward_issue: { type: string, description: "Outward issue key or ID" }
|
|
link_type: { type: string, description: "Link type name, e.g. Blocks, Duplicate" }
|
|
comment: { type: string, description: "Optional comment to add to the inward issue" }
|
|
required: [inward_issue, outward_issue, link_type]
|
|
outputs_schema: { properties: {} }
|
|
- id: get_link_types
|
|
name: jira-get-link-types
|
|
description: "List all issue link types."
|
|
risk: read
|
|
inputs_schema:
|
|
properties: {}
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Attachments ───────────────────────────────────────────────────────────
|
|
- id: upload_file
|
|
name: jira-upload-file
|
|
description: "Attach a file to an issue (content provided as base64)."
|
|
inputs_schema:
|
|
properties:
|
|
issue_id: { type: string, description: "Issue ID (issue_id or issue_key required)" }
|
|
issue_key: { type: string, description: "Issue key (issue_id or issue_key required)" }
|
|
file_name: { type: string, description: "Attachment file name" }
|
|
content_base64: { type: string, description: "File content, base64-encoded" }
|
|
required: [file_name, content_base64]
|
|
outputs_schema: { properties: {} }
|
|
- id: get_attachment
|
|
name: jira-get-attachment
|
|
description: "Download an attachment's content by attachment ID (returned as base64)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
attachment_id: { type: string, description: "Attachment ID (from jira-get-issue)" }
|
|
required: [attachment_id]
|
|
outputs_schema: { properties: {} }
|
|
- id: delete_attachment
|
|
name: jira-delete-attachment
|
|
description: "Delete an attachment by attachment ID."
|
|
inputs_schema:
|
|
properties:
|
|
attachment_id: { type: string, description: "Attachment ID" }
|
|
required: [attachment_id]
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Fields / users ────────────────────────────────────────────────────────
|
|
- id: list_fields
|
|
name: jira-list-fields
|
|
description: "List the system and custom issue fields."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
limit: { type: number, description: "Maximum fields (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: get_id_by_attribute
|
|
name: jira-get-id-by-attribute
|
|
description: "Resolve a user's account ID (Cloud) or username (On-Prem) from a display name or email."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
attribute: { type: string, description: "Display name or email address to search for" }
|
|
max_results: { type: number, description: "Maximum candidates to consider (default 50)" }
|
|
required: [attribute]
|
|
outputs_schema: { properties: {} }
|
|
- id: get_user_info
|
|
name: jira-get-user-info
|
|
description: "Get a user's details (account_id for Cloud; key or username for On-Prem). With no identifier, returns the caller."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
account_id: { type: string, description: "Account ID (Cloud)" }
|
|
key: { type: string, description: "User key (On-Prem)" }
|
|
username: { type: string, description: "Username (On-Prem)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
# ── Agile: boards / sprints / epics ───────────────────────────────────────
|
|
- id: board_list
|
|
name: jira-board-list
|
|
description: "List agile boards (optionally filter by name, project or type)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
board_id: { type: string, description: "Return only this board" }
|
|
board_name: { type: string, description: "Filter by board name" }
|
|
project_key_id: { type: string, description: "Filter by project key or ID" }
|
|
type: { type: string, description: "Board type: scrum, kanban or simple" }
|
|
limit: { type: number, description: "Maximum boards (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
- id: board_issue_list
|
|
name: jira-board-issue-list
|
|
description: "List the issues of a board (flattened)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
board_id: { type: string, description: "Board ID" }
|
|
jql_query: { type: string, description: "JQL to filter the issues" }
|
|
limit: { type: number, description: "Maximum issues (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: [board_id]
|
|
outputs_schema: { properties: {} }
|
|
- id: board_sprint_list
|
|
name: jira-board-sprint-list
|
|
description: "List the sprints of a board."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
board_id: { type: string, description: "Board ID" }
|
|
limit: { type: number, description: "Maximum sprints (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: [board_id]
|
|
outputs_schema: { properties: {} }
|
|
- id: sprint_issue_list
|
|
name: jira-sprint-issue-list
|
|
description: "List the issues of a sprint (flattened)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
sprint_id: { type: string, description: "Sprint ID" }
|
|
jql_query: { type: string, description: "JQL to filter the issues" }
|
|
limit: { type: number, description: "Maximum issues (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: [sprint_id]
|
|
outputs_schema: { properties: {} }
|
|
- id: epic_issue_list
|
|
name: jira-epic-issue-list
|
|
description: "List the issues that belong to an epic (flattened)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties:
|
|
epic_id: { type: string, description: "Epic ID (epic_id or epic_key required)" }
|
|
epic_key: { type: string, description: "Epic key (epic_id or epic_key required)" }
|
|
jql_query: { type: string, description: "JQL to filter the issues" }
|
|
limit: { type: number, description: "Maximum issues (default 50)" }
|
|
offset: { type: number, description: "Starting index (default 0)" }
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
- id: test_connection
|
|
name: jira-test-connection
|
|
description: "Verify connectivity and credentials by reading the current user (used by the Test button)."
|
|
risk: read
|
|
inputs_schema:
|
|
properties: {}
|
|
required: []
|
|
outputs_schema: { properties: {} }
|
|
|
|
ingestion:
|
|
command: get_incidents
|
|
mapper: get_incidents
|
|
default_incident_type: "Jira Issue"
|