From dde0cf5dfc51ec28f4b1012698d292243e9923c6 Mon Sep 17 00:00:00 2001 From: Valentin Lemaire Date: Fri, 12 Jul 2024 11:51:28 +0200 Subject: [PATCH 01/10] feat: add User, Group and App history with Backupta --- rockstar/rockstar.js | 234 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 224 insertions(+), 10 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index c7f5868..8d815b1 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -112,6 +112,27 @@ searchPlaceholder: "Search hook...", oktaFilter: 'eventType eq "event_hook.deleted"', backuptaFilterBy: 'type:DELETE;component:EVENT_HOOKS' + }, + userObjectHistory: { + menuTitle: 'User History for', + title: "User history", + searchPlaceholder: "Search event...", + oktaFilter: '(eventType sw "user.lifecycle" or eventType sw "user.account") and target.id eq "${userId}"', + backuptaFilterBy: 'component:USERS', + }, + groupObjectHistory: { + menuTitle: 'Group History for', + title: "Group history", + searchPlaceholder: "Search event...", + oktaFilter: 'eventType sw "group." and target.id eq "${groupId}"', + backuptaFilterBy: 'component:GROUPS', + }, + appObjectHistory: { + menuTitle: 'App History for', + title: "App history", + searchPlaceholder: "Search event...", + oktaFilter: '(eventType sw "application.lifecycle" or eventType sw "application.user_membership") and target.id eq "${appId}"', + backuptaFilterBy: 'component:APPS', } }; @@ -130,12 +151,17 @@ directoryPerson(); } else if (location.pathname == "/admin/groups") { directoryGroups(); - } else if (location.pathname == "/admin/access/admins") { + } else if (location.pathname.match("/admin/group/")) { + directoryGroup() + }else if (location.pathname == "/admin/access/admins") { securityAdministrators(); } else if (location.pathname.match("/report/system_log_2")) { systemLog(); - } else if (location.pathname.match("/admin/app/active_directory")) { - activeDirectory(); + } else if (location.pathname.match("/admin/app/")) { + directoryApp(); + if (location.pathname.match("/admin/app/active_directory")) { + activeDirectory(); + } } else if (location.pathname == "/admin/access/identity-providers") { identityProviders(); } @@ -464,6 +490,7 @@ return r.json(); } }); + createDiv('Show User History', mainPopup, () => createObjectHistory('userObjectHistory', user)); } function directoryGroups() { @@ -511,7 +538,29 @@ searcher(object); }); } - + + async function directoryGroup() { + var groupId = location.pathname.split("/")[3]; + var group; + await getJSON(`/api/v1/groups/${groupId}`).then(aGroup => { + group = aGroup; + }); + createDiv("Show Group History", mainPopup, () => createObjectHistory('groupObjectHistory', group)); + } + + async function directoryApp(){ + var appId = location.pathname.split("/")[5]; + console.log(appId); + var app; + await getJSON(`/api/v1/apps/${appId}`).then(anApp => { + app = anApp; + $(".subheader").html(e(app.label)); + document.title += ` - ${e(app.label)}`; + }); + createDiv("Show App History", mainPopup, () => createObjectHistory('appObjectHistory', app)); + + } + function securityAdministrators() { createDiv("Export Administrators", mainPopup, function () { // TODO: consider merging into exportObjects(). Will the Link headers be a problem? const adminsPopup = createPopup("Administrators"); @@ -1157,10 +1206,10 @@ } // Generic function to create a popup with search bar - function createPopupWithSearch(popupTitle, searchPlaceholder) { + function createPopupWithSearch(popupTitle, searchPlaceholder, id) { const logListPopup = createPopup(popupTitle); - logListPopup.parent().attr('id', 'logListPopup'); - const searchInputHTML = ``; + logListPopup.parent().attr('id', id); + const searchInputHTML = ``; logListPopup.prepend(searchInputHTML); return {logListPopup, searchInputHTML}; } @@ -1168,7 +1217,7 @@ // Fetch and display log data using utility functions async function fetchDataAndDisplay(type) { const popupConfig = logListPopups[type]; - const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title, popupConfig.searchPlaceholder); + const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title, popupConfig.searchPlaceholder, 'logListPopup'); displayResultTable(popupConfig, logListPopup, searchInputHTML); const sinceDate = new Date(); @@ -1229,7 +1278,7 @@ open(targetUrl, '_blank'); }; - $('#userSearch').on('keyup', function () { + $('.listSearch').on('keyup', function () { const searchVal = $(this).val().toLowerCase(); $('.data-list-item').each(function () { const displayName = $(this).data('displayname').toLowerCase(); @@ -1253,6 +1302,171 @@ }); } + function createObjectHistory(type, object) { + //Do not show history if a popup for this object is already opened, just place it on top of the others + const popup = $('#' + object.id + '.historyListPopup'); + if (popup.length) { + popup.remove(); + return; + } + switch (type) { + case 'userObjectHistory': + createObjectHistoryForUser(object); + break; + case 'groupObjectHistory': + createObjectHistoryForGroup(object); + break; + case 'appObjectHistory': + createObjectHistoryForApp(object); + break; + } + } + + async function createObjectHistoryForUser(user) { + const {logListPopup, searchInputHTML} = createPopupWithSearch(`User History for ${user.profile.firstName + " " + user.profile.lastName + " (" + user.id + ")"}`, "Event name", `${user.id}`); + logListPopup.parent().attr('class', `historyListPopup`); + const svg = $(`
` + + `` + + `
`); + logListPopup.append(svg); + const sinceDate = new Date(); + sinceDate.setDate(sinceDate.getDate() - 90); + const popupConfig = logListPopups['userObjectHistory']; + let historyTable = displayHistoryResultTable(popupConfig, logListPopup, searchInputHTML, user.id); + logListPopup.find('.refreshHistory').click(async function() { + historyTable.find('tbody').empty(); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${userId}", user.id)}&sortOrder=DESCENDING`, 10, historyTable); + }); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${userId}", user.id)}&sortOrder=DESCENDING`, 10, historyTable); + } + + async function createObjectHistoryForGroup(group) { + const {logListPopup, searchInputHTML} = createPopupWithSearch(`Group History for ${group.profile.name + " (" + group.id + ")"}`, "Event name", `${group.id}`); + logListPopup.parent().attr('class', `historyListPopup`); + const svg = $(`
` + + `` + + `
`); + logListPopup.append(svg); + const sinceDate = new Date(); + sinceDate.setDate(sinceDate.getDate() - 90); + const popupConfig = logListPopups['groupObjectHistory']; + let historyTable = displayHistoryResultTable(logListPopups['groupObjectHistory'], logListPopup, searchInputHTML, group.id); + logListPopup.find('.refreshHistory').click(async function() { + historyTable.find('tbody').empty(); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${groupId}", group.id)}&sortOrder=DESCENDING`, 10, historyTable); + }); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${groupId}", group.id)}&sortOrder=DESCENDING`, 10, historyTable); + } + + async function createObjectHistoryForApp(app) { + const {logListPopup, searchInputHTML} = createPopupWithSearch(`App History for ${app.label + " (" + app.id + ")"}`, "Event name", `${app.id}`); + logListPopup.parent().attr('class', `historyListPopup`); + const svg = $(`
` + + `` + + `
`); + logListPopup.append(svg); + const sinceDate = new Date(); + sinceDate.setDate(sinceDate.getDate() - 90); + const popupConfig = logListPopups['appObjectHistory']; + let historyTable = displayHistoryResultTable(logListPopups['appObjectHistory'], logListPopup, searchInputHTML, app.id); + logListPopup.find('.refreshHistory').click(async function() { + historyTable.find('tbody').empty(); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${appId}", app.id)}&sortOrder=DESCENDING`, 10, historyTable); + }); + await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${appId}", app.id)}&sortOrder=DESCENDING`, 10, historyTable); + } + + + function displayHistoryResultTable(popupConfig, historyListPopup, searchInputHTML, objectId) { + let targetHTML = `` + + "
 Event NameActor Display NameContextDate" + + "
