-
Notifications
You must be signed in to change notification settings - Fork 38
/
CloneAppPolicies.html
213 lines (198 loc) · 9.15 KB
/
CloneAppPolicies.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<!doctype html>
<html>
<head>
<title>Clone App Rules (old)</title>
<style>
body {
font-family: sans-serif;
}
</style>
</head>
<body>
<h1>Clone App Rules (old)</h1>
<p>This tool will clone rules from a "source" app to one or more "destination" apps. <a href=CloneAppRules.html>new</a></p>
<b>Setup</b>
<ol>
<li>Show your bookmarks toolbar. In Chrome, … > Bookmarks > Show Bookmarks Bar. In Firefox, right-click in the title bar and click Bookmarks Toolbar.
<li>Drag/drop this <a id=bm>Clone App Rules v0.2</a> to the bookmarks toolbar.
</ol>
<br>
<b>Usage</b>
<ol>
<li>In your Okta org, pick one app as your "source".
<li>Create one or more rules in it -- it won't clone the "Catch-all Rule".
<li>Click the "Clone App Rules" button from your bookmarks toolbar.
<li>To use an app CSV:
<ol>
<li>Click "Export Apps CSV" to export all apps.
<li>Edit the CSV so only your desired "destination" apps remain (i.e., delete any apps you don't want to clone to).
<li>Click "Import Apps CSV" to clone the rules.
</ol>
<li>Or, to use the UI, select 1 or more "destination" apps and click "Clone".
<li>It will generate a "cloned rules" CSV for your records, or if you need to rollback.
<li>If necessary, rollback using the "Rollback Rules" button to import the "cloned rules" CSV.
</ol>
<textarea id=code hidden>
javascript:
(async function () {
if (!window.$) {
alert('Drag/drop this link to your bookmarks toolbar.');
return;
}
/* Main popup. */
const popup = createPopup('Clone Application Rules');
const form = $('<form>Applications<div class=results>Loading...</div><br> ' +
'<input type=submit disabled value=Clone> ' +
'<input type=button class=checkAll value="Check All"> <input type=button class=uncheckAll value="Uncheck All"> ' +
'<input type=button class=exportAppsCSV value="Export Apps CSV">' +
'<br><br><label>Import Apps CSV<br><input type=file class=importApps></label>' +
'<br><label>Rollback Rules<br><input type=file class=rollbackRules></label>' +
'<br><br><div class=cloned></div></form>').appendTo(popup);
/* Import app CSV and clone rules. */
form.find('input.importApps').change(function () {
const reader = new FileReader();
reader.onload = () => parseAppFile(reader.result);
if (this.files.length > 0) reader.readAsText(this.files[0]);
});
async function parseAppFile(file) {
const lineSeparator = /\r\n|\r|\n/;
const fieldSeparator = ",";
const lines = file.split(lineSeparator);
if (lines[lines.length - 1] == '') lines.pop();
const fields = lines.shift().split(fieldSeparator);
const headers = {};
fields.forEach((val, i) => headers[val] = i); /* Map header name to number. */
/* TODO: merge with Clone Rules code below */
form.find('div.cloned').html('Cloned:<br>');
const cloned = [];
const csv = [];
for (const line of lines) {
const fields = line.replace(/"/g, '').split(fieldSeparator); /* TODO: improve support for "" */
const policyId = fields[headers.id];
for (const rule of rules) {
const clonedRule = await postJSON({url: `/api/v1/policies/${policyId}/rules`, data: rule});
csv.push(toCSV(policyId, clonedRule.id, fields[headers.name], rule.name));
cloned.push(fields[headers.name] + ', ' + rule.name);
}
}
form.find('div.cloned').html(form.find('div.cloned').html() + cloned.sort().join('<br>'));
downloadCSV(popup, 'policyId,ruleId,appName,ruleName', csv, 'cloned rules');
}
/* Rollback. */
form.find('input.rollbackRules').change(function () {
const reader = new FileReader();
reader.onload = () => parseRuleFile(reader.result);
if (this.files.length > 0) reader.readAsText(this.files[0]);
});
async function parseRuleFile(file) {
const lineSeparator = /\r\n|\r|\n/;
const fieldSeparator = ",";
const lines = file.split(lineSeparator);
if (lines[lines.length - 1] == '') lines.pop();
const fields = lines.shift().split(fieldSeparator);
const headers = {};
fields.forEach((val, i) => headers[val] = i); /* Map header name to number. */
form.find('div.cloned').html('Rolled back:<br>');
const rolled = [];
for (const line of lines) {
const fields = line.replace(/"/g, '').split(fieldSeparator); /* TODO: improve support for "" */
const policyId = fields[headers.policyId];
const ruleId = fields[headers.ruleId];
await $.ajax({url: `/api/v1/policies/${policyId}/rules/${ruleId}`, method: 'delete'});
rolled.push(fields[headers.appName] + ', ' + fields[headers.ruleName]);
}
form.find('div.cloned').html(form.find('div.cloned').html() + rolled.sort().join('<br>'));
}
/* Find policies and apps, show checkboxes. */
const appId = getAppId();
if (!appId) {
alert('Please pick an app first.');
form.find('div.results').html('<br>Please pick an app first.');
return;
}
var policyId;
const found = [];
const csv = [];
const policies = await $.getJSON('/api/v1/policies?type=Okta:SignOn');
for (const p of policies) {
const app = await $.getJSON(`/api/v1/policies/${p.id}/app`);
if (app.length) {
if (app[0].id == appId) {
policyId = p.id;
} else {
const sortBy = `<!--${p.name}-->`;
found.push(`${sortBy}<label><input type=checkbox value='${p.id}' checked>${p.name}</label>`);
}
csv.push(toCSV(p.id, p.name));
/* if (csv.length == 6) break; */ /* DEBUG CODE */
form.find('div.results').html(`Loading app ${csv.length + 1}...`);
}
}
form.find('input.exportAppsCSV').click(() => downloadCSV(popup, 'id,name', csv, 'apps'));
const results = found.length > 0 ? found.sort().join('<br>') : 'Not found';
form.find('div.results').html(results);
form.find('input[type=submit]').prop('disabled', false);
/* Fetch and edit rules. */
const rules = await $.getJSON(`/api/v1/policies/${policyId}/rules?type=Okta:SignOn`);
rules.pop(); /* Don't clone the "Catch-all Rule". */
rules.forEach(rule => {
rule.name += ' (clone)';
delete rule.id;
delete rule.resourceDisplayName;
delete rule._links;
});
/* Clone rules (from Clone button). */
form.submit(async event => {
event.preventDefault();
form.find('div.cloned').html('Cloned:<br>');
const checked = form.find('input:checked');
const cloned = [];
const csv = [];
for (const chk of checked) {
for (const rule of rules) {
const clonedRule = await postJSON({url: `/api/v1/policies/${chk.value}/rules`, data: rule});
cloned.push(chk.parentNode.textContent + ', ' + rule.name);
csv.push(toCSV(chk.value, clonedRule.id, chk.parentNode.textContent, rule.name));
}
}
form.find('div.cloned').html(form.find('div.cloned').html() + cloned.sort().join('<br>'));
downloadCSV(popup, 'policyId,ruleId,appName,ruleName', csv, 'cloned rules');
});
form.find('input.checkAll').click(() => form.find('input[type="checkbox"]').prop('checked', true));
form.find('input.uncheckAll').click(() => form.find('input[type="checkbox"]').prop('checked', false));
function createPopup(title) {
const popup = $(`<div style='position: absolute; z-index: 1000; top: 0px; max-height: calc(100% - 28px); max-width: calc(100% - 28px); padding: 8px; margin: 4px; overflow: auto; ` +
`background-color: white; border: 1px solid #ddd;'>` +
`${title}<div style='display: block; float: right;'><a href='https://gabrielsroka.github.io/CloneAppPolicies.html' target='_blank' rel='noopener' style='padding: 4px'>?</a> ` +
`<a onclick='document.body.removeChild(this.parentNode.parentNode)' style='cursor: pointer; padding: 4px'>X</a></div><br><br></div>`).appendTo(document.body);
return $('<div></div>').appendTo(popup);
}
function toCSV(...fields) {
return fields.map(field => `"${field == undefined ? "" : field.toString().replace(/"/g, '""')}"`).join(',');
}
function downloadCSV(popup, header, lines, filename) {
var a = $("<a>").appendTo(popup);
a.attr("href", URL.createObjectURL(new Blob([header + "\n" + lines.join("\n")], {type: 'text/csv'})));
var date = (new Date()).toISOString().replace(/T/, " ").replace(/:/g, "-").slice(0, 19);
a.attr("download", `${filename} ${date}.csv`);
a[0].click();
}
function getAppId() {
const path = location.pathname;
const pathparts = path.split('/');
if (path.match("admin/app") && (pathparts.length == 6 || pathparts.length == 7)) {
return pathparts[5];
}
}
function postJSON(settings) {
settings.contentType = "application/json";
settings.data = JSON.stringify(settings.data);
return $.post(settings);
}
})();
</textarea>
<script>
bm.href = code.value;
</script>
</body>
</html>