@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.
Files changed (112) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/README.md +121 -0
  5. package/.github/workflows/pipeline.yml +287 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +512 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/LICENSE +21 -0
  10. package/README.md +8 -0
  11. package/docs/CONTRIBUTE.md +96 -0
  12. package/docs/PIPELINE.md +169 -0
  13. package/docs/images/npm_versioning_guidelines.png +0 -0
  14. package/docs/images/release_pipeline.png +0 -0
  15. package/jsconfig.json +6 -0
  16. package/package.json +95 -0
  17. package/resources/sampleEntitiesFile.json +8 -0
  18. package/resources/sampleEnvFile.env +2 -0
  19. package/src/api/AuthenticateApi.js +33 -0
  20. package/src/api/BaseApi.js +242 -0
  21. package/src/api/CirclesOfTrustApi.js +87 -0
  22. package/src/api/EmailTemplateApi.js +37 -0
  23. package/src/api/IdmConfigApi.js +88 -0
  24. package/src/api/LogApi.js +45 -0
  25. package/src/api/ManagedObjectApi.js +62 -0
  26. package/src/api/OAuth2ClientApi.js +69 -0
  27. package/src/api/OAuth2OIDCApi.js +73 -0
  28. package/src/api/OAuth2ProviderApi.js +32 -0
  29. package/src/api/RealmApi.js +99 -0
  30. package/src/api/Saml2Api.js +176 -0
  31. package/src/api/ScriptApi.js +84 -0
  32. package/src/api/SecretsApi.js +151 -0
  33. package/src/api/ServerInfoApi.js +41 -0
  34. package/src/api/SocialIdentityProvidersApi.js +114 -0
  35. package/src/api/StartupApi.js +45 -0
  36. package/src/api/ThemeApi.js +181 -0
  37. package/src/api/TreeApi.js +207 -0
  38. package/src/api/VariablesApi.js +104 -0
  39. package/src/api/utils/ApiUtils.js +77 -0
  40. package/src/api/utils/ApiUtils.test.js +96 -0
  41. package/src/api/utils/Base64.js +62 -0
  42. package/src/index.js +32 -0
  43. package/src/index.test.js +13 -0
  44. package/src/ops/AdminOps.js +901 -0
  45. package/src/ops/AuthenticateOps.js +342 -0
  46. package/src/ops/CirclesOfTrustOps.js +350 -0
  47. package/src/ops/ConnectionProfileOps.js +254 -0
  48. package/src/ops/EmailTemplateOps.js +326 -0
  49. package/src/ops/IdmOps.js +227 -0
  50. package/src/ops/IdpOps.js +342 -0
  51. package/src/ops/JourneyOps.js +2026 -0
  52. package/src/ops/LogOps.js +357 -0
  53. package/src/ops/ManagedObjectOps.js +34 -0
  54. package/src/ops/OAuth2ClientOps.js +151 -0
  55. package/src/ops/OrganizationOps.js +85 -0
  56. package/src/ops/RealmOps.js +139 -0
  57. package/src/ops/SamlOps.js +541 -0
  58. package/src/ops/ScriptOps.js +211 -0
  59. package/src/ops/SecretsOps.js +288 -0
  60. package/src/ops/StartupOps.js +114 -0
  61. package/src/ops/ThemeOps.js +379 -0
  62. package/src/ops/VariablesOps.js +185 -0
  63. package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
  64. package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
  65. package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
  66. package/src/ops/templates/cloud/managed.json +4119 -0
  67. package/src/ops/utils/Console.js +434 -0
  68. package/src/ops/utils/DataProtection.js +92 -0
  69. package/src/ops/utils/DataProtection.test.js +28 -0
  70. package/src/ops/utils/ExportImportUtils.js +146 -0
  71. package/src/ops/utils/ExportImportUtils.test.js +119 -0
  72. package/src/ops/utils/OpsUtils.js +76 -0
  73. package/src/ops/utils/Wordwrap.js +11 -0
  74. package/src/storage/SessionStorage.js +45 -0
  75. package/src/storage/StaticStorage.js +15 -0
  76. package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
  77. package/test/e2e/journey/baseline/Login.journey.json +205 -0
  78. package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
  79. package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
  80. package/test/e2e/journey/baseline/Registration.journey.json +249 -0
  81. package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
  82. package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
  83. package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
  84. package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
  85. package/test/e2e/journey/delete/Login.journey.json +205 -0
  86. package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
  87. package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
  88. package/test/e2e/journey/delete/Registration.journey.json +249 -0
  89. package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
  90. package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
  91. package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
  92. package/test/e2e/journey/list/Disabled.journey.json +43 -0
  93. package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
  94. package/test/e2e/journey/list/Login.journey.json +205 -0
  95. package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
  96. package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
  97. package/test/e2e/journey/list/Registration.journey.json +249 -0
  98. package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
  99. package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
  100. package/test/e2e/setup.js +107 -0
  101. package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
  102. package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
  103. package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
  104. package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
  105. package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
  106. package/test/e2e/theme/import/Contrast.theme.json +95 -0
  107. package/test/e2e/theme/import/Highlander.theme.json +95 -0
  108. package/test/e2e/theme/import/Robroy.theme.json +95 -0
  109. package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
  110. package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
  111. package/test/fs_tmp/.gitkeep +2 -0
  112. package/test/global/setup.js +65 -0