" + + "
Show more
" + + "
"; + historyListPopup.append(targetHTML); + + historyListPopup.find("#btnRestore").click(async function () { + var baseUrl = localStorage.backuptaBaseUrl; + if (!baseUrl) { + await openConfigPopup(true); + return; + } + var targetUrl = `${baseUrl}/${backuptaTenantId}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${objectId}`; + open(targetUrl, '_blank'); + }); + + $(`#${objectId}.listSearch`).on('keyup', function () { + const searchVal = $(this).val().toLowerCase(); + $('.data-list-item').each(function () { + const eventName = $(this).data('eventname').toLowerCase(); + if (eventName.includes(searchVal)) { + $(this).show(); + } else { + $(this).hide(); + } + }); + }); + return $('#' + objectId + '.history-table'); + } + + async function fetchMoreHistory(url, limit, historyTable) { + const response = await fetch(url.replace(/limit=\d+/, `limit=${limit}`), {headers}); + const logs = await response.json(); + if (logs.length === 0 || logs.length < limit) { + historyTable.parent().find('#showMore').hide(); + } else { + historyTable.parent().find('#showMore').show(); + } + const links = getLinks(response.headers.get('Link')); + appendResultsHistory(logs, links, historyTable); + } + + function appendResultsHistory(logs, links, historyTable) { + let targetHTML = ''; + logs.forEach(log => { + let context = ''; + if(log.target.length > 1) { + // For apps events, the actor shows up in targets. So if an app edits another app, it would get added to the actor app history without this. The first target is the modified target + if(log.target[0].id !== historyTable.attr('id')) { + return; + } + for (target of log.target) { + if(target.id === historyTable.attr('id')) + continue; + context += 'target=' + target.displayName + ', '; + } + context = context.slice(0, -2); + } + if(log.debugContext?.debugData?.changedAttributes !== undefined) { + context += 'changedAttributes=' + log.debugContext.debugData.changedAttributes; + } + let svgOutcome = ''; + if(log.outcome.result === "SUCCESS") { + svgOutcome = '\n' + + '\n' + + '\n' + + '' + } else { + svgOutcome = '\n' + + '\n' + + '\n' + + '\n' + + '' + } + targetHTML += ``+ + `${svgOutcome}` + + `${e(log.displayMessage)}` + + `${e(log.actor.displayName)}` + + `${e(context)}` + + `${log.published.substring(0, 19).replace('T', ' ')}`; + }); + const tableBody = historyTable.find('tbody'); + tableBody.append(targetHTML); + const button = historyTable.parent().find('#showMore'); + button.off("click"); + button.on("click", () => fetchMoreHistory(links.next, 100, historyTable)); + } + // API functions function apiExplorer() { createDiv("API Explorer", mainPopup, function () { @@ -1571,7 +1785,7 @@ a[0].click(); } function e(s) { - return s == null ? '' : s.toString().replace(//g, '>'); + return s == null ? '' : s.toString().replace(//g, '>').replace(/'/g, ''').replace(/"/g, '"'); } function dot(o, dots) { var ps = dots.split("."); From 4ba70e3f0176d583efa95daef01399ecd9afe0c6 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 10:19:34 +0200 Subject: [PATCH 02/10] Update changed function names --- rockstar/rockstar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 8d815b1..9da8204 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1274,7 +1274,7 @@ } var items = document.querySelectorAll(".data-list-table.rockstar input[type='checkbox']:checked"); var ids = Array.from(items).map(item => item.id); - var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${ids.join(',')}`; + var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy}&search=${ids.join(',')}`; open(targetUrl, '_blank'); }; @@ -1388,10 +1388,10 @@ historyListPopup.find("#btnRestore").click(async function () { var baseUrl = localStorage.backuptaBaseUrl; if (!baseUrl) { - await openConfigPopup(true); + settings(); return; } - var targetUrl = `${baseUrl}/${backuptaTenantId}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${objectId}`; + var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${objectId}`; open(targetUrl, '_blank'); }); From fd50c0f2d61e2500e895312b1d40b8834b2966b2 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 10:41:57 +0200 Subject: [PATCH 03/10] remove refresh buttons --- rockstar/rockstar.js | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 9da8204..998ae18 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1323,56 +1323,32 @@ } async function createObjectHistoryForUser(user) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`User History for ${user.profile.firstName + " " + user.profile.lastName + " (" + user.id + ")"}`, "Event name", `${user.id}`); + const {logListPopup, searchInputHTML} = createPopupWithSearch(`User History for ${user.profile.firstName + " " + user.profile.lastName + " (" + user.id + ")"}`, "Search event name...", `${user.id}`); logListPopup.parent().attr('class', `historyListPopup`); - const svg = $(``); - logListPopup.append(svg); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); const popupConfig = logListPopups['userObjectHistory']; let historyTable = displayHistoryResultTable(popupConfig, logListPopup, searchInputHTML, user.id); - logListPopup.find('.refreshHistory').click(async function() { - historyTable.find('tbody').empty(); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${userId}", user.id)}&sortOrder=DESCENDING`, 10, historyTable); - }); await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${userId}", user.id)}&sortOrder=DESCENDING`, 10, historyTable); } async function createObjectHistoryForGroup(group) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`Group History for ${group.profile.name + " (" + group.id + ")"}`, "Event name", `${group.id}`); + const {logListPopup, searchInputHTML} = createPopupWithSearch(`Group History for ${group.profile.name + " (" + group.id + ")"}`, "Search event name...", `${group.id}`); logListPopup.parent().attr('class', `historyListPopup`); - const svg = $(``); - logListPopup.append(svg); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); const popupConfig = logListPopups['groupObjectHistory']; let historyTable = displayHistoryResultTable(logListPopups['groupObjectHistory'], logListPopup, searchInputHTML, group.id); - logListPopup.find('.refreshHistory').click(async function() { - historyTable.find('tbody').empty(); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${groupId}", group.id)}&sortOrder=DESCENDING`, 10, historyTable); - }); await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${groupId}", group.id)}&sortOrder=DESCENDING`, 10, historyTable); } async function createObjectHistoryForApp(app) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`App History for ${app.label + " (" + app.id + ")"}`, "Event name", `${app.id}`); + const {logListPopup, searchInputHTML} = createPopupWithSearch(`App History for ${app.label + " (" + app.id + ")"}`, "Search event name...", `${app.id}`); logListPopup.parent().attr('class', `historyListPopup`); - const svg = $(``); - logListPopup.append(svg); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); const popupConfig = logListPopups['appObjectHistory']; let historyTable = displayHistoryResultTable(logListPopups['appObjectHistory'], logListPopup, searchInputHTML, app.id); - logListPopup.find('.refreshHistory').click(async function() { - historyTable.find('tbody').empty(); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${appId}", app.id)}&sortOrder=DESCENDING`, 10, historyTable); - }); await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${appId}", app.id)}&sortOrder=DESCENDING`, 10, historyTable); } From 1b2be510b958ce9363e1563aa1f66cf8df217e3c Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 10:44:38 +0200 Subject: [PATCH 04/10] Revert changes to existing code --- rockstar/rockstar.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 998ae18..4d98b5c 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1206,10 +1206,10 @@ } // Generic function to create a popup with search bar - function createPopupWithSearch(popupTitle, searchPlaceholder, id) { + function createPopupWithSearch(popupTitle, searchPlaceholder) { const logListPopup = createPopup(popupTitle); - logListPopup.parent().attr('id', id); - const searchInputHTML = ``; + logListPopup.parent().attr('id', 'logListPopup'); + const searchInputHTML = ``; logListPopup.prepend(searchInputHTML); return {logListPopup, searchInputHTML}; } @@ -1217,7 +1217,7 @@ // Fetch and display log data using utility functions async function fetchDataAndDisplay(type) { const popupConfig = logListPopups[type]; - const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title, popupConfig.searchPlaceholder, 'logListPopup'); + const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title, popupConfig.searchPlaceholder); displayResultTable(popupConfig, logListPopup, searchInputHTML); const sinceDate = new Date(); @@ -1278,7 +1278,7 @@ open(targetUrl, '_blank'); }; - $('.listSearch').on('keyup', function () { + $('#userSearch').on('keyup', function () { const searchVal = $(this).val().toLowerCase(); $('.data-list-item').each(function () { const displayName = $(this).data('displayname').toLowerCase(); From 820882971419663d964e036dc526ea22019f8c2c Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 11:00:53 +0200 Subject: [PATCH 05/10] More code simplifcation --- rockstar/rockstar.js | 57 ++++++++++---------------------------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 4d98b5c..6ecfb4b 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -117,21 +117,21 @@ menuTitle: 'User History for', title: "User history", searchPlaceholder: "Search event...", - oktaFilter: '(eventType sw "user.lifecycle" or eventType sw "user.account") and target.id eq "${userId}"', + oktaFilter: '(eventType sw "user.lifecycle" or eventType sw "user.account") and target.id eq "${objectId}"', backuptaFilterBy: 'component:USERS', }, groupObjectHistory: { menuTitle: 'Group History for', title: "Group history", searchPlaceholder: "Search event...", - oktaFilter: 'eventType sw "group." and target.id eq "${groupId}"', + oktaFilter: 'eventType sw "group." and target.id eq "${objectId}"', backuptaFilterBy: 'component:GROUPS', }, appObjectHistory: { menuTitle: 'App History for', title: "App history", searchPlaceholder: "Search event...", - oktaFilter: '(eventType sw "application.lifecycle" or eventType sw "application.user_membership") and target.id eq "${appId}"', + oktaFilter: '(eventType sw "application.lifecycle" or eventType sw "application.user_membership") and target.id eq "${objectId}"', backuptaFilterBy: 'component:APPS', } }; @@ -1302,57 +1302,26 @@ }); } - function createObjectHistory(type, object) { - //Do not show history if a popup for this object is already opened, just place it on top of the others - const popup = $('#' + object.id + '.historyListPopup'); - if (popup.length) { - popup.remove(); - return; - } + function getObjectTitle(type, object) { switch (type) { case 'userObjectHistory': - createObjectHistoryForUser(object); - break; + return `${object.profile.firstName + " " + object.profile.lastName + " (" + object.id + ")"}`; case 'groupObjectHistory': - createObjectHistoryForGroup(object); - break; + return `${object.profile.name + " (" + object.id + ")"}`; case 'appObjectHistory': - createObjectHistoryForApp(object); - break; + return `${object.label + " (" + object.id + ")"}`, "Search event name...", `${object.id}`; } } - async function createObjectHistoryForUser(user) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`User History for ${user.profile.firstName + " " + user.profile.lastName + " (" + user.id + ")"}`, "Search event name...", `${user.id}`); - logListPopup.parent().attr('class', `historyListPopup`); - const sinceDate = new Date(); - sinceDate.setDate(sinceDate.getDate() - 90); - const popupConfig = logListPopups['userObjectHistory']; - let historyTable = displayHistoryResultTable(popupConfig, logListPopup, searchInputHTML, user.id); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${userId}", user.id)}&sortOrder=DESCENDING`, 10, historyTable); - } - - async function createObjectHistoryForGroup(group) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`Group History for ${group.profile.name + " (" + group.id + ")"}`, "Search event name...", `${group.id}`); - logListPopup.parent().attr('class', `historyListPopup`); - const sinceDate = new Date(); - sinceDate.setDate(sinceDate.getDate() - 90); - const popupConfig = logListPopups['groupObjectHistory']; - let historyTable = displayHistoryResultTable(logListPopups['groupObjectHistory'], logListPopup, searchInputHTML, group.id); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${groupId}", group.id)}&sortOrder=DESCENDING`, 10, historyTable); - } - - async function createObjectHistoryForApp(app) { - const {logListPopup, searchInputHTML} = createPopupWithSearch(`App History for ${app.label + " (" + app.id + ")"}`, "Search event name...", `${app.id}`); - logListPopup.parent().attr('class', `historyListPopup`); + function createObjectHistory(type, object) { + const popupConfig = logListPopups[type]; + const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title + getObjectTitle(type, object), "Search event name...", `${object.id}`); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); - const popupConfig = logListPopups['appObjectHistory']; - let historyTable = displayHistoryResultTable(logListPopups['appObjectHistory'], logListPopup, searchInputHTML, app.id); - await fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${appId}", app.id)}&sortOrder=DESCENDING`, 10, historyTable); + let historyTable = displayHistoryResultTable(popupConfig, logListPopup, searchInputHTML, object.id); + fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${objectId}", object.id)}&sortOrder=DESCENDING`, 10, historyTable); } - function displayHistoryResultTable(popupConfig, historyListPopup, searchInputHTML, objectId) { let targetHTML = `` + "`+ - `` + + `` + + `` + + `` + + `` + + ``; }); const tableBody = historyTable.find('tbody'); tableBody.append(targetHTML); From b5b8677896347031d10d6ce15afb85fd50b645e8 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Wed, 25 Sep 2024 12:41:05 +0200 Subject: [PATCH 08/10] Format date in locale (with UTC in alt) --- rockstar/rockstar.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index a73ddfd..4e3ce6d 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1226,6 +1226,20 @@ appendResults(logs, links); } + function formatDateLocale(date) { + return new Date(date).toLocaleString(undefined, { + month: 'short', // Short month name + day: 'numeric', // Numeric day without leading zero + hour: '2-digit', // Two-digit hour + minute: '2-digit', // Two-digit minute + second: '2-digit' // Two-digit second + }); + } + + function formatDateUTC(date) { + return new Date(date).toUTCString(); + } + function appendResults(logs, links) { let targetHTML = ''; logs.forEach(log => { @@ -1237,7 +1251,7 @@ ``+ `` + - `` + + `` + `` + `` + `` + From bb202538c6e8125619b0b9b8d4b4d6713031c6b7 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Wed, 25 Sep 2024 13:26:46 +0200 Subject: [PATCH 09/10] Add target type icon --- rockstar/rockstar.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 4e3ce6d..558d94c 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1370,6 +1370,15 @@ appendResultsHistory(logs, links, historyTable); } + const heroIconUsers = ''; + const heroIconSquare2x2 = ''; + const heroIconEllispsiHorizontal = ''; + + const targetTypeIcon = { + UserGroup: heroIconUsers, + AppInstance: heroIconSquare2x2, + } + function appendResultsHistory(logs, links, historyTable) { let targetHTML = ''; logs.forEach(log => { @@ -1377,7 +1386,10 @@ if(log.target[0].id !== historyTable.attr('id')) { return; } - const target = log.target.filter(target => target.id !== historyTable.attr('id')).map(target =>target.displayName).filter(v => !!v).join(', '); + const target = log.target.filter(target => target.id !== historyTable.attr('id')).map(target => { + const icon = targetTypeIcon[target.type] ?? heroIconEllispsiHorizontal; + return `${e(target.displayName)} (${icon})`; + }).filter(v => !!v).join('
'); const changedAttributes = log.debugContext?.debugData?.changedAttributes; let svgOutcome = ''; if(log.outcome.result === "SUCCESS") { @@ -1397,7 +1409,7 @@ `
` + `` + `` + - `` + + `` + ``; }); const tableBody = historyTable.find('tbody'); From 824db4b8cc51c068c5559b26e74b679f92ad5c30 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Wed, 25 Sep 2024 14:35:42 +0200 Subject: [PATCH 10/10] Add missing icons --- rockstar/rockstar.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 558d94c..aa34a5b 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -1370,12 +1370,16 @@ appendResultsHistory(logs, links, historyTable); } + const heroIconUser = ''; const heroIconUsers = ''; const heroIconSquare2x2 = ''; - const heroIconEllispsiHorizontal = ''; + const heroIconUserGroup = ''; + const heroIconCog6Tooth = ''; const targetTypeIcon = { + User: heroIconUser, UserGroup: heroIconUsers, + Group: heroIconUserGroup, AppInstance: heroIconSquare2x2, } @@ -1387,7 +1391,7 @@ return; } const target = log.target.filter(target => target.id !== historyTable.attr('id')).map(target => { - const icon = targetTypeIcon[target.type] ?? heroIconEllispsiHorizontal; + const icon = targetTypeIcon[target.type] ?? heroIconCog6Tooth; return `${e(target.displayName)} (${icon})`; }).filter(v => !!v).join('
'); const changedAttributes = log.debugContext?.debugData?.changedAttributes;
 Event NameActor Display NameContextDate" + @@ -1371,7 +1340,7 @@ open(targetUrl, '_blank'); }); - $(`#${objectId}.listSearch`).on('keyup', function () { + $('#userSearch').on('keyup', function () { const searchVal = $(this).val().toLowerCase(); $('.data-list-item').each(function () { const eventName = $(this).data('eventname').toLowerCase(); From f0b345cb501112b5f0652f171f696279829e819d Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 11:03:29 +0200 Subject: [PATCH 06/10] Minor fixes --- rockstar/rockstar.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index 6ecfb4b..da7643b 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -116,21 +116,21 @@ userObjectHistory: { menuTitle: 'User History for', title: "User history", - searchPlaceholder: "Search event...", + searchPlaceholder: "Search event name...", oktaFilter: '(eventType sw "user.lifecycle" or eventType sw "user.account") and target.id eq "${objectId}"', backuptaFilterBy: 'component:USERS', }, groupObjectHistory: { menuTitle: 'Group History for', title: "Group history", - searchPlaceholder: "Search event...", + searchPlaceholder: "Search event name...", oktaFilter: 'eventType sw "group." and target.id eq "${objectId}"', backuptaFilterBy: 'component:GROUPS', }, appObjectHistory: { menuTitle: 'App History for', title: "App history", - searchPlaceholder: "Search event...", + searchPlaceholder: "Search event name...", oktaFilter: '(eventType sw "application.lifecycle" or eventType sw "application.user_membership") and target.id eq "${objectId}"', backuptaFilterBy: 'component:APPS', } @@ -1274,7 +1274,7 @@ } var items = document.querySelectorAll(".data-list-table.rockstar input[type='checkbox']:checked"); var ids = Array.from(items).map(item => item.id); - var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy}&search=${ids.join(',')}`; + var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${ids.join(',')}`; open(targetUrl, '_blank'); }; @@ -1309,20 +1309,20 @@ case 'groupObjectHistory': return `${object.profile.name + " (" + object.id + ")"}`; case 'appObjectHistory': - return `${object.label + " (" + object.id + ")"}`, "Search event name...", `${object.id}`; + return `${object.label + " (" + object.id + ")"}`; } } function createObjectHistory(type, object) { const popupConfig = logListPopups[type]; - const {logListPopup, searchInputHTML} = createPopupWithSearch(popupConfig.title + getObjectTitle(type, object), "Search event name...", `${object.id}`); + const {logListPopup} = createPopupWithSearch(popupConfig.title + getObjectTitle(type, object), popupConfig.searchPlaceholder, `${object.id}`); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); - let historyTable = displayHistoryResultTable(popupConfig, logListPopup, searchInputHTML, object.id); + let historyTable = displayHistoryResultTable(popupConfig, logListPopup, object.id); fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${objectId}", object.id)}&sortOrder=DESCENDING`, 10, historyTable); } - function displayHistoryResultTable(popupConfig, historyListPopup, searchInputHTML, objectId) { + function displayHistoryResultTable(popupConfig, historyListPopup, objectId) { let targetHTML = `` + "
 Event NameActor Display NameContextDate" + "
