diff --git a/rockstar/rockstar.js b/rockstar/rockstar.js
index c7f5868..aa34a5b 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: '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: '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: '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',
}
};
@@ -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/")) {
+ 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/active_directory")) {
- activeDirectory();
+ } else if (location.pathname.match("/admin/app/")) {
+ appHistory();
+ 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(logListPopups.userObjectHistory.menuTitle, mainPopup, () => createObjectHistory('userObjectHistory', user));
}
function directoryGroups() {
@@ -511,7 +538,19 @@
searcher(object);
});
}
-
+
+ async function groupHistory() {
+ var groupId = location.pathname.split("/")[3];
+ const group = await getJSON(`/api/v1/groups/${groupId}`);
+ createDiv(logListPopups.groupObjectHistory.menuTitle, mainPopup, () => createObjectHistory('groupObjectHistory', group));
+ }
+
+ async function appHistory(){
+ var appId = location.pathname.split("/")[5];
+ const app = await getJSON(`/api/v1/apps/${appId}`);
+ createDiv(logListPopups.appObjectHistory.menuTitle, 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");
@@ -1187,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 => {
@@ -1198,7 +1251,7 @@
`
${e(target.id)}` +
` | ${e(target.type)}` +
` | ${e(log.actor.displayName)}` +
- ` | ${log.published.substring(0, 19).replace('T', ' ')}`;
+ ` | ${formatDateLocale(log.published)}`;
});
}
});
@@ -1253,6 +1306,123 @@
});
}
+ function getObjectTitle(type, object) {
+ switch (type) {
+ case 'userObjectHistory':
+ return `${object.profile.firstName + " " + object.profile.lastName + " (" + object.id + ")"}`;
+ case 'groupObjectHistory':
+ return `${object.profile.name + " (" + object.id + ")"}`;
+ case 'appObjectHistory':
+ return `${object.label + " (" + object.id + ")"}`;
+ }
+ }
+
+ function createObjectHistory(type, object) {
+ const popupConfig = logListPopups[type];
+ 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);
+ fetchMoreHistory(`/api/v1/logs?since=${sinceDate.toISOString()}&limit=10&filter=${popupConfig.oktaFilter.replaceAll("${objectId}", object.id)}&sortOrder=DESCENDING`, 10, historyTable);
+ }
+
+ function displayHistoryResultTable(popupConfig, historyListPopup, objectId) {
+ let targetHTML = `` +
+ "  | Event date | Event name | Actor | Target(s) | Changed attributes | " +
+ "
---|
" +
+ "" +
+ "";
+ historyListPopup.append(targetHTML);
+
+ historyListPopup.find("#btnRestore").click(function () {
+ var baseUrl = localStorage.backuptaBaseUrl;
+ if (!baseUrl) {
+ settings();
+ return;
+ }
+ var targetUrl = `${baseUrl}/${getBackuptaTenantId()}/changes?filter_by=${popupConfig.backuptaFilterBy};id:${objectId}`;
+ open(targetUrl, '_blank');
+ });
+
+ $('#userSearch').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);
+ }
+
+ const heroIconUser = '';
+ const heroIconUsers = '';
+ const heroIconSquare2x2 = '';
+ const heroIconUserGroup = '';
+ const heroIconCog6Tooth = '';
+
+ const targetTypeIcon = {
+ User: heroIconUser,
+ UserGroup: heroIconUsers,
+ Group: heroIconUserGroup,
+ AppInstance: heroIconSquare2x2,
+ }
+
+ function appendResultsHistory(logs, links, historyTable) {
+ let targetHTML = '';
+ logs.forEach(log => {
+ // 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 => {
+ const icon = targetTypeIcon[target.type] ?? heroIconCog6Tooth;
+ return `${e(target.displayName)} (${icon})`;
+ }).filter(v => !!v).join(' ');
+ const changedAttributes = log.debugContext?.debugData?.changedAttributes;
+ let svgOutcome = '';
+ if(log.outcome.result === "SUCCESS") {
+ svgOutcome = ''
+ } else {
+ svgOutcome = ''
+ }
+ targetHTML += ` | `+
+ `${svgOutcome} | ` +
+ `${formatDateLocale(log.published)} | ` +
+ `${e(log.displayMessage)} | ` +
+ `${e(log.actor.displayName)} | ` +
+ `${target} | ` +
+ `${e(changedAttributes)} | `;
+ });
+ 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 +1741,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(".");