Atlassian Confluence Server

Atlassian Confluence Server API.

Utilities · Atlassian Confluence Server

Details

IDAtlassian Confluence Server
ProviderAtlassian
CategoryUtilities
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Atlassian Confluence Server API.
This integration was integrated and tested with version 6.1 of Atlassian Confluence Server.

Configure Atlassian Confluence Server in Cortex

Parameter Required
Server URL (e.g. http://1.2.3.4:8090) True
Username False
Password False
Personal Access Token False
Use system proxy settings False
Trust any certificate (not secure) False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

confluence-create-space


Creates a new Confluence space.

Base Command

confluence-create-space

Input

Argument Name Description Required
name Space name, for example: “Test Space”. Required
description A description for the space. Required
key Space key, which will be used as input when creating or updating child components from a space. Required

Context Output

Path Type Description
Confluence.Space.ID String Space ID.
Confluence.Space.Key String Space key.
Confluence.Space.Name String Space name.

confluence-create-content


Creates Confluence content for a given space.

Base Command

confluence-create-content

Input

Argument Name Description Required
title Confluence page title. Required
type Confluence content type. Can be “page” or “blogpost”. Possible values are: page, blogpost. Default is page. Required
space Space key to add content to a specific space. Required
body Confluence page body to add. Optional

Context Output

Path Type Description
Confluence.Content.ID String Page content ID.
Confluence.Content.Title String Content title.
Confluence.Content.Type String Content type.
Confluence.Content.Body String Content body.

confluence-list-spaces


Returns a list of all Confluence spaces.

Base Command

confluence-list-spaces

Input

Argument Name Description Required
limit Maximum number of spaces to return. Default is 25. Optional
type Filter the returned list of spaces by type. Can be “global” or “personal”. Possible values are: global, personal. Optional
status Filter the returned list of spaces by status. Can be “current” or “archived”. Possible values are: current, archived. Optional

Context Output

Path Type Description
Confluence.Space.ID String Space ID.
Confluence.Space.Key String Space key.
Confluence.Space.Name String Space name.

confluence-get-content


Returns Confluence content by space key and title.

Base Command

confluence-get-content

Input

Argument Name Description Required
key Space key. Required
title Content title. Required

Context Output

Path Type Description
Confluence.Content.ID String Content ID.
Confluence.Content.Title String Content title.
Confluence.Content.Type String Content type.
Confluence.Content.Version String Content version.
Confluence.Content.Body String Content body.

confluence-get-page-as-pdf


Returns Confluence Page as PDF by PageID.

Base Command

confluence-get-page-as-pdf

Input

Argument Name Description Required
pageid ID of the Page to download as PDF. Required

Context Output

Path Type Description
File.Size number File size.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Name string The sample name.
File.SSDeep string SSDeep hash of the file.
File.EntryID string War Room entry ID of the file.
File.Info string Basic information of the file.
File.Type string File type, e.g., “PE”.
File.MD5 string MD5 hash of the file.
File.Extension string File extension.

confluence-delete-content


Deletes Confluence content.

Base Command

confluence-delete-content

Input

Argument Name Description Required
id Content ID. Required

Context Output

Path Type Description
Confluence.Content.Result String Content delete result.
Confluence.Content.ID String Content ID deleted.

confluence-update-content


Update (overwrite) the existing content of a Confluence page with new content.

Base Command

confluence-update-content

Input

Argument Name Description Required
pageid Page ID used to find and update the page. Required
currentversion The version number, extracted from a content search. The integration will increment by 1. Required
title Title of the page to update. Required
type Content type. Can be “page” or “blogpost”. Possible values are: page, blogpost. Default is page. Required
space Space key to update. Required
body Content body to replace (overwrite) existing content of a Confluence page. Optional

Context Output

Path Type Description
Confluence.Content.ID String Content ID.
Confluence.Content.Title String Content title.
Confluence.Content.Type String Content type.
Confluence.Content.Body String Content body.

confluence-search-content


Fetches a list of content using the Confluence Query Language (CQL). For more information about CQL syntax, see https://developer.atlassian.com/server/confluence/advanced-searching-using-cql/

Base Command

confluence-search-content

Input

Argument Name Description Required
cql A CQL query string to use to locate content, for example: “space = DEV order by created”. Required
cqlcontext The context in which to execute a CQL search. The context is the JSON serialized form of SearchContext. Optional
expand A CSV list of properties to expand on the content. Default is version. Optional
start The start point of the collection to return. Optional
limit Maximum number of items to return. This can be restricted by fixed system limits. Default is 25. Default is 25. Optional

Context Output

Path Type Description
Confluence.Content.ID String Content ID.
Confluence.Content.Title String Content title.
Confluence.Content.Type String Content type.
Confluence.Content.Version String Content version.

Configuration parameters

  • url — Server URL (e.g. http://1.2.3.4:8090) (required)
  • credentials — Username
  • personal_access_token
  • proxy — Use system proxy settings
  • unsecure — Trust any certificate (not secure)

Commands (8)

  • confluence-create-content

    Creates Confluence content for a given space.

  • confluence-create-space

    Creates a new Confluence space.

  • confluence-delete-content

    Deletes Confluence content.

  • confluence-get-content

    Returns Confluence content by space key and title.

  • confluence-get-page-as-pdf

    Returns Confluence Page as PDF by PageID.

  • confluence-list-spaces

    Returns a list of all Confluence spaces.

  • confluence-search-content

    Fetches a list of content using the Confluence Query Language (CQL). For more information about CQL syntax, see https://developer.atlassian.com/server/confluence/advanced-searching-using-cql/

  • confluence-update-content

    Update (overwrite) the existing content of a Confluence page with new content.

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

"""IMPORTS"""
import json

import requests
import urllib3

urllib3.disable_warnings()

"""
GLOBAL VARIABLES
"""

SERVER = demisto.params()["url"][:-1] if demisto.params()["url"].endswith("/") else demisto.params()["url"]
BASE_URL = SERVER + "/rest/api"
VERIFY_CERTIFICATE = not demisto.params().get("unsecure", False)

# Support Credentials
USERNAME = demisto.params().get("credentials", {}).get("identifier")
PASSWORD = demisto.params().get("credentials", {}).get("password")
PERSONAL_ACCESS_TOKEN = demisto.params().get("personal_access_token", {}).get("password")
if not ((USERNAME and PASSWORD) or PERSONAL_ACCESS_TOKEN):
    return_error("You must provide either both Username and Password, or a Personal Access Token.")
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "X-Atlassian-Token": "no-check",
}
if PERSONAL_ACCESS_TOKEN:
    HEADERS["Authorization"] = f"Bearer {PERSONAL_ACCESS_TOKEN}"

"""
Helper Functions
"""


def http_request(
    method,
    full_url,
    data=None,
    params=None,
    is_test=False,
    resp_type: str = "json",
):  # pragma: no cover
    try:
        res = requests.request(
            method,
            full_url,
            verify=VERIFY_CERTIFICATE,
            auth=(USERNAME, PASSWORD) if not PERSONAL_ACCESS_TOKEN else None,
            data=data,
            headers=HEADERS,
            params=params,
        )
    except requests.exceptions.RequestException:  # This is the correct syntax
        return_error(f"Failed to connect to - {full_url} - Please check the URL")

    # Handle error responses gracefully
    if res.status_code < 200 or res.status_code >= 400:
        if is_test:
            return res

        return_error(
            f"Failed to execute command.\n"
            f"URL: {res.url}\n"
            f"Params: {params}\n"
            f"Status Code: {res.status_code}\n"
            f"Response: {res.text}"
        )

    if is_test:
        return res
    try:
        if resp_type == "json":
            return res.json()
        elif resp_type == "content":
            return res.content
        else:
            return res

    except ValueError as err:
        return_error(f"Failed to parse response from service, received the following error:\n{err!s}")


"""
Confluence Commands
"""


def update_content(page_id, content_title, space_key, content_body, content_type, content_version):
    content_data = {}
    # Populate the content_data dictionary
    content_data["type"] = content_type
    if space_key is not None:
        content_data["space"] = {"key": space_key}
    if content_title is not None:
        content_data["title"] = content_title

    content_data["body"] = {"storage": {"value": content_body, "representation": "storage"}}
    content_data["version"] = {"number": content_version}

    full_url = BASE_URL + "/content/" + page_id

    res = http_request("PUT", full_url, json.dumps(content_data))

    return res


def update_content_command():
    """
    Confluence Update Content method
    """

    page_id = demisto.args().get("pageid")
    content_title = demisto.args().get("title")
    space_key = demisto.args().get("space")
    content_body = demisto.args().get("body")
    content_type = demisto.args().get("type")
    content_version = int(demisto.args().get("currentversion")) + 1

    raw_content = update_content(page_id, content_title, space_key, content_body, content_type, content_version)
    content = {
        "ID": page_id,
        "Title": content_title,
        "Type": content_type,
        "Body": content_body,
    }

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Updated Content", content, ["ID", "Title", "Type", "Body"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": raw_content,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Content(val.ID == obj.ID)": content},
        }
    )