" + @@ -1330,7 +1330,7 @@ "
"; historyListPopup.append(targetHTML); - historyListPopup.find("#btnRestore").click(async function () { + historyListPopup.find("#btnRestore").click(function () { var baseUrl = localStorage.backuptaBaseUrl; if (!baseUrl) { settings(); From de4dca32fdd081e6ecd38ec5b727ef9ad0ce1d53 Mon Sep 17 00:00:00 2001 From: Remi Koenig Date: Fri, 20 Sep 2024 11:38:00 +0200 Subject: [PATCH 07/10] Minor updates and code simplification --- rockstar/rockstar.js | 75 +++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js index da7643b..a73ddfd 100644 --- a/rockstar/rockstar.js +++ b/rockstar/rockstar.js @@ -114,22 +114,22 @@ backuptaFilterBy: 'type:DELETE;component:EVENT_HOOKS' }, userObjectHistory: { - menuTitle: 'User History for', - title: "User history", + menuTitle: 'Show User History', + title: "User history for", searchPlaceholder: "Search event name...", oktaFilter: '(eventType sw "user.lifecycle" or eventType sw "user.account") and target.id eq "${objectId}"', backuptaFilterBy: 'component:USERS', }, groupObjectHistory: { - menuTitle: 'Group History for', - title: "Group history", + menuTitle: 'Show Group History', + title: "Group history for", searchPlaceholder: "Search event name...", oktaFilter: 'eventType sw "group." and target.id eq "${objectId}"', backuptaFilterBy: 'component:GROUPS', }, appObjectHistory: { - menuTitle: 'App History for', - title: "App history", + menuTitle: 'Show App History', + title: "App history for", searchPlaceholder: "Search event name...", oktaFilter: '(eventType sw "application.lifecycle" or eventType sw "application.user_membership") and target.id eq "${objectId}"', backuptaFilterBy: 'component:APPS', @@ -152,13 +152,13 @@ } else if (location.pathname == "/admin/groups") { directoryGroups(); } else if (location.pathname.match("/admin/group/")) { - directoryGroup() + groupHistory() }else if (location.pathname == "/admin/access/admins") { securityAdministrators(); } else if (location.pathname.match("/report/system_log_2")) { systemLog(); } else if (location.pathname.match("/admin/app/")) { - directoryApp(); + appHistory(); if (location.pathname.match("/admin/app/active_directory")) { activeDirectory(); } @@ -490,7 +490,7 @@ return r.json(); } }); - createDiv('Show User History', mainPopup, () => createObjectHistory('userObjectHistory', user)); + createDiv(logListPopups.userObjectHistory.menuTitle, mainPopup, () => createObjectHistory('userObjectHistory', user)); } function directoryGroups() { @@ -539,26 +539,16 @@ }); } - async function directoryGroup() { + async function groupHistory() { var groupId = location.pathname.split("/")[3]; - var group; - await getJSON(`/api/v1/groups/${groupId}`).then(aGroup => { - group = aGroup; - }); - createDiv("Show Group History", mainPopup, () => createObjectHistory('groupObjectHistory', group)); + const group = await getJSON(`/api/v1/groups/${groupId}`); + createDiv(logListPopups.groupObjectHistory.menuTitle, mainPopup, () => createObjectHistory('groupObjectHistory', group)); } - async function directoryApp(){ + async function appHistory(){ var appId = location.pathname.split("/")[5]; - console.log(appId); - var app; - await getJSON(`/api/v1/apps/${appId}`).then(anApp => { - app = anApp; - $(".subheader").html(e(app.label)); - document.title += ` - ${e(app.label)}`; - }); - createDiv("Show App History", mainPopup, () => createObjectHistory('appObjectHistory', app)); - + const app = await getJSON(`/api/v1/apps/${appId}`); + createDiv(logListPopups.appObjectHistory.menuTitle, mainPopup, () => createObjectHistory('appObjectHistory', app)); } function securityAdministrators() { @@ -1315,7 +1305,7 @@ function createObjectHistory(type, object) { const popupConfig = logListPopups[type]; - const {logListPopup} = createPopupWithSearch(popupConfig.title + getObjectTitle(type, object), popupConfig.searchPlaceholder, `${object.id}`); + const {logListPopup} = createPopupWithSearch(`${popupConfig.title} ${getObjectTitle(type, object)}`, popupConfig.searchPlaceholder, `${object.id}`); const sinceDate = new Date(); sinceDate.setDate(sinceDate.getDate() - 90); let historyTable = displayHistoryResultTable(popupConfig, logListPopup, object.id); @@ -1324,7 +1314,7 @@ function displayHistoryResultTable(popupConfig, historyListPopup, objectId) { let targetHTML = `` + - "" + "
 Event NameActor Display NameContextDate" + + "
 Event dateEvent nameActorTarget(s)Changed attributes
