I Went Down a Rabbit Hole, And All I Got Was This Confluence UUID

Intro to Macro-IDs

The structure of a Confluence macro is predictable.  The structure of one instance of a macro looks the same as that of another instance of the same macro.    The only thing that makes two copies of the same macro unique is the UUID (or macro-id).

Example, here’s a page with two copies of the same macro on it

<ac:structured-macro ac:name="status" ac:schema-version="1" ac:macro-id="7a286483-1a4d-471b-8d13-f5ac544ab111">
<ac:parameter ac:name="title">Macro 1</ac:parameter></ac:structured-macro>

<ac:structured-macro ac:name="status" ac:schema-version="1" ac:macro-id="24363853-e334-4867-905d-d23ad501b5c9">
<ac:parameter ac:name="title">Macro 2</ac:parameter></ac:structured-macro>

The macro-id acts like the primary key in a database, and ensures that specific macros can be referenced when the page content is being parsed or referenced.

So, what if we wanted to programmatically insert a macro into the page? How would we auto-generate a UUID that the system would accept?

Examining UUIDs

Imagine that you have a draft of a Confluence page open.  Every time you make a change to the page, that change gets committed to the draft state of the page. This happens without you taking any action to commit the changes to the draft.  It’s essentially an autosave feature that is constantly running.

We can test this by examining the draft version of a page. 

So you’ve got a page, and you want to add a macro to it. By calling this REST API endpoint, we can view the current contents of a draft.

https://<confluence-url>.com/rest/tinymce/1/content/1507330.json

You can get the content ID of the draft from the page URL when you’re in edit mode, i.e.:

https://<confluence-url>com/pages/resumedraft.action?draftId=1507330&draftShareId=51141906-8216-4bf1-8f7d-5a8434226a32&

So if I’ve got the draft open in edit mode, and I view the contents of the draft using the first URL, the page contents look like this:

{
    "editorContent": "<p><img class=\"editor-inline-macro\" height=\"18\" width=\"88\" 
src=\"/plugins/servlet/status-macro/placeholder?title=Macro+1\" data-macro-name=\"status\" 
data-macro-id=\"7a286483-1a4d-471b-8d13-f5ac544ab111\" role=\"button\" tabindex=\"0\" 
aria-haspopup=\"true\" aria-label=\"Macro 1 status macro\" data-macro-parameters=\"title=Macro 1\" 
data-macro-schema-version=\"1\"></p><p><br /></p><p><img class=\"editor-inline-macro\" height=\"18\" 
width=\"88\" src=\"/plugins/servlet/status-macro/placeholder?title=Macro+2\" data-macro-name=\"status\" 
data-macro-id=\"24363853-e334-4867-905d-d23ad501b5c9\" role=\"button\" tabindex=\"0\" aria-haspopup=\"true\" 
aria-label=\"Macro 2 status macro\" data-macro-parameters=\"title=Macro 2\" data-macro-schema-version=\"1\">
</p><p><br /></p>",
    "atlToken": "0ea7f6c38cdbe271a420cc9823816xxxxx",
    "pageVersion": "1",
    "syncRevSource": "synchrony-ack",
    "permissions": {
        "viewPermissionsUsers": "",
        "viewPermissionsGroups": "",
        "editPermissionsGroups": "",
        "editPermissionsUsers": ""
    },
    "editMode": "collaborative",
    "syncRev": "0.aKNOur1kLn-xxxxxxx.0",
    "title": "Example Page",
    "confRev": "confluence$content$1507xxx.37"
}

However if I delete the page contents and type something new, without closing or saving the page, the contents of rest/tinymce/1/content/1507330.json return that change.

{
    "editorContent": "<p>The macros are gone</p><p><br /></p><p><br /></p>",
    "atlToken": "0ea7f6c38cdbe271a420xxxxx",
    "pageVersion": "1",
    "syncRevSource": "synchrony-ack",
    "permissions": {
        "viewPermissionsUsers": "",
        "viewPermissionsGroups": "",
        "editPermissionsGroups": "",
        "editPermissionsUsers": ""
    },
    "editMode": "collaborative",
    "syncRev": "0.nM6dm8K0opxxxxx",
    "title": "Example Page",
    "confRev": "confluence$content$1507xxx.38"
}

So if I watch the network traffic when adding a macro to a draft page, I should be able to see the format of the macro when it’s immediately added to the page, and to what URL that is being sent.  But that’s not what’s happening.

The Ugly Truth About UUID Generation

There are actually two ways in which Confluence can generate a UUID. The first is by making use of the window.crypto.randomUUID() function:

if(window.crypto&&window.crypto.randomUUID)return window.crypto.randomUUID}

If this function isn’t available, Confluence generates the UUID itself:

var generateUUID = function() {
    var a = function() {
        return Math.floor(65536 * (1 + Math.random())).toString(16).substring(1);
    };
    return a() + a() + "-" + a() + "-" + a() + "-" + a() + "-" + a() + a() + a();
};

The resulting value follows the standard format of a UUID, 8-4-4-4-12.  However, the characters generated are all clearly numbers, so this isn’t the UUID’s final form. It gets passed to a function that converts it to a proper UUID:

return ((t, n = 0) => 
    e[t[n + 0]] + e[t[n + 1]] + e[t[n + 2]] + e[t[n + 3]] + "-" +
    e[t[n + 4]] + e[t[n + 5]] + "-" + 
    e[t[n + 6]] + e[t[n + 7]] + "-" +
    e[t[n + 8]] + e[t[n + 9]] + "-" + 
    e[t[n + 10]] + e[t[n + 11]] + 
    e[t[n + 12]] + e[t[n + 13]] + 
    e[t[n + 14]] + e[t[n + 15]]
)(t);

What does it all mean?

Ultimately, the result of this rabbit hole is that Confluence macro-id generation is highly dependent on randomness, rather than being a derivative or sequential value.  The total number of possible UUIDs is 2^128, so the odds of a collision are extremely low. 

It is conceivable that there is actually some function within Confluence that checks for duplicate UUIDs.  However, the obfuscation of the Confluence code, in combination with the generally opaque way that Confluence works on the back-end, makes hunting for this theoretical function an unproductive use of time.

 

Leave a Reply

Your email address will not be published. Required fields are marked *