@@ -0,0 +1,181 @@
1
+ import { getConfigEntity, putConfigEntity } from './IdmConfigApi.js';
2
+ import { getCurrentRealmName } from './utils/ApiUtils.js';
3
+
4
+ const THEMEREALM_ID = 'ui/themerealm';
5
+
6
+ /**
7
+ * Get realm themes
8
+ * @param {Object} themes object containing themes
9
+ * @returns {Object} array of theme pertaining to the current realm
10
+ */
11
+ function getRealmThemes(themes) {
12
+ return themes.realm[getCurrentRealmName()]
13
+ ? themes.realm[getCurrentRealmName()]
14
+ : [];
15
+ }
16
+
17
+ /**
18
+ * Get all themes
19
+ * @returns {Promise} a promise that resolves to an array of themes
20
+ */
21
+ export async function getThemes() {
22
+ return getConfigEntity(THEMEREALM_ID).then((response) =>
23
+ getRealmThemes(response.data)
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Get theme by id
29
+ * @param {String} id theme id
30
+ * @returns {Promise} a promise that resolves to an array of themes
31
+ */
32
+ export async function getTheme(id) {
33
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
34
+ return getRealmThemes(themes).filter((theme) => theme._id === id);
35
+ }
36
+
37
+ /**
38
+ * Get theme by name
39
+ * @param {String} name theme name
40
+ * @returns {Promise} a promise that resolves to an array of themes
41
+ */
42
+ export async function getThemeByName(name) {
43
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
44
+ return getRealmThemes(themes).filter((theme) => theme.name === name);
45
+ }
46
+
47
+ /**
48
+ * Put theme by id
49
+ * @param {String} id theme id
50
+ * @param {Object} data theme object
51
+ * @returns {Promise} a promise that resolves to an object containing a themes object
52
+ */
53
+ export async function putTheme(id, data) {
54
+ const themeData = data;
55
+ themeData._id = id;
56
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
57
+ let isNew = true;
58
+ const realmThemes = getRealmThemes(themes).map((theme) => {
59
+ if (theme._id === id) {
60
+ isNew = false;
61
+ return themeData;
62
+ }
63
+ // eslint-disable-next-line no-param-reassign
64
+ if (themeData.isDefault) theme.isDefault = false;
65
+ return theme;
66
+ });
67
+ if (isNew) {
68
+ realmThemes.push(themeData);
69
+ }
70
+ themes.realm[getCurrentRealmName()] = realmThemes;
71
+ return putConfigEntity(THEMEREALM_ID, themes);
72
+ }
73
+
74
+ /**
75
+ * Put theme by name
76
+ * @param {String} name theme name
77
+ * @param {Object} data theme object
78
+ * @returns {Promise} a promise that resolves to an object containing a themes object
79
+ */
80
+ export async function putThemeByName(name, data) {
81
+ const themeData = data;
82
+ themeData.name = name;
83
+ const themes = await getConfigEntity(THEMEREALM_ID);
84
+ let isNew = true;
85
+ const realmThemes = getRealmThemes(themes).map((theme) => {
86
+ if (theme.name === name) {
87
+ isNew = false;
88
+ return themeData;
89
+ }
90
+ // eslint-disable-next-line no-param-reassign
91
+ if (themeData.isDefault) theme.isDefault = false;
92
+ return theme;
93
+ });
94
+ if (isNew) {
95
+ realmThemes.push(themeData);
96
+ }
97
+ themes.realm[getCurrentRealmName()] = realmThemes;
98
+ return putConfigEntity(THEMEREALM_ID, themes);
99
+ }
100
+
101
+ /**
102
+ * Put all themes
103
+ * @param {Object} data themes object containing all themes for all realms
104
+ * @returns {Promise} a promise that resolves to an object containing a themes object
105
+ */
106
+ export async function putThemes(data) {
107
+ const allThemesData = data;
108
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
109
+ const allThemeIDs = Object.keys(allThemesData);
110
+ const existingThemeIDs = [];
111
+ let defaultThemeId = null;
112
+ // update existing themes
113
+ let realmThemes = getRealmThemes(themes).map((theme) => {
114
+ if (allThemesData[theme._id]) {
115
+ existingThemeIDs.push(theme._id);
116
+ // remember the id of the last default theme
117
+ if (allThemesData[theme._id].isDefault) defaultThemeId = theme._id;
118
+ return allThemesData[theme._id];
119
+ }
120
+ return theme;
121
+ });
122
+ const newThemeIDs = allThemeIDs.filter(
123
+ (id) => !existingThemeIDs.includes(id)
124
+ );
125
+ // add new themes
126
+ newThemeIDs.forEach((themeId) => {
127
+ // remember the id of the last default theme
128
+ if (allThemesData[themeId].isDefault) defaultThemeId = themeId;
129
+ realmThemes.push(allThemesData[themeId]);
130
+ });
131
+ // if we imported a default theme, flag all the other themes as not default
132
+ if (defaultThemeId) {
133
+ realmThemes = realmThemes.map((theme) => {
134
+ // eslint-disable-next-line no-param-reassign
135
+ theme.isDefault = theme._id === defaultThemeId;
136
+ return theme;
137
+ });
138
+ }
139
+ themes.realm[getCurrentRealmName()] = realmThemes;
140
+ return putConfigEntity(THEMEREALM_ID, themes);
141
+ }
142
+
143
+ /**
144
+ * Delete theme by id
145
+ * @param {String} id theme id
146
+ * @returns {Promise} a promise that resolves to an object containing a themes object
147
+ */
148
+ export async function deleteTheme(id) {
149
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
150
+ const realmThemes = getRealmThemes(themes);
151
+ const finalThemes = realmThemes.filter((theme) => theme._id !== id);
152
+ if (realmThemes.length === finalThemes.length)
153
+ throw new Error(`${id} not found`);
154
+ themes.realm[getCurrentRealmName()] = realmThemes;
155
+ return putConfigEntity(THEMEREALM_ID, themes);
156
+ }
157
+
158
+ /**
159
+ * Delete theme by name
160
+ * @param {String} name theme name
161
+ * @returns {Promise} a promise that resolves to an object containing a themes object
162
+ */
163
+ export async function deleteThemeByName(name) {
164
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
165
+ const realmThemes = getRealmThemes(themes);
166
+ const finalThemes = realmThemes.filter((theme) => theme.name !== name);
167
+ if (realmThemes.length === finalThemes.length)
168
+ throw new Error(`${name} not found`);
169
+ themes.realm[getCurrentRealmName()] = finalThemes;
170
+ return putConfigEntity(THEMEREALM_ID, themes);
171
+ }
172
+
173
+ /**
174
+ * Delete all themes
175
+ * @returns {Promise} a promise that resolves to an array of themes
176
+ */
177
+ export async function deleteThemes() {
178
+ const themes = (await getConfigEntity(THEMEREALM_ID)).data;
179
+ themes.realm[getCurrentRealmName()] = [];
180
+ return putConfigEntity(THEMEREALM_ID, themes);
181
+ }
@@ -0,0 +1,207 @@
1
+ import util from 'util';
2
+ import { deleteDeepByKey, getCurrentRealmPath } from './utils/ApiUtils.js';
3
+ import { generateAmApi } from './BaseApi.js';
4
+ import storage from '../storage/SessionStorage.js';
5
+
6
+ const nodeURLTemplate =
7
+ '%s/json%s/realm-config/authentication/authenticationtrees/nodes/%s/%s';
8
+ const queryAllNodesByTypeURLTemplate =
9
+ '%s/json%s/realm-config/authentication/authenticationtrees/nodes/%s?_queryFilter=true';
10
+ const queryAllNodesURLTemplate =
11
+ '%s/json%s/realm-config/authentication/authenticationtrees/nodes?_action=nextdescendents';
12
+ const queryAllNodeTypesURLTemplate =
13
+ '%s/json%s/realm-config/authentication/authenticationtrees/nodes?_action=getAllTypes';
14
+ const treeByIdURLTemplate =
15
+ '%s/json%s/realm-config/authentication/authenticationtrees/trees/%s';
16
+ const queryAllTreesURLTemplate =
17
+ '%s/json%s/realm-config/authentication/authenticationtrees/trees?_queryFilter=true';
18
+
19
+ const apiVersion = 'protocol=2.1,resource=1.0';
20
+ const getTreeApiConfig = () => {
21
+ const configPath = getCurrentRealmPath();
22
+ return {
23
+ path: `${configPath}/authentication/authenticationtrees`,
24
+ apiVersion,
25
+ };
26
+ };
27
+
28
+ export async function getNodeTypes() {
29
+ const urlString = util.format(
30
+ queryAllNodeTypesURLTemplate,
31
+ storage.session.getTenant(),
32
+ getCurrentRealmPath()
33
+ );
34
+ return generateAmApi(getTreeApiConfig()).post(
35
+ urlString,
36
+ {},
37
+ {
38
+ withCredentials: true,
39
+ headers: { 'Accept-Encoding': 'gzip, deflate, br' },
40
+ }
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Get all nodes
46
+ * @returns {Promise} a promise that resolves to an object containing an array of all node objects
47
+ */
48
+ export async function getNodes() {
49
+ const urlString = util.format(
50
+ queryAllNodesURLTemplate,
51
+ storage.session.getTenant(),
52
+ getCurrentRealmPath()
53
+ );
54
+ return generateAmApi(getTreeApiConfig()).post(
55
+ urlString,
56
+ {},
57
+ {
58
+ withCredentials: true,
59
+ headers: { 'Accept-Encoding': 'gzip, deflate, br' },
60
+ }
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Get all nodes by type
66
+ * @param {*} type node type
67
+ * @returns {Promise} a promise that resolves to an object containing an array of node objects of the requested type
68
+ */
69
+ export async function getNodesByType(type) {
70
+ const urlString = util.format(
71
+ queryAllNodesByTypeURLTemplate,
72
+ storage.session.getTenant(),
73
+ getCurrentRealmPath(),
74
+ type
75
+ );
76
+ return generateAmApi(getTreeApiConfig()).get(urlString, {
77
+ withCredentials: true,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Get node by uuid and type
83
+ * @param {String} id node uuid
84
+ * @param {String} nodeType node type
85
+ * @returns {Promise} a promise that resolves to an object containing a node object
86
+ */
87
+ export async function getNode(id, nodeType) {
88
+ const urlString = util.format(
89
+ nodeURLTemplate,
90
+ storage.session.getTenant(),
91
+ getCurrentRealmPath(storage.session.getRealm()),
92
+ nodeType,
93
+ id
94
+ );
95
+ return generateAmApi(getTreeApiConfig()).get(urlString, {
96
+ withCredentials: true,
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Put node by uuid and type
102
+ * @param {String} id node uuid
103
+ * @param {String} nodeType node type
104
+ * @param {Object} data node object
105
+ * @returns {Promise} a promise that resolves to an object containing a node object
106
+ */
107
+ export async function putNode(id, nodeType, nodeData) {
108
+ // until we figure out a way to use transport keys in Frodo,
109
+ // we'll have to drop those encrypted attributes.
110
+ const data = deleteDeepByKey(nodeData, '-encrypted');
111
+ const urlString = util.format(
112
+ nodeURLTemplate,
113
+ storage.session.getTenant(),
114
+ getCurrentRealmPath(storage.session.getRealm()),
115
+ nodeType,
116
+ id
117
+ );
118
+ return generateAmApi(getTreeApiConfig()).put(urlString, data, {
119
+ withCredentials: true,
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Delete node by uuid and type
125
+ * @param {String} id node uuid
126
+ * @param {String} nodeType node type
127
+ * @returns {Promise} a promise that resolves to an object containing a node object
128
+ */
129
+ export async function deleteNode(id, nodeType) {
130
+ const urlString = util.format(
131
+ nodeURLTemplate,
132
+ storage.session.getTenant(),
133
+ getCurrentRealmPath(),
134
+ nodeType,
135
+ id
136
+ );
137
+ return generateAmApi(getTreeApiConfig()).delete(urlString, {
138
+ withCredentials: true,
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Get all trees
144
+ * @returns {Promise} a promise that resolves to an object containing an array of tree objects
145
+ */
146
+ export async function getTrees() {
147
+ const urlString = util.format(
148
+ queryAllTreesURLTemplate,
149
+ storage.session.getTenant(),
150
+ getCurrentRealmPath()
151
+ );
152
+ return generateAmApi(getTreeApiConfig()).get(urlString, {
153
+ withCredentials: true,
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Get tree by id/name
159
+ * @param {String} id tree id/name
160
+ * @returns {Promise} a promise that resolves to an object containing a tree object
161
+ */
162
+ export async function getTree(id) {
163
+ const urlString = util.format(
164
+ treeByIdURLTemplate,
165
+ storage.session.getTenant(),
166
+ getCurrentRealmPath(),
167
+ id
168
+ );
169
+ return generateAmApi(getTreeApiConfig()).get(urlString, {
170
+ withCredentials: true,
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Put tree by id/name
176
+ * @param {String} id tree id/name
177
+ * @param {Object} data tree object
178
+ * @returns {Promise} a promise that resolves to an object containing a tree object
179
+ */
180
+ export async function putTree(id, data) {
181
+ const urlString = util.format(
182
+ treeByIdURLTemplate,
183
+ storage.session.getTenant(),
184
+ getCurrentRealmPath(storage.session.getRealm()),
185
+ id
186
+ );
187
+ return generateAmApi(getTreeApiConfig()).put(urlString, data, {
188
+ withCredentials: true,
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Delete tree by id/name
194
+ * @param {String} id tree id/name
195
+ * @returns {Promise} a promise that resolves to an object containing a tree object
196
+ */
197
+ export async function deleteTree(id) {
198
+ const urlString = util.format(
199
+ treeByIdURLTemplate,
200
+ storage.session.getTenant(),
201
+ getCurrentRealmPath(),
202
+ id
203
+ );
204
+ return generateAmApi(getTreeApiConfig()).delete(urlString, {
205
+ withCredentials: true,
206
+ });
207
+ }
@@ -0,0 +1,104 @@
1
+ import util from 'util';
2
+ import { encode } from './utils/Base64.js';
3
+ import { getTenantURL, getCurrentRealmPath } from './utils/ApiUtils.js';
4
+ import { generateESVApi } from './BaseApi.js';
5
+ import storage from '../storage/SessionStorage.js';
6
+
7
+ const variablesListURLTemplate = '%s/environment/variables';
8
+ const variableURLTemplate = '%s/environment/variables/%s';
9
+ const variableSetDescriptionURLTemplate = `${variableURLTemplate}?_action=setDescription`;
10
+
11
+ const apiVersion = 'protocol=1.0,resource=1.0';
12
+ const getApiConfig = () => {
13
+ const configPath = getCurrentRealmPath();
14
+ return {
15
+ path: `${configPath}/environment/secrets`,
16
+ apiVersion,
17
+ };
18
+ };
19
+
20
+ /**
21
+ * Get all variables
22
+ * @returns {Promise} a promise that resolves to an object containing an array of variable objects
23
+ */
24
+ export async function getVariables() {
25
+ const urlString = util.format(
26
+ variablesListURLTemplate,
27
+ getTenantURL(storage.session.getTenant())
28
+ );
29
+ return generateESVApi(getApiConfig()).get(urlString, {
30
+ withCredentials: true,
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Get variable by id/name
36
+ * @param {String} id variable id/name
37
+ * @returns {Promise} a promise that resolves to an object containing a variable object
38
+ */
39
+ export async function getVariable(id) {
40
+ const urlString = util.format(
41
+ variableURLTemplate,
42
+ getTenantURL(storage.session.getTenant()),
43
+ id
44
+ );
45
+ return generateESVApi(getApiConfig()).get(urlString, {
46
+ withCredentials: true,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Put variable by id/name
52
+ * @param {String} id variable id/name
53
+ * @param {String} value variable value
54
+ * @param {String} description variable description
55
+ * @returns {Promise} a promise that resolves to an object containing a variable object
56
+ */
57
+ export async function putVariable(id, value, description) {
58
+ const data = {};
59
+ if (value) data.valueBase64 = encode(value);
60
+ if (description) data.description = description;
61
+ const urlString = util.format(
62
+ variableURLTemplate,
63
+ getTenantURL(storage.session.getTenant()),
64
+ id
65
+ );
66
+ return generateESVApi(getApiConfig()).put(urlString, data, {
67
+ withCredentials: true,
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Set variable description
73
+ * @param {*} id variable id/name
74
+ * @param {*} description variable description
75
+ * @returns {Promise} a promise that resolves to an object containing a status object
76
+ */
77
+ export async function setVariableDescription(id, description) {
78
+ const urlString = util.format(
79
+ variableSetDescriptionURLTemplate,
80
+ getTenantURL(storage.session.getTenant()),
81
+ id
82
+ );
83
+ return generateESVApi(getApiConfig()).post(
84
+ urlString,
85
+ { description },
86
+ { withCredentials: true }
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Delete variable by id/name
92
+ * @param {String} id variable id/name
93
+ * @returns {Promise} a promise that resolves to an object containing a variable object
94
+ */
95
+ export async function deleteVariable(id) {
96
+ const urlString = util.format(
97
+ variableURLTemplate,
98
+ getTenantURL(storage.session.getTenant()),
99
+ id
100
+ );
101
+ return generateESVApi(getApiConfig()).delete(urlString, {
102
+ withCredentials: true,
103
+ });
104
+ }
@@ -0,0 +1,77 @@
1
+ import util from 'util';
2
+ import storage from '../../storage/SessionStorage.js';
3
+
4
+ const realmPathTemplate = '/realms/%s';
5
+
6
+ /**
7
+ * Get current realm path
8
+ * @returns {String} a CREST-compliant realm path, e.g. /realms/root/realms/alpha
9
+ */
10
+ export function getCurrentRealmPath() {
11
+ let realm = storage.session.getRealm();
12
+ if (realm.startsWith('/') && realm.length > 1) {
13
+ realm = realm.substring(1);
14
+ }
15
+ let realmPath = util.format(realmPathTemplate, 'root');
16
+ if (realm !== '/') {
17
+ realmPath += util.format(realmPathTemplate, realm);
18
+ }
19
+ return realmPath;
20
+ }
21
+
22
+ /**
23
+ * Get current realm name
24
+ * @returns {String} name of the current realm. /alpha -> alpha
25
+ */
26
+ export function getCurrentRealmName() {
27
+ const realm = storage.session.getRealm();
28
+ const components = realm.split('/');
29
+ let realmName = '/';
30
+ if (components.length > 0 && realmName !== realm) {
31
+ realmName = components[components.length - 1];
32
+ }
33
+ return realmName;
34
+ }
35
+
36
+ /**
37
+ * Get current realm name
38
+ * @param {String} realm realm
39
+ * @returns {String} name of the realm. /alpha -> alpha
40
+ */
41
+ export function getRealmName(realm) {
42
+ const components = realm.split('/');
43
+ let realmName = '/';
44
+ if (components.length > 0 && realmName !== realm) {
45
+ realmName = components[components.length - 1];
46
+ }
47
+ return realmName;
48
+ }
49
+
50
+ /**
51
+ * Get tenant base URL
52
+ * @param {String} tenant tenant URL with path and query params
53
+ * @returns {String} tenant base URL without path and query params
54
+ */
55
+ export function getTenantURL(tenant) {
56
+ const parsedUrl = new URL(tenant);
57
+ return `${parsedUrl.protocol}//${parsedUrl.host}`;
58
+ }
59
+
60
+ /**
61
+ * Deep delete keys and their values from an input object. If a key in object contains substring, the key an its value is deleted.
62
+ * @param {Object} object input object that needs keys removed
63
+ * @param {String} substring substring to search for in key
64
+ * @returns the modified object without the matching keys and their values
65
+ */
66
+ export function deleteDeepByKey(object, substring) {
67
+ const obj = object;
68
+ const keys = Object.keys(obj);
69
+ for (const key of keys) {
70
+ if (key.indexOf(substring) > 0) {
71
+ delete obj[key];
72
+ } else if (Object(obj[key]) === obj[key]) {
73
+ obj[key] = deleteDeepByKey(obj[key], substring);
74
+ }
75
+ }
76
+ return obj;
77
+ }
@@ -0,0 +1,96 @@
1
+ import {
2
+ getCurrentRealmPath,
3
+ getTenantURL,
4
+ } from './ApiUtils.js';
5
+ import sessionStorage from '../../storage/SessionStorage.js';
6
+
7
+ test.skip('replaceAll should be deleted because it works like native String.replaceAll', () => {
8
+ // Arrange
9
+ // Act
10
+ // Assert
11
+ expect(true).toBe(false);
12
+ });
13
+
14
+ test('getCurrentRealmPath should prepend realmPath to specified realm', () => {
15
+ // Arrange
16
+ const REALM_PATH = 'alpha';
17
+ sessionStorage.session.setItem('realm', REALM_PATH);
18
+ // Act
19
+ const testString = getCurrentRealmPath(REALM_PATH);
20
+ // Assert
21
+ expect(testString).toBe('/realms/root/realms/alpha');
22
+ });
23
+
24
+ test('getCurrentRealmPath should prepend realmPath to specified realm with leading slash', () => {
25
+ // Arrange
26
+ const REALM_PATH = '/alpha';
27
+ sessionStorage.session.setItem('realm', REALM_PATH);
28
+ // Act
29
+ const testString = getCurrentRealmPath(REALM_PATH);
30
+ // Assert
31
+ expect(testString).toBe('/realms/root/realms/alpha');
32
+ });
33
+
34
+ test('getCurrentRealmPath "/" should resolve to root', () => {
35
+ // Arrange
36
+ const REALM_PATH = '/';
37
+ sessionStorage.session.setItem('realm', REALM_PATH);
38
+ // Act
39
+ const testString = getCurrentRealmPath(REALM_PATH);
40
+ // Assert
41
+ expect(testString).toBe('/realms/root');
42
+ });
43
+
44
+ test('getCurrentRealmPath should not handle multiple leading slash', () => {
45
+ // Arrange
46
+ const REALM_PATH = '//alpha';
47
+ sessionStorage.session.setItem('realm', REALM_PATH);
48
+ // Act
49
+ const testString = getCurrentRealmPath(REALM_PATH);
50
+ // Assert
51
+ expect(testString).toBe('/realms/root/realms//alpha');
52
+ });
53
+
54
+ test('getCurrentRealmPath should not handle nested depth realms', () => {
55
+ // Arrange
56
+ const REALM_PATH = '/alpha/erm';
57
+ sessionStorage.session.setItem('realm', REALM_PATH);
58
+ // Act
59
+ const testString = getCurrentRealmPath(REALM_PATH);
60
+ // Assert
61
+ expect(testString).toBe('/realms/root/realms/alpha/erm');
62
+ });
63
+
64
+ test('getTenantURL should parse the https protocol and the hostname', () => {
65
+ // Arrange
66
+ const URL_WITH_TENANT =
67
+ 'https://example.frodo.com/am/ui-admin/#realms/%2Falpha/dashboard';
68
+
69
+ // Act
70
+ const parsed = getTenantURL(URL_WITH_TENANT);
71
+
72
+ // Assert
73
+ expect(parsed).toBe('https://example.frodo.com');
74
+ });
75
+
76
+ test('getTenantURL should not validate protocol', () => {
77
+ // Arrange
78
+ const URL_WITH_TENANT =
79
+ 'ftp://example.frodo.com/am/ui-admin/#realms/%2Falpha/dashboard';
80
+ // Act
81
+ const parsed = getTenantURL(URL_WITH_TENANT);
82
+ // Assert
83
+ expect(parsed).toBe('ftp://example.frodo.com');
84
+ });
85
+
86
+ test('getTenantURL Invalid URL should throw', () => {
87
+ // Arrange
88
+ const URL_WITH_TENANT =
89
+ '//:example.frodo.com/am/ui-admin/#realms/%2Falpha/dashboard';
90
+ // Act
91
+ const trap = () => {
92
+ getTenantURL(URL_WITH_TENANT);
93
+ };
94
+ // Assert
95
+ expect(trap).toThrow('Invalid URL');
96
+ });