def create_content(content_type, content_title, space_key, content_body):
    content_data = {
        "type": content_type,
        "space": {"key": space_key},
        "title": content_title,
        "body": {"storage": {"value": content_body, "representation": "storage"}},
    }

    full_url = BASE_URL + "/content"

    res = http_request("POST", full_url, json.dumps(content_data))

    return res


def create_content_command():
    """
    Confluence Create Content method
    """
    content_type = demisto.args().get("type")
    content_title = demisto.args().get("title")
    space_key = demisto.args().get("space")
    content_body = demisto.args().get("body")

    raw_content = create_content(content_type, content_title, space_key, content_body)

    content = {
        "ID": raw_content["id"],
        "Title": content_title,
        "Type": content_type,
        "Body": content_body,
    }

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("New Content", content, ["ID", "Title", "Type", "Body"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": raw_content,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Content(val.ID == obj.ID)": content},
        }
    )


def create_space(space_description, space_key, space_name):
    space_data = {
        "type": "global",
        "description": {"plain": {"value": space_description, "representation": "plain"}},
        "name": space_name,
        "key": space_key,
    }

    full_url = BASE_URL + "/space"

    res = http_request("POST", full_url, json.dumps(space_data))

    return res


def create_space_command():
    """
    Confluence Create Space method
    """
    space_description = demisto.args().get("description")
    space_key = demisto.args().get("key")
    space_name = demisto.args().get("name")

    raw_space = create_space(space_description, space_key, space_name)

    space = {"ID": raw_space["id"], "Key": raw_space["key"], "Name": raw_space["name"]}

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Space created successfully", space, ["ID", "Key", "Name"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": raw_space,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Space(val.ID == obj.ID)": space},
        }
    )