" + "" + "
"; @@ -1369,22 +1359,12 @@ function appendResultsHistory(logs, links, historyTable) { let targetHTML = ''; logs.forEach(log => { - let context = ''; - if(log.target.length > 1) { - // For apps events, the actor shows up in targets. So if an app edits another app, it would get added to the actor app history without this. The first target is the modified target - if(log.target[0].id !== historyTable.attr('id')) { - return; - } - for (target of log.target) { - if(target.id === historyTable.attr('id')) - continue; - context += 'target=' + target.displayName + ', '; - } - context = context.slice(0, -2); - } - if(log.debugContext?.debugData?.changedAttributes !== undefined) { - context += 'changedAttributes=' + log.debugContext.debugData.changedAttributes; + // For apps events, the actor shows up in targets. So if an app edits another app, it would get added to the actor app history without this. The first target is the modified target + if(log.target[0].id !== historyTable.attr('id')) { + return; } + const target = log.target.filter(target => target.id !== historyTable.attr('id')).map(target =>target.displayName).filter(v => !!v).join(', '); + const changedAttributes = log.debugContext?.debugData?.changedAttributes; let svgOutcome = ''; if(log.outcome.result === "SUCCESS") { svgOutcome = '\n' + @@ -1399,11 +1379,12 @@ '' } targetHTML += `
${svgOutcome}` + - `${e(log.displayMessage)}` + - `${e(log.actor.displayName)}` + - `${e(context)}` + - `${log.published.substring(0, 19).replace('T', ' ')}`; + `${svgOutcome}${log.published.substring(0, 19).replace('T', ' ')}${e(log.displayMessage)}${e(log.actor.displayName)}${e(target)}${e(changedAttributes)}${e(target.id)}` + `${e(target.type)}` + `${e(log.actor.displayName)}` + - `${log.published.substring(0, 19).replace('T', ' ')}`; + `${formatDateLocale(log.published)}`; }); } }); @@ -1380,7 +1394,7 @@ } targetHTML += `
${svgOutcome}${log.published.substring(0, 19).replace('T', ' ')}${formatDateLocale(log.published)}${e(log.displayMessage)}${e(log.actor.displayName)}${e(target)}${formatDateLocale(log.published)}${e(log.displayMessage)}${e(log.actor.displayName)}${e(target)}${target}${e(changedAttributes)}