@rockcarver/frodo-lib 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +32 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/README.md +121 -0
- package/.github/workflows/pipeline.yml +287 -0
- package/.prettierrc +6 -0
- package/CHANGELOG.md +512 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +21 -0
- package/README.md +8 -0
- package/docs/CONTRIBUTE.md +96 -0
- package/docs/PIPELINE.md +169 -0
- package/docs/images/npm_versioning_guidelines.png +0 -0
- package/docs/images/release_pipeline.png +0 -0
- package/jsconfig.json +6 -0
- package/package.json +95 -0
- package/resources/sampleEntitiesFile.json +8 -0
- package/resources/sampleEnvFile.env +2 -0
- package/src/api/AuthenticateApi.js +33 -0
- package/src/api/BaseApi.js +242 -0
- package/src/api/CirclesOfTrustApi.js +87 -0
- package/src/api/EmailTemplateApi.js +37 -0
- package/src/api/IdmConfigApi.js +88 -0
- package/src/api/LogApi.js +45 -0
- package/src/api/ManagedObjectApi.js +62 -0
- package/src/api/OAuth2ClientApi.js +69 -0
- package/src/api/OAuth2OIDCApi.js +73 -0
- package/src/api/OAuth2ProviderApi.js +32 -0
- package/src/api/RealmApi.js +99 -0
- package/src/api/Saml2Api.js +176 -0
- package/src/api/ScriptApi.js +84 -0
- package/src/api/SecretsApi.js +151 -0
- package/src/api/ServerInfoApi.js +41 -0
- package/src/api/SocialIdentityProvidersApi.js +114 -0
- package/src/api/StartupApi.js +45 -0
- package/src/api/ThemeApi.js +181 -0
- package/src/api/TreeApi.js +207 -0
- package/src/api/VariablesApi.js +104 -0
- package/src/api/utils/ApiUtils.js +77 -0
- package/src/api/utils/ApiUtils.test.js +96 -0
- package/src/api/utils/Base64.js +62 -0
- package/src/index.js +32 -0
- package/src/index.test.js +13 -0
- package/src/ops/AdminOps.js +901 -0
- package/src/ops/AuthenticateOps.js +342 -0
- package/src/ops/CirclesOfTrustOps.js +350 -0
- package/src/ops/ConnectionProfileOps.js +254 -0
- package/src/ops/EmailTemplateOps.js +326 -0
- package/src/ops/IdmOps.js +227 -0
- package/src/ops/IdpOps.js +342 -0
- package/src/ops/JourneyOps.js +2026 -0
- package/src/ops/LogOps.js +357 -0
- package/src/ops/ManagedObjectOps.js +34 -0
- package/src/ops/OAuth2ClientOps.js +151 -0
- package/src/ops/OrganizationOps.js +85 -0
- package/src/ops/RealmOps.js +139 -0
- package/src/ops/SamlOps.js +541 -0
- package/src/ops/ScriptOps.js +211 -0
- package/src/ops/SecretsOps.js +288 -0
- package/src/ops/StartupOps.js +114 -0
- package/src/ops/ThemeOps.js +379 -0
- package/src/ops/VariablesOps.js +185 -0
- package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
- package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
- package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
- package/src/ops/templates/cloud/managed.json +4119 -0
- package/src/ops/utils/Console.js +434 -0
- package/src/ops/utils/DataProtection.js +92 -0
- package/src/ops/utils/DataProtection.test.js +28 -0
- package/src/ops/utils/ExportImportUtils.js +146 -0
- package/src/ops/utils/ExportImportUtils.test.js +119 -0
- package/src/ops/utils/OpsUtils.js +76 -0
- package/src/ops/utils/Wordwrap.js +11 -0
- package/src/storage/SessionStorage.js +45 -0
- package/src/storage/StaticStorage.js +15 -0
- package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/baseline/Login.journey.json +205 -0
- package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/baseline/Registration.journey.json +249 -0
- package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
- package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
- package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/delete/Login.journey.json +205 -0
- package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/delete/Registration.journey.json +249 -0
- package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
- package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
- package/test/e2e/journey/list/Disabled.journey.json +43 -0
- package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/list/Login.journey.json +205 -0
- package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/list/Registration.journey.json +249 -0
- package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
- package/test/e2e/setup.js +107 -0
- package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
- package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
- package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
- package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
- package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
- package/test/e2e/theme/import/Contrast.theme.json +95 -0
- package/test/e2e/theme/import/Highlander.theme.json +95 -0
- package/test/e2e/theme/import/Robroy.theme.json +95 -0
- package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
- package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
- package/test/fs_tmp/.gitkeep +2 -0
- package/test/global/setup.js +65 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createKeyValueTable,
|
|
3
|
+
createTable,
|
|
4
|
+
printMessage,
|
|
5
|
+
} from './utils/Console.js';
|
|
6
|
+
import { getRealmByName, getRealms, putRealm } from '../api/RealmApi.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* List realms
|
|
10
|
+
* @param {boolean} long Long list format with details
|
|
11
|
+
*/
|
|
12
|
+
export async function listRealms(long = false) {
|
|
13
|
+
try {
|
|
14
|
+
const realms = (await getRealms()).data.result;
|
|
15
|
+
if (long) {
|
|
16
|
+
const table = createTable([
|
|
17
|
+
'Name'.brightCyan,
|
|
18
|
+
'Status'.brightCyan,
|
|
19
|
+
'Custom Domains'.brightCyan,
|
|
20
|
+
'Parent'.brightCyan,
|
|
21
|
+
]);
|
|
22
|
+
realms.forEach((realmConfig) => {
|
|
23
|
+
table.push([
|
|
24
|
+
realmConfig.name,
|
|
25
|
+
realmConfig.active ? 'active'.brightGreen : 'inactive'.brightRed,
|
|
26
|
+
realmConfig.aliases.join('\n'),
|
|
27
|
+
realmConfig.parentPath,
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
printMessage(table.toString());
|
|
31
|
+
} else {
|
|
32
|
+
realms.forEach((realmConfig) => {
|
|
33
|
+
printMessage(realmConfig.name, 'info');
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
printMessage(`Error listing realms: ${error.rmessage}`, 'error');
|
|
38
|
+
printMessage(error.response.data, 'error');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Describe realm
|
|
44
|
+
* @param {String} realm realm name
|
|
45
|
+
*/
|
|
46
|
+
export async function describe(realm) {
|
|
47
|
+
try {
|
|
48
|
+
const realmConfig = await getRealmByName(realm);
|
|
49
|
+
const table = createKeyValueTable();
|
|
50
|
+
table.push(['Name'.brightCyan, realmConfig.name]);
|
|
51
|
+
table.push([
|
|
52
|
+
'Status'.brightCyan,
|
|
53
|
+
realmConfig.active ? 'active'.brightGreen : 'inactive'.brightRed,
|
|
54
|
+
]);
|
|
55
|
+
table.push(['Custom Domains'.brightCyan, realmConfig.aliases.join('\n')]);
|
|
56
|
+
table.push(['Parent'.brightCyan, realmConfig.parentPath]);
|
|
57
|
+
table.push(['Id'.brightCyan, realmConfig._id]);
|
|
58
|
+
printMessage(table.toString());
|
|
59
|
+
} catch (error) {
|
|
60
|
+
printMessage(`Realm ${realm} not found!`, 'error');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add custom DNS domain name (realm DNS alias)
|
|
66
|
+
* @param {String} realm realm name
|
|
67
|
+
* @param {String} domain domain name
|
|
68
|
+
*/
|
|
69
|
+
export async function addCustomDomain(realm, domain) {
|
|
70
|
+
try {
|
|
71
|
+
let realmConfig = await getRealmByName(realm);
|
|
72
|
+
let exists = false;
|
|
73
|
+
realmConfig.aliases.forEach((alias) => {
|
|
74
|
+
if (domain.toLowerCase() === alias.toLowerCase()) {
|
|
75
|
+
exists = true;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (!exists) {
|
|
79
|
+
try {
|
|
80
|
+
realmConfig.aliases.push(domain.toLowerCase());
|
|
81
|
+
realmConfig = (await putRealm(realmConfig._id, realmConfig)).data;
|
|
82
|
+
const table = createKeyValueTable();
|
|
83
|
+
table.push(['Name'.brightCyan, realmConfig.name]);
|
|
84
|
+
table.push([
|
|
85
|
+
'Status'.brightCyan,
|
|
86
|
+
realmConfig.active ? 'active'.brightGreen : 'inactive'.brightRed,
|
|
87
|
+
]);
|
|
88
|
+
table.push([
|
|
89
|
+
'Custom Domains'.brightCyan,
|
|
90
|
+
realmConfig.aliases.join('\n'),
|
|
91
|
+
]);
|
|
92
|
+
table.push(['Parent'.brightCyan, realmConfig.parentPath]);
|
|
93
|
+
table.push(['Id'.brightCyan, realmConfig._id]);
|
|
94
|
+
printMessage(table.toString());
|
|
95
|
+
} catch (error) {
|
|
96
|
+
printMessage(`Error adding custom domain: ${error.message}`, 'error');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
printMessage(`${error.message}`, 'error');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Remove custom DNS domain name (realm DNS alias)
|
|
106
|
+
* @param {String} realm realm name
|
|
107
|
+
* @param {String} domain domain name
|
|
108
|
+
*/
|
|
109
|
+
export async function removeCustomDomain(realm, domain) {
|
|
110
|
+
try {
|
|
111
|
+
let realmConfig = await getRealmByName(realm);
|
|
112
|
+
const aliases = realmConfig.aliases.filter(
|
|
113
|
+
(alias) => domain.toLowerCase() !== alias.toLowerCase()
|
|
114
|
+
);
|
|
115
|
+
if (aliases.length < realmConfig.aliases.length) {
|
|
116
|
+
try {
|
|
117
|
+
realmConfig.aliases = aliases;
|
|
118
|
+
realmConfig = (await putRealm(realmConfig._id, realmConfig)).data;
|
|
119
|
+
const table = createKeyValueTable();
|
|
120
|
+
table.push(['Name'.brightCyan, realmConfig.name]);
|
|
121
|
+
table.push([
|
|
122
|
+
'Status'.brightCyan,
|
|
123
|
+
realmConfig.active ? 'active'.brightGreen : 'inactive'.brightRed,
|
|
124
|
+
]);
|
|
125
|
+
table.push([
|
|
126
|
+
'Custom Domains'.brightCyan,
|
|
127
|
+
realmConfig.aliases.join('\n'),
|
|
128
|
+
]);
|
|
129
|
+
table.push(['Parent'.brightCyan, realmConfig.parentPath]);
|
|
130
|
+
table.push(['Id'.brightCyan, realmConfig._id]);
|
|
131
|
+
printMessage(table.toString());
|
|
132
|
+
} catch (error) {
|
|
133
|
+
printMessage(`Error removing custom domain: ${error.message}`, 'error');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
printMessage(`${error.message}`, 'error');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { decode, encode, encodeBase64Url } from '../api/utils/Base64.js';
|
|
4
|
+
import {
|
|
5
|
+
createTable,
|
|
6
|
+
printMessage,
|
|
7
|
+
createProgressBar,
|
|
8
|
+
updateProgressBar,
|
|
9
|
+
stopProgressBar,
|
|
10
|
+
createObjectTable,
|
|
11
|
+
} from './utils/Console.js';
|
|
12
|
+
import {
|
|
13
|
+
getProviders,
|
|
14
|
+
findProviders,
|
|
15
|
+
getProviderByLocationAndId,
|
|
16
|
+
getProviderMetadata,
|
|
17
|
+
createProvider,
|
|
18
|
+
getProviderMetadataUrl,
|
|
19
|
+
} from '../api/Saml2Api.js';
|
|
20
|
+
import { getScript } from '../api/ScriptApi.js';
|
|
21
|
+
import {
|
|
22
|
+
convertBase64TextToArray,
|
|
23
|
+
convertBase64UrlTextToArray,
|
|
24
|
+
convertTextArrayToBase64,
|
|
25
|
+
convertTextArrayToBase64Url,
|
|
26
|
+
getRealmString,
|
|
27
|
+
getTypedFilename,
|
|
28
|
+
saveJsonToFile,
|
|
29
|
+
saveTextToFile,
|
|
30
|
+
validateImport,
|
|
31
|
+
} from './utils/ExportImportUtils.js';
|
|
32
|
+
import { createOrUpdateScript } from './ScriptOps.js';
|
|
33
|
+
|
|
34
|
+
const roleMap = {
|
|
35
|
+
identityProvider: 'IDP',
|
|
36
|
+
serviceProvider: 'SP',
|
|
37
|
+
attributeQueryProvider: 'AttrQuery',
|
|
38
|
+
xacmlPolicyEnforcementPoint: 'XACML PEP',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// use a function vs a template variable to avoid problems in loops
|
|
42
|
+
function getFileDataTemplate() {
|
|
43
|
+
return {
|
|
44
|
+
meta: {},
|
|
45
|
+
script: {},
|
|
46
|
+
saml: {
|
|
47
|
+
hosted: {},
|
|
48
|
+
remote: {},
|
|
49
|
+
metadata: {},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* List entity providers
|
|
56
|
+
* @param {boolean} long Long list format with details
|
|
57
|
+
*/
|
|
58
|
+
export async function listProviders(long = false) {
|
|
59
|
+
const providerList = (await getProviders()).data.result;
|
|
60
|
+
providerList.sort((a, b) => a._id.localeCompare(b._id));
|
|
61
|
+
if (!long) {
|
|
62
|
+
providerList.forEach((item) => {
|
|
63
|
+
printMessage(`${item.entityId}`, 'data');
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
const table = createTable([
|
|
67
|
+
'Entity Id'.brightCyan,
|
|
68
|
+
'Location'.brightCyan,
|
|
69
|
+
'Role(s)'.brightCyan,
|
|
70
|
+
]);
|
|
71
|
+
providerList.forEach((provider) => {
|
|
72
|
+
table.push([
|
|
73
|
+
provider.entityId,
|
|
74
|
+
provider.location,
|
|
75
|
+
provider.roles.map((role) => roleMap[role]).join(', '),
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
printMessage(table.toString());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Include dependencies in the export file
|
|
84
|
+
* @param {Object} providerData Object representing a SAML entity provider
|
|
85
|
+
* @param {Object} fileData File data object to add dependencies to
|
|
86
|
+
*/
|
|
87
|
+
async function exportDependencies(providerData, fileData) {
|
|
88
|
+
const attrMapperScriptId = _.get(providerData, [
|
|
89
|
+
'identityProvider',
|
|
90
|
+
'assertionProcessing',
|
|
91
|
+
'attributeMapper',
|
|
92
|
+
'attributeMapperScript',
|
|
93
|
+
]);
|
|
94
|
+
if (attrMapperScriptId && attrMapperScriptId !== '[Empty]') {
|
|
95
|
+
const scriptData = (await getScript(attrMapperScriptId)).data;
|
|
96
|
+
scriptData.script = convertBase64TextToArray(scriptData.script);
|
|
97
|
+
// eslint-disable-next-line no-param-reassign
|
|
98
|
+
fileData.script[attrMapperScriptId] = scriptData;
|
|
99
|
+
}
|
|
100
|
+
const idpAdapterScriptId = _.get(providerData, [
|
|
101
|
+
'identityProvider',
|
|
102
|
+
'advanced',
|
|
103
|
+
'idpAdapter',
|
|
104
|
+
'idpAdapterScript',
|
|
105
|
+
]);
|
|
106
|
+
if (idpAdapterScriptId && idpAdapterScriptId !== '[Empty]') {
|
|
107
|
+
const scriptData = (await getScript(idpAdapterScriptId)).data;
|
|
108
|
+
scriptData.script = convertBase64TextToArray(scriptData.script);
|
|
109
|
+
// eslint-disable-next-line no-param-reassign
|
|
110
|
+
fileData.script[idpAdapterScriptId] = scriptData;
|
|
111
|
+
}
|
|
112
|
+
const metaDataResponse = await getProviderMetadata(providerData.entityId);
|
|
113
|
+
// eslint-disable-next-line no-param-reassign
|
|
114
|
+
fileData.saml.metadata[providerData._id] = convertBase64UrlTextToArray(
|
|
115
|
+
encodeBase64Url(metaDataResponse.data)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Export a single entity provider to file
|
|
121
|
+
* @param {String} entityId Provider entity id
|
|
122
|
+
* @param {String} file Optional filename
|
|
123
|
+
*/
|
|
124
|
+
export async function exportProvider(entityId, file = null) {
|
|
125
|
+
let fileName = file;
|
|
126
|
+
if (!fileName) {
|
|
127
|
+
fileName = getTypedFilename(entityId, 'saml');
|
|
128
|
+
}
|
|
129
|
+
createProgressBar(1, `Exporting provider ${entityId}`);
|
|
130
|
+
const found = await findProviders(`entityId eq '${entityId}'`, 'location');
|
|
131
|
+
switch (found.data.resultCount) {
|
|
132
|
+
case 0:
|
|
133
|
+
printMessage(`No provider with entity id '${entityId}' found`, 'error');
|
|
134
|
+
break;
|
|
135
|
+
case 1:
|
|
136
|
+
{
|
|
137
|
+
const { location } = found.data.result[0];
|
|
138
|
+
const id = found.data.result[0]._id;
|
|
139
|
+
getProviderByLocationAndId(location, id)
|
|
140
|
+
.then(async (response) => {
|
|
141
|
+
const providerData = response.data;
|
|
142
|
+
updateProgressBar(`Writing file ${fileName}`);
|
|
143
|
+
const fileData = getFileDataTemplate();
|
|
144
|
+
fileData.saml[location][providerData._id] = providerData;
|
|
145
|
+
await exportDependencies(providerData, fileData);
|
|
146
|
+
saveJsonToFile(fileData, fileName);
|
|
147
|
+
stopProgressBar(
|
|
148
|
+
`Exported ${entityId.brightCyan} to ${fileName.brightCyan}.`
|
|
149
|
+
);
|
|
150
|
+
})
|
|
151
|
+
.catch((err) => {
|
|
152
|
+
stopProgressBar(`${err}`);
|
|
153
|
+
printMessage(err, 'error');
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
printMessage(
|
|
159
|
+
`Multiple providers with entity id '${entityId}' found`,
|
|
160
|
+
'error'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Export provider metadata to file
|
|
167
|
+
* @param {String} entityId Provider entity id
|
|
168
|
+
* @param {String} file Optional filename
|
|
169
|
+
*/
|
|
170
|
+
export async function exportMetadata(entityId, file = null) {
|
|
171
|
+
let fileName = file;
|
|
172
|
+
if (!fileName) {
|
|
173
|
+
fileName = getTypedFilename(entityId, 'metadata', 'xml');
|
|
174
|
+
}
|
|
175
|
+
createProgressBar(1, `Exporting metadata for: ${entityId}`);
|
|
176
|
+
getProviderMetadata(entityId)
|
|
177
|
+
.then(async (response) => {
|
|
178
|
+
updateProgressBar(`Writing file ${fileName}`);
|
|
179
|
+
// printMessage(response.data, 'error');
|
|
180
|
+
const metaData = response.data;
|
|
181
|
+
saveTextToFile(metaData, fileName);
|
|
182
|
+
stopProgressBar(
|
|
183
|
+
`Exported ${entityId.brightCyan} metadata to ${fileName.brightCyan}.`
|
|
184
|
+
);
|
|
185
|
+
})
|
|
186
|
+
.catch((err) => {
|
|
187
|
+
stopProgressBar(`${err}`);
|
|
188
|
+
printMessage(err, 'error');
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Describe an entity provider's configuration
|
|
194
|
+
* @param {String} entityId Provider entity id
|
|
195
|
+
*/
|
|
196
|
+
export async function describeProvider(entityId) {
|
|
197
|
+
const found = await findProviders(
|
|
198
|
+
`entityId eq '${entityId}'`,
|
|
199
|
+
'location,roles'
|
|
200
|
+
);
|
|
201
|
+
switch (found.data.resultCount) {
|
|
202
|
+
case 0:
|
|
203
|
+
printMessage(`No provider with entity id '${entityId}' found`, 'error');
|
|
204
|
+
break;
|
|
205
|
+
case 1:
|
|
206
|
+
{
|
|
207
|
+
const { location } = found.data.result[0];
|
|
208
|
+
const id = found.data.result[0]._id;
|
|
209
|
+
const roles = found.data.result[0].roles
|
|
210
|
+
.map((role) => roleMap[role])
|
|
211
|
+
.join(', ');
|
|
212
|
+
getProviderByLocationAndId(location, id)
|
|
213
|
+
.then(async (response) => {
|
|
214
|
+
const rawProviderData = response.data;
|
|
215
|
+
delete rawProviderData._id;
|
|
216
|
+
delete rawProviderData._rev;
|
|
217
|
+
rawProviderData.location = location;
|
|
218
|
+
rawProviderData.roles = roles;
|
|
219
|
+
rawProviderData.metadataUrl = getProviderMetadataUrl(entityId);
|
|
220
|
+
// const fullProviderData = getFileDataTemplate();
|
|
221
|
+
// fullProviderData.saml[location][rawProviderData._id] =
|
|
222
|
+
// rawProviderData;
|
|
223
|
+
// await exportDependencies(rawProviderData, fullProviderData);
|
|
224
|
+
// describe the provider
|
|
225
|
+
const table = createObjectTable(rawProviderData);
|
|
226
|
+
printMessage(table.toString());
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
printMessage(err, 'error');
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
printMessage(
|
|
235
|
+
`Multiple providers with entity id '${entityId}' found`,
|
|
236
|
+
'error'
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Export all entity providers to one file
|
|
243
|
+
* @param {String} file Optional filename
|
|
244
|
+
*/
|
|
245
|
+
export async function exportProvidersToFile(file = null) {
|
|
246
|
+
let fileName = file;
|
|
247
|
+
if (!fileName) {
|
|
248
|
+
fileName = getTypedFilename(`all${getRealmString()}Providers`, 'saml');
|
|
249
|
+
}
|
|
250
|
+
const fileData = getFileDataTemplate();
|
|
251
|
+
const found = await getProviders();
|
|
252
|
+
if (found.status < 200 || found.status > 399) {
|
|
253
|
+
printMessage(found, 'data');
|
|
254
|
+
printMessage(`exportProvidersToFile: ${found.status}`, 'error');
|
|
255
|
+
} else if (found.data.resultCount > 0) {
|
|
256
|
+
createProgressBar(found.data.resultCount, 'Exporting providers');
|
|
257
|
+
for (const stubData of found.data.result) {
|
|
258
|
+
updateProgressBar(`Exporting provider ${stubData.entityId}`);
|
|
259
|
+
// eslint-disable-next-line no-await-in-loop
|
|
260
|
+
const response = await getProviderByLocationAndId(
|
|
261
|
+
stubData.location,
|
|
262
|
+
stubData._id
|
|
263
|
+
);
|
|
264
|
+
const providerData = response.data;
|
|
265
|
+
// eslint-disable-next-line no-await-in-loop
|
|
266
|
+
await exportDependencies(providerData, fileData);
|
|
267
|
+
fileData.saml[stubData.location][providerData._id] = providerData;
|
|
268
|
+
}
|
|
269
|
+
saveJsonToFile(fileData, fileName);
|
|
270
|
+
stopProgressBar(
|
|
271
|
+
`${found.data.resultCount} providers exported to ${fileName}.`
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
printMessage('No entity providers found.', 'info');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Export all entity providers to individual files
|
|
280
|
+
*/
|
|
281
|
+
export async function exportProvidersToFiles() {
|
|
282
|
+
const found = await getProviders();
|
|
283
|
+
if (found.data.resultCount > 0) {
|
|
284
|
+
createProgressBar(found.data.resultCount, 'Exporting providers');
|
|
285
|
+
for (const stubData of found.data.result) {
|
|
286
|
+
updateProgressBar(`Exporting provider ${stubData.entityId}`);
|
|
287
|
+
const fileName = getTypedFilename(stubData.entityId, 'saml');
|
|
288
|
+
const fileData = getFileDataTemplate();
|
|
289
|
+
// eslint-disable-next-line no-await-in-loop
|
|
290
|
+
const response = await getProviderByLocationAndId(
|
|
291
|
+
stubData.location,
|
|
292
|
+
stubData._id
|
|
293
|
+
);
|
|
294
|
+
const providerData = response.data;
|
|
295
|
+
// eslint-disable-next-line no-await-in-loop
|
|
296
|
+
await exportDependencies(providerData, fileData);
|
|
297
|
+
fileData.saml[stubData.location][providerData._id] = providerData;
|
|
298
|
+
saveJsonToFile(fileData, fileName);
|
|
299
|
+
}
|
|
300
|
+
stopProgressBar(`${found.data.resultCount} providers exported.`);
|
|
301
|
+
} else {
|
|
302
|
+
printMessage('No entity providers found.', 'info');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Include dependencies from the import file
|
|
308
|
+
* @param {Object} providerData Object representing a SAML entity provider
|
|
309
|
+
* @param {Object} fileData File data object to read dependencies from
|
|
310
|
+
*/
|
|
311
|
+
async function importDependencies(providerData, fileData) {
|
|
312
|
+
const attrMapperScriptId = _.get(providerData, [
|
|
313
|
+
'identityProvider',
|
|
314
|
+
'assertionProcessing',
|
|
315
|
+
'attributeMapper',
|
|
316
|
+
'attributeMapperScript',
|
|
317
|
+
]);
|
|
318
|
+
if (attrMapperScriptId && attrMapperScriptId !== '[Empty]') {
|
|
319
|
+
const scriptData = _.get(fileData, ['script', attrMapperScriptId]);
|
|
320
|
+
scriptData.script = convertTextArrayToBase64(scriptData.script);
|
|
321
|
+
await createOrUpdateScript(attrMapperScriptId, scriptData);
|
|
322
|
+
}
|
|
323
|
+
const idpAdapterScriptId = _.get(providerData, [
|
|
324
|
+
'identityProvider',
|
|
325
|
+
'advanced',
|
|
326
|
+
'idpAdapter',
|
|
327
|
+
'idpAdapterScript',
|
|
328
|
+
]);
|
|
329
|
+
if (idpAdapterScriptId && idpAdapterScriptId !== '[Empty]') {
|
|
330
|
+
const scriptData = _.get(fileData, ['script', idpAdapterScriptId]);
|
|
331
|
+
scriptData.script = convertTextArrayToBase64(scriptData.script);
|
|
332
|
+
await createOrUpdateScript(attrMapperScriptId, scriptData);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Find provider in import file and return its location
|
|
338
|
+
* @param {String} entityId64 Base64-encoded provider entity id
|
|
339
|
+
* @param {Object} fileData Import file json data
|
|
340
|
+
* @returns {String} 'hosted' or 'remote' if found, undefined otherwise
|
|
341
|
+
*/
|
|
342
|
+
function getLocation(entityId64, fileData) {
|
|
343
|
+
if (_.get(fileData, ['saml', 'hosted', entityId64])) {
|
|
344
|
+
return 'hosted';
|
|
345
|
+
}
|
|
346
|
+
if (_.get(fileData, ['saml', 'remote', entityId64])) {
|
|
347
|
+
return 'remote';
|
|
348
|
+
}
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Import a SAML entity provider by entity id from file
|
|
354
|
+
* @param {String} entityId Provider entity id
|
|
355
|
+
* @param {String} file Import file name
|
|
356
|
+
*/
|
|
357
|
+
export async function importProvider(entityId, file) {
|
|
358
|
+
const entityId64 = encode(entityId, false);
|
|
359
|
+
fs.readFile(file, 'utf8', async (err, data) => {
|
|
360
|
+
if (err) throw err;
|
|
361
|
+
const fileData = JSON.parse(data);
|
|
362
|
+
if (validateImport(fileData.meta)) {
|
|
363
|
+
createProgressBar(1, 'Importing provider...');
|
|
364
|
+
const location = getLocation(entityId64, fileData);
|
|
365
|
+
if (location) {
|
|
366
|
+
const providerData = _.get(fileData, ['saml', location, entityId64]);
|
|
367
|
+
updateProgressBar(`Importing ${entityId}`);
|
|
368
|
+
await importDependencies(providerData, fileData);
|
|
369
|
+
let metaData = null;
|
|
370
|
+
if (location === 'remote') {
|
|
371
|
+
metaData = convertTextArrayToBase64Url(
|
|
372
|
+
fileData.saml.metadata[entityId64]
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
createProvider(location, providerData, metaData)
|
|
376
|
+
.then(() => {
|
|
377
|
+
stopProgressBar(`Successfully imported provider ${entityId}.`);
|
|
378
|
+
})
|
|
379
|
+
.catch((createProviderErr) => {
|
|
380
|
+
printMessage(`\nError importing provider ${entityId}`, 'error');
|
|
381
|
+
printMessage(createProviderErr.response, 'error');
|
|
382
|
+
});
|
|
383
|
+
} else {
|
|
384
|
+
stopProgressBar(
|
|
385
|
+
`Provider ${entityId.brightCyan} not found in ${file.brightCyan}!`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
printMessage('Import validation failed...', 'error');
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Import first SAML entity provider from file
|
|
396
|
+
* @param {String} file Import file name
|
|
397
|
+
*/
|
|
398
|
+
export async function importFirstProvider(file) {
|
|
399
|
+
fs.readFile(file, 'utf8', async (err, data) => {
|
|
400
|
+
if (err) throw err;
|
|
401
|
+
const fileData = JSON.parse(data);
|
|
402
|
+
if (validateImport(fileData.meta)) {
|
|
403
|
+
createProgressBar(1, 'Importing provider...');
|
|
404
|
+
// find providers in hosted and if none exist in remote
|
|
405
|
+
let location = 'hosted';
|
|
406
|
+
let providerIds = _.keys(fileData.saml[location]);
|
|
407
|
+
if (providerIds.length === 0) {
|
|
408
|
+
location = 'remote';
|
|
409
|
+
providerIds = _.keys(fileData.saml[location]);
|
|
410
|
+
if (providerIds.length === 0) {
|
|
411
|
+
location = null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (location) {
|
|
415
|
+
const entityId64 = providerIds[0];
|
|
416
|
+
const entityId = decode(entityId64);
|
|
417
|
+
const providerData = _.get(fileData, ['saml', location, entityId64]);
|
|
418
|
+
updateProgressBar(`Importing ${entityId}`);
|
|
419
|
+
await importDependencies(providerData, fileData);
|
|
420
|
+
let metaData = null;
|
|
421
|
+
if (location === 'remote') {
|
|
422
|
+
metaData = convertTextArrayToBase64Url(
|
|
423
|
+
fileData.saml.metadata[entityId64]
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
createProvider(location, providerData, metaData)
|
|
427
|
+
.then(() => {
|
|
428
|
+
stopProgressBar(`Successfully imported provider ${entityId}.`);
|
|
429
|
+
})
|
|
430
|
+
.catch((createProviderErr) => {
|
|
431
|
+
stopProgressBar(`Error importing provider ${entityId}`);
|
|
432
|
+
printMessage(`\nError importing provider ${entityId}`, 'error');
|
|
433
|
+
printMessage(createProviderErr.response.data, 'error');
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
stopProgressBar(`No providers found in ${file.brightCyan}!`);
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
printMessage('Import validation failed...', 'error');
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Import all SAML entity providers from file
|
|
446
|
+
* @param {String} file Import file name
|
|
447
|
+
*/
|
|
448
|
+
export async function importProvidersFromFile(file) {
|
|
449
|
+
fs.readFile(file, 'utf8', async (err, data) => {
|
|
450
|
+
if (err) throw err;
|
|
451
|
+
const fileData = JSON.parse(data);
|
|
452
|
+
if (validateImport(fileData.meta)) {
|
|
453
|
+
// find providers in hosted and in remote and map locations
|
|
454
|
+
const hostedIds = _.keys(fileData.saml.hosted);
|
|
455
|
+
const remoteIds = _.keys(fileData.saml.remote);
|
|
456
|
+
const providerIds = hostedIds.concat(remoteIds);
|
|
457
|
+
createProgressBar(providerIds.length, 'Importing providers...');
|
|
458
|
+
for (const entityId64 of providerIds) {
|
|
459
|
+
const location = hostedIds.includes(entityId64) ? 'hosted' : 'remote';
|
|
460
|
+
const entityId = decode(entityId64);
|
|
461
|
+
const providerData = _.get(fileData, ['saml', location, entityId64]);
|
|
462
|
+
// eslint-disable-next-line no-await-in-loop
|
|
463
|
+
await importDependencies(providerData, fileData);
|
|
464
|
+
let metaData = null;
|
|
465
|
+
if (location === 'remote') {
|
|
466
|
+
metaData = convertTextArrayToBase64Url(
|
|
467
|
+
fileData.saml.metadata[entityId64]
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
// eslint-disable-next-line no-await-in-loop
|
|
472
|
+
await createProvider(location, providerData, metaData);
|
|
473
|
+
updateProgressBar(`Imported ${entityId}`);
|
|
474
|
+
} catch (createProviderErr) {
|
|
475
|
+
printMessage(`\nError importing provider ${entityId}`, 'error');
|
|
476
|
+
printMessage(createProviderErr.response.data, 'error');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
stopProgressBar(`Providers imported.`);
|
|
480
|
+
} else {
|
|
481
|
+
printMessage('Import validation failed...', 'error');
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Import all SAML entity providers from all *.saml.json files in the current directory
|
|
488
|
+
*/
|
|
489
|
+
export async function importProvidersFromFiles() {
|
|
490
|
+
const names = fs.readdirSync('.');
|
|
491
|
+
const jsonFiles = names.filter((name) =>
|
|
492
|
+
name.toLowerCase().endsWith('.saml.json')
|
|
493
|
+
);
|
|
494
|
+
createProgressBar(jsonFiles.length, 'Importing providers...');
|
|
495
|
+
let total = 0;
|
|
496
|
+
let totalErrors = 0;
|
|
497
|
+
for (const file of jsonFiles) {
|
|
498
|
+
const data = fs.readFileSync(file, 'utf8');
|
|
499
|
+
const fileData = JSON.parse(data);
|
|
500
|
+
if (validateImport(fileData.meta)) {
|
|
501
|
+
// find providers in hosted and in remote and map locations
|
|
502
|
+
const hostedIds = _.keys(fileData.saml.hosted);
|
|
503
|
+
const remoteIds = _.keys(fileData.saml.remote);
|
|
504
|
+
const providerIds = hostedIds.concat(remoteIds);
|
|
505
|
+
total += providerIds.length;
|
|
506
|
+
let errors = 0;
|
|
507
|
+
for (const entityId64 of providerIds) {
|
|
508
|
+
const location = hostedIds.includes(entityId64) ? 'hosted' : 'remote';
|
|
509
|
+
const entityId = decode(entityId64);
|
|
510
|
+
const providerData = _.get(fileData, ['saml', location, entityId64]);
|
|
511
|
+
importDependencies(providerData, fileData);
|
|
512
|
+
let metaData = null;
|
|
513
|
+
if (location === 'remote') {
|
|
514
|
+
metaData = convertTextArrayToBase64Url(
|
|
515
|
+
fileData.saml.metadata[entityId64]
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
// eslint-disable-next-line no-await-in-loop
|
|
520
|
+
await createProvider(location, providerData, metaData);
|
|
521
|
+
// updateProgressBar(`Imported ${entityId}`);
|
|
522
|
+
} catch (createProviderErr) {
|
|
523
|
+
errors += 1;
|
|
524
|
+
printMessage(`\nError importing provider ${entityId}`, 'error');
|
|
525
|
+
printMessage(createProviderErr.response.data, 'error');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
totalErrors += errors;
|
|
529
|
+
updateProgressBar(
|
|
530
|
+
`Imported ${providerIds.length - errors} provider(s) from ${file}`
|
|
531
|
+
);
|
|
532
|
+
} else {
|
|
533
|
+
printMessage(`Validation of ${file} failed!`, 'error');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
stopProgressBar(
|
|
537
|
+
`Imported ${total - totalErrors} of ${total} provider(s) from ${
|
|
538
|
+
jsonFiles.length
|
|
539
|
+
} file(s).`
|
|
540
|
+
);
|
|
541
|
+
}
|