def get_content(key, title):
    params = {"title": title, "spaceKey": key, "expand": "body.view,version"}

    full_url = BASE_URL + "/content"

    res = http_request("GET", full_url, None, params)

    return res


def get_pdf(page_id):
    params = {"pageId": page_id}

    full_url = SERVER + "/spaces/flyingpdf/pdfpageexport.action"
    res = http_request("GET", full_url, None, params=params, resp_type="content")

    return res


def get_content_command():
    """
    Confluence Get Content method
    """
    space_key = demisto.args().get("key")
    content_title = demisto.args().get("title")
    raw_content = get_content(space_key, content_title)

    content_list = []
    for obj in raw_content["results"]:
        content = {"ID": obj["id"], "Title": obj["title"], "Type": obj["type"]}
        if obj.get("version") is not None:
            content["Version"] = obj["version"]["number"]
        if obj.get("body") is not None:
            content["Body"] = obj["body"]["view"]["value"]

        content_list.append(content)

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Content", content_list, ["ID", "Title", "Type", "Version", "Body"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": raw_content,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Content(val.ID == obj.ID)": content_list},
        }
    )


def get_page_as_pdf_command():
    """
    Confluence Get Page as PDF command method
    """
    page_id = demisto.args().get("pageid")
    pdf = get_pdf(page_id)

    demisto.results(fileResult(f"Confluence_page_{page_id}.pdf", pdf))


