Files
riposte-marketplace/integrations/jira/manifest.yaml
T
f3nris 2354ab0a00 fix(jira): name the credential that failed instead of returning a bare 401
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.
2026-09-02 12:49:26 +02:00

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"