def search_content(cql, cql_context, expand, start, limit):
    params = {"limit": limit, "cql": cql}
    if cql_context is not None:
        params["cqlcontext"] = cql_context

    if expand is not None:
        params["expand"] = expand

    if start is not None:
        params["start"] = start

    full_url = BASE_URL + "/content/search"

    res = http_request("GET", full_url, None, params)

    return res


def search_content_command():
    """
    Confluence Search Content method
    Reference:  https://developer.atlassian.com/server/confluence/advanced-searching-using-cql/
    """

    cql = demisto.args().get("cql")
    cql_context = demisto.args().get("cqlcontext")
    expand = demisto.args().get("expand")
    start = demisto.args().get("start")
    limit = demisto.args().get("limit")

    raw_search = search_content(cql, cql_context, expand, start, limit)

    searches = []
    for result in raw_search["results"]:
        search = {}

        search["ID"] = result["id"]
        search["Title"] = result["title"]
        search["Type"] = result["type"]
        if result.get("version") is not None:
            search["Version"] = result["version"]["number"]

        searches.append(search)

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Content Search", searches, ["ID", "Title", "Type", "Version"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": raw_search,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Content(val.ID == obj.ID)": searches},
        }
    )


def list_spaces(limit, status, space_type):
    full_url = BASE_URL + "/space"

    params = {"limit": limit}

    if status:
        params["status"] = status

    if space_type:
        params["type"] = space_type

    res = http_request("GET", full_url, params=params)

    return res


def list_spaces_command():
    """
    Confluence list Spaces method
    """
    limit = demisto.args().get("limit", 25)
    status = demisto.args().get("status")
    space_type = demisto.args().get("type")
    space_list = list_spaces(limit, status, space_type)

    spaces = []
    for raw_space in space_list["results"]:
        space = {}

        space["ID"] = raw_space["id"]
        space["Key"] = raw_space["key"]
        space["Name"] = raw_space["name"]

        spaces.append(space)

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Spaces", spaces, ["ID", "Key", "Name"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": space_list,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Space(val.ID == obj.ID)": spaces},
        }
    )


def delete_content(content_id):
    full_url = BASE_URL + "/content/" + content_id
    http_request("DELETE", full_url, is_test=True)
    result = {
        "Results": "Successfully Deleted Content ID " + content_id,
        "ID": content_id,
    }
    return result


def delete_content_command():
    """
    Confluence Delete Content Spaces method
    """

    content_id = demisto.args().get("id")

    deleted_content = delete_content(content_id)

    # create markdown table string from context
    # the outputs must be array in order the tableToMarkdown to work
    # headers must be array of strings (which column should appear in the table)
    md = tableToMarkdown("Content", deleted_content, ["ID", "Results"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": deleted_content,
            "HumanReadable": md,
            "EntryContext": {"Confluence.Content(val.ID == obj.ID)": deleted_content},
        }
    )


def test():  # pragma: no cover
    full_url = BASE_URL + "/user/current"
    res = http_request("GET", full_url, is_test=True)

    if not res:
        return_error(
            "Test failed. \nCheck URL and Username/Password.\nURL: {}, Status Code: {}, Response: {}".format(
                full_url, res.status_code, res.text.encode("utf8")
            )
        )

    demisto.results("ok")


"""
CODE EXECUTION STARTS HERE

demisto.command() returns the name of the command which executed now
"""
LOG(f"Confluence integration is executing the command {demisto.command()}")
try:
    handle_proxy()
    if demisto.command() == "test-module":
        """
        demisto.command() will return 'test-module' when the Test button in integration page clicked
        """
        test()

    elif demisto.command() == "confluence-create-space":
        create_space_command()

    elif demisto.command() == "confluence-create-content":
        create_content_command()

    elif demisto.command() == "confluence-get-content":
        get_content_command()

    elif demisto.command() == "confluence-get-page-as-pdf":
        get_page_as_pdf_command()

    elif demisto.command() == "confluence-list-spaces":
        list_spaces_command()

    elif demisto.command() == "confluence-delete-content":
        delete_content_command()

    elif demisto.command() == "confluence-update-content":
        update_content_command()

    elif demisto.command() == "confluence-search-content":
        search_content_command()

except Exception as e:
    return_error(str(e))