@mindline/sync 1.0.110 → 1.0.112
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/.vs/VSWorkspaceState.json +2 -3
- package/.vs/slnx.sqlite +0 -0
- package/.vs/sync.slnx/FileContentIndex/1ca8d456-29aa-416f-9787-e53a88d5d5ab.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/3741cf32-b4b7-4b99-9366-c24391f22a74.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/a8b9c3a0-52bd-4e8b-ad08-9046e741e403.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/{233f16f5-9502-4eee-892d-94508f320b43.vsidx → c8e19530-a150-45f1-a571-cd5fdea9925a.vsidx} +0 -0
- package/.vs/sync.slnx/FileContentIndex/ced1adfa-715d-4462-88ac-43cb0271386e.vsidx +0 -0
- package/.vs/sync.slnx/v18/.wsuo +0 -0
- package/.vs/sync.slnx/v18/DocumentLayout.backup.json +27 -12
- package/.vs/sync.slnx/v18/DocumentLayout.json +23 -8
- package/dist/src/api-client.d.ts +74 -0
- package/dist/src/index.d.ts +8 -10
- package/dist/sync.es.js +1021 -1073
- package/dist/sync.es.js.map +1 -1
- package/dist/sync.umd.js +50 -50
- package/dist/sync.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/api-client.ts +382 -0
- package/src/index.ts +52 -419
- package/.vs/sync.slnx/FileContentIndex/46af7fa0-1a65-4634-9faa-600623e92173.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/88b226f1-9afd-4cf3-bdb6-5db742bb7e8d.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/eb2f80ef-22e0-4cc0-a8fb-f7837f87af2d.vsidx +0 -0
- package/.vs/sync.slnx/FileContentIndex/f109c15d-d422-45e9-a5df-0b391ae0a643.vsidx +0 -0
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import workspaces from "./workspaces.json";
|
|
|
9
9
|
import syncmilestones from './syncmilestones.json';
|
|
10
10
|
import resources from './resources.json';
|
|
11
11
|
import actors from './actors.json';
|
|
12
|
+
import * as secureApiClient from './api-client';
|
|
12
13
|
|
|
13
14
|
const FILTER_FIELD = "workspaceIDs";
|
|
14
15
|
// called by unit tests
|
|
@@ -929,13 +930,6 @@ export class BatchArray {
|
|
|
929
930
|
private pollAuthorizedUser: User | null = null;
|
|
930
931
|
private pollBatchIdArray: Array<any> = [];
|
|
931
932
|
|
|
932
|
-
// Last known backend queues (strongest completion signal)
|
|
933
|
-
private lastQueues: { main: number | null; writer: number | null; deferred: number | null } = {
|
|
934
|
-
main: null,
|
|
935
|
-
writer: null,
|
|
936
|
-
deferred: null
|
|
937
|
-
};
|
|
938
|
-
private hasQueueInfo: boolean = false;
|
|
939
933
|
|
|
940
934
|
// Store UI callbacks so we can update the UI from poll/completion events.
|
|
941
935
|
private setIdleText: ((idleText: string) => void) | null = null;
|
|
@@ -1049,10 +1043,6 @@ export class BatchArray {
|
|
|
1049
1043
|
// Save callback so completion/polling can update the idle text too.
|
|
1050
1044
|
this.setIdleText = setIdleText;
|
|
1051
1045
|
|
|
1052
|
-
// IMPORTANT: reset completion signals between sync runs.
|
|
1053
|
-
// Otherwise a previous run that ended with queues=0 could make the next run immediately "Complete".
|
|
1054
|
-
this.hasQueueInfo = false;
|
|
1055
|
-
this.lastQueues = { main: null, writer: null, deferred: null };
|
|
1056
1046
|
|
|
1057
1047
|
this.pb_startTS = Date.now();
|
|
1058
1048
|
this.pb_progress = 0;
|
|
@@ -1061,35 +1051,14 @@ export class BatchArray {
|
|
|
1061
1051
|
setSyncProgress(this.pb_progress);
|
|
1062
1052
|
setIdleText("Starting sync...");
|
|
1063
1053
|
this.pb_timer = setInterval(() => {
|
|
1064
|
-
//
|
|
1054
|
+
// Completion check is now handled by the SignalR handler via isSyncCompleted()
|
|
1055
|
+
// This timer only increments elapsed time; actual completion logic lives in the handler.
|
|
1065
1056
|
console.log("this.tenantNodes", this.tenantNodes)
|
|
1066
|
-
let isCompletedOrNothingToSync = this.tenantNodes.every((tn: TenantNode) =>
|
|
1067
|
-
tn.nothingtosync || tn.targets.every((ta) => ta.status === "complete" || ta.status === "failed")
|
|
1068
|
-
);
|
|
1069
|
-
|
|
1070
|
-
const queuesEmpty = this.hasQueueInfo &&
|
|
1071
|
-
this.lastQueues.main === 0 &&
|
|
1072
|
-
this.lastQueues.writer === 0 &&
|
|
1073
|
-
this.lastQueues.deferred === 0;
|
|
1074
1057
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (isCompleted) {
|
|
1079
|
-
clearInterval(this.pb_timer!);
|
|
1080
|
-
this.pb_timer = null;
|
|
1081
|
-
this.pb_progress = 100;
|
|
1082
|
-
setSyncProgress(this.pb_progress);
|
|
1083
|
-
setIdleText(`Complete.`);
|
|
1084
|
-
this.stopPolling();
|
|
1085
|
-
this.clearStoredBatchIds();
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
this.pb_total = this.pb_total + 1;
|
|
1089
|
-
this.pb_idle = this.pb_idle + 1;
|
|
1058
|
+
this.pb_total = this.pb_total + 1;
|
|
1059
|
+
this.pb_idle = this.pb_idle + 1;
|
|
1090
1060
|
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1061
|
+
setIdleText(`${this.pb_total} seconds elapsed. Last update ${this.pb_idle} seconds ago.`);
|
|
1093
1062
|
}, 1000);
|
|
1094
1063
|
this.milestoneArray.start(setMilestones);
|
|
1095
1064
|
}
|
|
@@ -1202,9 +1171,6 @@ export class BatchArray {
|
|
|
1202
1171
|
bClearLocalStorage = bClearLocalStorage;
|
|
1203
1172
|
message = message;
|
|
1204
1173
|
|
|
1205
|
-
// Starting a new SignalR session for a (potentially new) run: reset queue-based completion signals.
|
|
1206
|
-
this.hasQueueInfo = false;
|
|
1207
|
-
this.lastQueues = { main: null, writer: null, deferred: null };
|
|
1208
1174
|
|
|
1209
1175
|
// we have just completed a successful POST to startSync
|
|
1210
1176
|
this.milestoneArray.post(setMilestones);
|
|
@@ -1235,10 +1201,6 @@ export class BatchArray {
|
|
|
1235
1201
|
// does this tenantnode/batch have nothing to sync?
|
|
1236
1202
|
let bTotalCountZero: boolean = false;
|
|
1237
1203
|
let bCurrentCountZero: boolean = false;
|
|
1238
|
-
// queue stats (better completion signal than only written/total)
|
|
1239
|
-
let totalInMainQueue: number | null = null;
|
|
1240
|
-
let totalInWriterQueue: number | null = null;
|
|
1241
|
-
let totalInDeferredQueue: number | null = null;
|
|
1242
1204
|
for (let j = 0; j < statskeys.length; j++) {
|
|
1243
1205
|
let bTotalCount = statskeys[j].endsWith("TotalCount");
|
|
1244
1206
|
let bCurrentCount = statskeys[j].endsWith("CurrentCount");
|
|
@@ -1292,16 +1254,6 @@ export class BatchArray {
|
|
|
1292
1254
|
}
|
|
1293
1255
|
tenantNode.nothingtosync = bTotalCountZero && bCurrentCountZero;
|
|
1294
1256
|
|
|
1295
|
-
// queue stats are global for the batch
|
|
1296
|
-
if (statskeys[j] === "TotalInMainQueue") {
|
|
1297
|
-
totalInMainQueue = Number(statsvalues[j]);
|
|
1298
|
-
}
|
|
1299
|
-
if (statskeys[j] === "TotalInWriterQueue") {
|
|
1300
|
-
totalInWriterQueue = Number(statsvalues[j]);
|
|
1301
|
-
}
|
|
1302
|
-
if (statskeys[j] === "TotalInDeferredQueue") {
|
|
1303
|
-
totalInDeferredQueue = Number(statsvalues[j]);
|
|
1304
|
-
}
|
|
1305
1257
|
|
|
1306
1258
|
if (statskeys[j].startsWith("Writer")) {
|
|
1307
1259
|
// parse tid from Writer key
|
|
@@ -1379,23 +1331,17 @@ export class BatchArray {
|
|
|
1379
1331
|
readerExcluded += sourceTenantNode.excluded;
|
|
1380
1332
|
});
|
|
1381
1333
|
|
|
1382
|
-
//
|
|
1383
|
-
//
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const queuesEmpty = this.hasQueueInfo &&
|
|
1390
|
-
this.lastQueues.main === 0 &&
|
|
1391
|
-
this.lastQueues.writer === 0 &&
|
|
1392
|
-
this.lastQueues.deferred === 0;
|
|
1334
|
+
// New completion condition: sync is complete when:
|
|
1335
|
+
// (Reader/TotalCount - Reader/ExtCount) === (Writer/CurrentCount + Writer/DeferredCount)
|
|
1336
|
+
// This means all users that should be written (total minus excluded) have been processed.
|
|
1337
|
+
const usersToSync = readerTotal - readerExcluded;
|
|
1338
|
+
const usersProcessed = writerCurrent + writerDeferred;
|
|
1339
|
+
const isSyncCompleted = usersToSync > 0 && usersToSync === usersProcessed;
|
|
1393
1340
|
|
|
1394
|
-
if (
|
|
1341
|
+
if (isSyncCompleted) {
|
|
1395
1342
|
bWritingComplete = true;
|
|
1396
1343
|
|
|
1397
|
-
//
|
|
1398
|
-
// We trust the backend queues more than client-side totals in this scenario.
|
|
1344
|
+
// Mark all nodes as complete since sync condition is satisfied.
|
|
1399
1345
|
this.tenantNodes.forEach((src) => {
|
|
1400
1346
|
src.targets.forEach((t) => {
|
|
1401
1347
|
if (t.status !== "failed") t.status = "complete";
|
|
@@ -1450,8 +1396,8 @@ export class BatchArray {
|
|
|
1450
1396
|
setRefreshDeltaTrigger(config!.workspaceId);
|
|
1451
1397
|
}
|
|
1452
1398
|
// with that out of the way, is writing complete?
|
|
1453
|
-
//
|
|
1454
|
-
if (bWritingComplete &&
|
|
1399
|
+
// Completion is determined by the new formula: usersToSync === usersProcessed
|
|
1400
|
+
if (bWritingComplete && isSyncCompleted) {
|
|
1455
1401
|
this.milestoneArray.write(setMilestones);
|
|
1456
1402
|
this.stopPolling();
|
|
1457
1403
|
setConfigSyncResult("sync complete");
|
|
@@ -1684,152 +1630,14 @@ export class ActorNode {
|
|
|
1684
1630
|
}
|
|
1685
1631
|
}
|
|
1686
1632
|
// ======================= Azure AD Graph API ===============================
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
user = user;
|
|
1690
|
-
return "Group.Read.All User.Read.All openid profile offline_access User.Read Contacts.Read CrossTenantInformation.ReadBasic.All";
|
|
1633
|
+
export async function groupsGet(_instance: IPublicClientApplication, _user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
|
|
1634
|
+
return await secureApiClient.groupsGet(groupSearchString);
|
|
1691
1635
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
instance: IPublicClientApplication,
|
|
1695
|
-
user: User
|
|
1696
|
-
): Promise<Headers> {
|
|
1697
|
-
const headers = new Headers();
|
|
1698
|
-
headers.append("Content-Type", "application/json");
|
|
1699
|
-
headers.append("accept", "*/*");
|
|
1700
|
-
const graphAPIScope: string = getGraphAPIScope(user);
|
|
1701
|
-
// only call acquireTokenByCode if we have never redeemed the code
|
|
1702
|
-
if (user.graphAccessToken == null || user.graphAccessToken === "") {
|
|
1703
|
-
try {
|
|
1704
|
-
let response: AuthenticationResult = await instance.acquireTokenByCode({
|
|
1705
|
-
code: user.spacode,
|
|
1706
|
-
});
|
|
1707
|
-
user.graphAccessToken = response.accessToken; // cache access token
|
|
1708
|
-
console.log("Front end token acquired by code: " + user.graphAccessToken.slice(0, 20));
|
|
1709
|
-
}
|
|
1710
|
-
catch (error: any) {
|
|
1711
|
-
console.log("Front end token failure: " + error);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
// otherwise, call acquireTokenSilent and deal with token expiration on exception
|
|
1715
|
-
else {
|
|
1716
|
-
try {
|
|
1717
|
-
let accounts: AccountInfo[] = instance.getAllAccounts();
|
|
1718
|
-
let homeAccountId = user.oid + "." + user.tid;
|
|
1719
|
-
let account: AccountInfo | undefined | null = null;
|
|
1720
|
-
for (let i: number = 0; i < accounts.length; i++) {
|
|
1721
|
-
if (accounts[i].homeAccountId == homeAccountId) {
|
|
1722
|
-
account = accounts[i];
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
let response: AuthenticationResult = await instance.acquireTokenSilent({
|
|
1726
|
-
scopes: [graphAPIScope],
|
|
1727
|
-
account: account!
|
|
1728
|
-
});
|
|
1729
|
-
user.graphAccessToken = response.accessToken; // cache access token
|
|
1730
|
-
console.log("Front end token graph acquired silently: " + user.graphAccessToken.slice(0, 20));
|
|
1731
|
-
}
|
|
1732
|
-
catch (error: any) {
|
|
1733
|
-
try {
|
|
1734
|
-
console.log("Front end graph token silent acquisition failure: " + error);
|
|
1735
|
-
// fallback to redirect if silent acquisition fails
|
|
1736
|
-
let accounts: AccountInfo[] = instance.getAllAccounts();
|
|
1737
|
-
let homeAccountId = user.oid + "." + user.tid;
|
|
1738
|
-
let account: AccountInfo | null = null;
|
|
1739
|
-
for (let i: number = 0; i < accounts.length; i++) {
|
|
1740
|
-
if (accounts[i].homeAccountId == homeAccountId) {
|
|
1741
|
-
account = accounts[i];
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
// assumption: this redirect will trigger login flow callbacks in program.cs
|
|
1745
|
-
instance.acquireTokenRedirect({
|
|
1746
|
-
scopes: [graphAPIScope],
|
|
1747
|
-
account: account!
|
|
1748
|
-
});
|
|
1749
|
-
}
|
|
1750
|
-
catch (error: any) {
|
|
1751
|
-
console.log("Front end graph token redirect acquisition failure: " + error);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
headers.append("Authorization", `Bearer ${user.graphAccessToken}`);
|
|
1756
|
-
return headers;
|
|
1757
|
-
}
|
|
1758
|
-
export async function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
|
|
1759
|
-
// need a logged in user to get graph users
|
|
1760
|
-
if (user == null || user.spacode == "") {
|
|
1761
|
-
return { groups: [], error: `500: invalid user passed to groupsGet` };
|
|
1762
|
-
}
|
|
1763
|
-
// create headers
|
|
1764
|
-
const headers = await graphDefineHeaders(instance, user);
|
|
1765
|
-
let options = { method: "GET", headers: headers };
|
|
1766
|
-
// make /groups endpoint call
|
|
1767
|
-
try {
|
|
1768
|
-
let groupsEndpoint: string = getGraphEndpoint(user.authority) + graphConfig.graphGroupsPredicate;
|
|
1769
|
-
groupsEndpoint += `/?$filter=startsWith(displayName, '${groupSearchString}')`;
|
|
1770
|
-
let response = await fetch(groupsEndpoint, options);
|
|
1771
|
-
let data = await response.json();
|
|
1772
|
-
if (typeof data.error !== "undefined") {
|
|
1773
|
-
return { groups: [], error: `${data.error.code}: ${data.error.message}` };
|
|
1774
|
-
}
|
|
1775
|
-
return { groups: data.value, error: `` };
|
|
1776
|
-
}
|
|
1777
|
-
catch (error: any) {
|
|
1778
|
-
console.log(error);
|
|
1779
|
-
return { groups: [], error: `Exception: ${error}` };
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
export async function oauth2PermissionGrantsGet(options: RequestInit, user: User, spid: string, oid: string): Promise<{ grants: string | null, id: string | null, error: string }> {
|
|
1783
|
-
try {
|
|
1784
|
-
// make /oauth2PermissionGrants endpoint call
|
|
1785
|
-
let spurl: string = getGraphEndpoint(user.authority) + graphConfig.graphOauth2PermissionGrantsPredicate;
|
|
1786
|
-
let url: URL = new URL(spurl);
|
|
1787
|
-
url.searchParams.append("$filter", `resourceId eq '${spid}' and consentType eq 'Principal' and principalId eq '${oid}'`);
|
|
1788
|
-
let response = await fetch(url.href, options);
|
|
1789
|
-
let data = await response.json();
|
|
1790
|
-
if (typeof data.error != "undefined") {
|
|
1791
|
-
return { grants: null, id: null, error: `${data.error.code}: ${data.error.message}` };
|
|
1792
|
-
}
|
|
1793
|
-
// we assume there is only one such grant
|
|
1794
|
-
if (data.value.length != 1) {
|
|
1795
|
-
debugger;
|
|
1796
|
-
return { grants: null, id: null, error: `oauth2PermissionGrantsGet: more than one matching delegated consent grant.` };
|
|
1797
|
-
}
|
|
1798
|
-
return { grants: data.value[0].scope, id: data.value[0].id, error: `` };
|
|
1799
|
-
}
|
|
1800
|
-
catch (error: any) {
|
|
1801
|
-
console.log(error);
|
|
1802
|
-
return { grants: null, id: null, error: `Exception: ${error}` };
|
|
1803
|
-
}
|
|
1636
|
+
export async function oauth2PermissionGrantsGet(_options: RequestInit, _user: User, spid: string, oid: string): Promise<{ grants: string | null, id: string | null, error: string }> {
|
|
1637
|
+
return await secureApiClient.oauth2PermissionGrantsGet(spid, oid);
|
|
1804
1638
|
}
|
|
1805
|
-
export async function oauth2PermissionGrantsSet(
|
|
1806
|
-
|
|
1807
|
-
if (loggedInUser == null || loggedInUser.spacode == "") {
|
|
1808
|
-
return false;
|
|
1809
|
-
}
|
|
1810
|
-
// make /oauth2PermissionGrants endpoint call
|
|
1811
|
-
try {
|
|
1812
|
-
let grantsurl: string = getGraphEndpoint(loggedInUser.authority);
|
|
1813
|
-
grantsurl += graphConfig.graphOauth2PermissionGrantsPredicate + `/${id}`;
|
|
1814
|
-
let scopesBody: string = `{ "scope": "${scopes}" }`;
|
|
1815
|
-
const headers = await graphDefineHeaders(instance, loggedInUser);
|
|
1816
|
-
let options: RequestInit = { method: "PATCH", headers: headers, body: scopesBody };
|
|
1817
|
-
let response = await fetch(grantsurl, options);
|
|
1818
|
-
let data = await response.json();
|
|
1819
|
-
if (response.status == 204 && response.statusText == "No Content") {
|
|
1820
|
-
return true;
|
|
1821
|
-
}
|
|
1822
|
-
else {
|
|
1823
|
-
debugger;
|
|
1824
|
-
console.log(`oauth2PermissionGrantsSet: PATCH failed ${data.error.code}: ${data.error.message}`);
|
|
1825
|
-
return false;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
catch (error: any) {
|
|
1829
|
-
debugger;
|
|
1830
|
-
console.log(error);
|
|
1831
|
-
return false;
|
|
1832
|
-
}
|
|
1639
|
+
export async function oauth2PermissionGrantsSet(_instance: IPublicClientApplication, _loggedInUser: User, id: string, scopes: string): Promise<boolean> {
|
|
1640
|
+
return await secureApiClient.oauth2PermissionGrantsSet(id, scopes);
|
|
1833
1641
|
}
|
|
1834
1642
|
export function requestAdminConsent(admin: User, tct: TenantConfigType): void {
|
|
1835
1643
|
//
|
|
@@ -1860,27 +1668,8 @@ export function requestAdminConsent(admin: User, tct: TenantConfigType): void {
|
|
|
1860
1668
|
url.searchParams.append("login_hint", admin.mail);
|
|
1861
1669
|
window.location.assign(url.href);
|
|
1862
1670
|
}
|
|
1863
|
-
export async function servicePrincipalGet(
|
|
1864
|
-
|
|
1865
|
-
// make /servicePrincipals endpoint call to get the Service Principal ID
|
|
1866
|
-
let spurl: string = getGraphEndpoint(user.authority);
|
|
1867
|
-
spurl += graphConfig.graphServicePrincipalsPredicate;
|
|
1868
|
-
spurl += `(appId='${appid}')`;
|
|
1869
|
-
let url: URL = new URL(spurl);
|
|
1870
|
-
url.searchParams.append("$select", "id,appId,displayName");
|
|
1871
|
-
let response = await fetch(url.href, options);
|
|
1872
|
-
let data = await response.json();
|
|
1873
|
-
if (typeof data.error !== "undefined") {
|
|
1874
|
-
return { spid: "", error: `${data.error.code}: ${data.error.message}` };
|
|
1875
|
-
}
|
|
1876
|
-
else {
|
|
1877
|
-
return { spid: data.id, error: `` };
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
catch (error: any) {
|
|
1881
|
-
console.log(error);
|
|
1882
|
-
return { spid: "", error: `Exception: ${error}` };
|
|
1883
|
-
}
|
|
1671
|
+
export async function servicePrincipalGet(_options: RequestInit, _user: User, appid: string): Promise<{ spid: string, error: string }> {
|
|
1672
|
+
return await secureApiClient.servicePrincipalGet(appid);
|
|
1884
1673
|
}
|
|
1885
1674
|
export async function signIn(user: User, tasks: TaskArray): Promise<boolean> {
|
|
1886
1675
|
// admin authority is blank at signIn, lookup authority real-time
|
|
@@ -2171,59 +1960,47 @@ export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean
|
|
|
2171
1960
|
}
|
|
2172
1961
|
return false; // failed, no need for UX to re-render
|
|
2173
1962
|
}
|
|
2174
|
-
export async function userDelegatedScopesGet(
|
|
2175
|
-
|
|
2176
|
-
if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
|
|
2177
|
-
debugger;
|
|
1963
|
+
export async function userDelegatedScopesGet(_instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant): Promise<{ scopes: string | null, id: string | null, error: string }> {
|
|
1964
|
+
if (loggedInUser == null || tenant == null) {
|
|
2178
1965
|
return { scopes: null, id: null, error: `500: invalid parameter(s) passed to getUserDelegatedScopes` };
|
|
2179
1966
|
}
|
|
2180
|
-
// create headers
|
|
2181
|
-
const headers = await graphDefineHeaders(instance, loggedInUser);
|
|
2182
|
-
let options: RequestInit = { method: "GET", headers: headers };
|
|
2183
1967
|
try {
|
|
2184
1968
|
// first, cache Graph resource ID (service principal) for this tenant if we don't have it already
|
|
2185
1969
|
if (tenant.graphSP == "") {
|
|
2186
|
-
let { spid, error } = await servicePrincipalGet(
|
|
1970
|
+
let { spid, error } = await secureApiClient.servicePrincipalGet("00000003-0000-0000-c000-000000000000");
|
|
2187
1971
|
if (error != "") {
|
|
2188
|
-
debugger;
|
|
2189
1972
|
return { scopes: null, id: null, error: `${error}` };
|
|
2190
1973
|
}
|
|
2191
1974
|
tenant.graphSP = spid;
|
|
2192
1975
|
}
|
|
2193
1976
|
// then, retrieve the delegated Graph permissions assigned to this user
|
|
2194
|
-
let { grants, id, error } = await oauth2PermissionGrantsGet(
|
|
1977
|
+
let { grants, id, error } = await secureApiClient.oauth2PermissionGrantsGet(tenant.graphSP, loggedInUser.oid);
|
|
2195
1978
|
if (error != "") {
|
|
2196
|
-
debugger;
|
|
2197
1979
|
return { scopes: null, id: null, error: `${error}` };
|
|
2198
1980
|
}
|
|
2199
1981
|
return { scopes: grants, id: id, error: `` };
|
|
2200
1982
|
}
|
|
2201
1983
|
catch (error: any) {
|
|
2202
|
-
debugger;
|
|
2203
1984
|
console.log(error);
|
|
2204
1985
|
return { scopes: null, id: null, error: `Exception: ${error}` };
|
|
2205
1986
|
}
|
|
2206
1987
|
}
|
|
2207
1988
|
export async function userDelegatedScopesRemove(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant, scope: string): Promise<boolean> {
|
|
2208
|
-
|
|
2209
|
-
if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
|
|
2210
|
-
debugger;
|
|
1989
|
+
if (loggedInUser == null || tenant == null) {
|
|
2211
1990
|
return false;
|
|
2212
1991
|
}
|
|
2213
1992
|
// get current set of delegated scopes in order to remove passed scope
|
|
2214
1993
|
let { scopes, id, error } = await userDelegatedScopesGet(instance, loggedInUser, tenant);
|
|
2215
1994
|
if (error != "") {
|
|
2216
|
-
debugger;
|
|
2217
1995
|
console.log(`userDelegatedScopesRemove: cannot find userDelegatedScopes for ${loggedInUser.mail}: ${error}`);
|
|
2218
1996
|
return false;
|
|
2219
1997
|
}
|
|
2220
1998
|
// remove passed scope (case sensitive)
|
|
2221
1999
|
scopes = scopes!.replace(scope, "");
|
|
2222
|
-
// set updated oauth2permissions
|
|
2223
|
-
let removed: boolean = await oauth2PermissionGrantsSet(
|
|
2000
|
+
// set updated oauth2permissions using secure backend API
|
|
2001
|
+
let removed: boolean = await secureApiClient.oauth2PermissionGrantsSet(id!, scopes);
|
|
2224
2002
|
if (!removed) {
|
|
2225
|
-
|
|
2226
|
-
console.log(`userDelegatedScopesRemove: cannot set oauth2PermissionGrants for ${loggedInUser.mail}: ${error}`);
|
|
2003
|
+
console.log(`userDelegatedScopesRemove: cannot set oauth2PermissionGrants for ${loggedInUser.mail}`);
|
|
2227
2004
|
return false;
|
|
2228
2005
|
}
|
|
2229
2006
|
// replace scope array on logged in user
|
|
@@ -2231,33 +2008,8 @@ export async function userDelegatedScopesRemove(instance: IPublicClientApplicati
|
|
|
2231
2008
|
return removed;
|
|
2232
2009
|
}
|
|
2233
2010
|
//usersGet - GET from AAD Users endpoint
|
|
2234
|
-
export async function usersGet(
|
|
2235
|
-
|
|
2236
|
-
if (user == null || user.spacode == "") {
|
|
2237
|
-
return { users: [], error: `500: invalid user passed to usersGet` };
|
|
2238
|
-
}
|
|
2239
|
-
// make /users endpoint call
|
|
2240
|
-
try {
|
|
2241
|
-
// create headers
|
|
2242
|
-
const headers = await graphDefineHeaders(instance, user);
|
|
2243
|
-
let options = { method: "GET", headers: headers };
|
|
2244
|
-
let usersEndpoint = getGraphEndpoint(user.authority);
|
|
2245
|
-
usersEndpoint += graphConfig.graphUsersPredicate;
|
|
2246
|
-
let response = await fetch(usersEndpoint, options);
|
|
2247
|
-
let data = await response.json();
|
|
2248
|
-
if (typeof data.error !== "undefined") {
|
|
2249
|
-
return { users: [], error: `${data.error.code}: ${data.error.message}` };
|
|
2250
|
-
}
|
|
2251
|
-
let users = new Array<string>();
|
|
2252
|
-
for (let user of data.value) {
|
|
2253
|
-
users.push(user.mail);
|
|
2254
|
-
}
|
|
2255
|
-
return { users: users, error: `` };
|
|
2256
|
-
}
|
|
2257
|
-
catch (error: any) {
|
|
2258
|
-
console.log(error);
|
|
2259
|
-
return { users: [], error: `Exception: ${error}` };
|
|
2260
|
-
}
|
|
2011
|
+
export async function usersGet(_instance: IPublicClientApplication, _user: User | undefined, searchString?: string): Promise<{ users: string[], error: string }> {
|
|
2012
|
+
return await secureApiClient.usersGet(searchString);
|
|
2261
2013
|
}
|
|
2262
2014
|
// ======================= Mindline SyncConfig API ===============================
|
|
2263
2015
|
export async function auditConfigAdd(instance: IPublicClientApplication, user: User, ac: AuditConfig, debug: boolean): Promise<APIResult> {
|
|
@@ -2725,147 +2477,28 @@ export async function getPowerBIAccessToken(
|
|
|
2725
2477
|
return accesstoken;
|
|
2726
2478
|
}
|
|
2727
2479
|
// ======================= Azure REST API ===============================
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
instance: IPublicClientApplication,
|
|
2731
|
-
user: User
|
|
2732
|
-
): Promise<Headers> {
|
|
2733
|
-
const headers = new Headers();
|
|
2734
|
-
headers.append("Content-Type", "application/json");
|
|
2735
|
-
headers.append("accept", "*/*");
|
|
2736
|
-
// authorization header - if needed, retrieve and cache access token
|
|
2737
|
-
if (user.azureAccessToken == null || user.azureAccessToken === "") {
|
|
2738
|
-
try {
|
|
2739
|
-
let accounts: AccountInfo[] = instance.getAllAccounts();
|
|
2740
|
-
let homeAccountId = user.oid + "." + user.tid;
|
|
2741
|
-
let account: AccountInfo | null = null;
|
|
2742
|
-
for (let i: number = 0; i < accounts.length; i++) {
|
|
2743
|
-
if (accounts[i].homeAccountId == homeAccountId) {
|
|
2744
|
-
account = accounts[i];
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
let response: AuthenticationResult = await instance.acquireTokenSilent({
|
|
2748
|
-
scopes: ["https://management.azure.com/user_impersonation"],
|
|
2749
|
-
account: account!
|
|
2750
|
-
});
|
|
2751
|
-
user.azureAccessToken = response.accessToken; // cache access token
|
|
2752
|
-
console.log("Front end token acquired silently: " + user.azureAccessToken.slice(0, 20));
|
|
2753
|
-
}
|
|
2754
|
-
catch (error: any) {
|
|
2755
|
-
try {
|
|
2756
|
-
console.log("Front end token silent acquisition failure: " + error);
|
|
2757
|
-
// fallback to redirect if silent acquisition fails
|
|
2758
|
-
let accounts: AccountInfo[] = instance.getAllAccounts();
|
|
2759
|
-
let homeAccountId = user.oid + "." + user.tid;
|
|
2760
|
-
let account: AccountInfo | null = null;
|
|
2761
|
-
for (let i: number = 0; i < accounts.length; i++) {
|
|
2762
|
-
if (accounts[i].homeAccountId == homeAccountId) {
|
|
2763
|
-
account = accounts[i];
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
instance.acquireTokenRedirect({
|
|
2767
|
-
scopes: ["https://management.azure.com/user_impersonation"],
|
|
2768
|
-
account: account!
|
|
2769
|
-
});
|
|
2770
|
-
}
|
|
2771
|
-
catch (error: any) {
|
|
2772
|
-
console.log("Front end token popup acquisition failure: " + error);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
headers.append("Authorization", `Bearer ${user.azureAccessToken}`);
|
|
2777
|
-
return headers;
|
|
2480
|
+
export async function canListRootAssignments(_instance: IPublicClientApplication, _user: User): Promise<boolean> {
|
|
2481
|
+
return await secureApiClient.canListRootAssignments();
|
|
2778
2482
|
}
|
|
2779
|
-
export async function
|
|
2780
|
-
|
|
2781
|
-
if (user == null || user.spacode == "") {
|
|
2782
|
-
return false;
|
|
2783
|
-
}
|
|
2784
|
-
// make Azure REST API call
|
|
2785
|
-
try {
|
|
2786
|
-
// create headers
|
|
2787
|
-
const headers = await azureDefineHeaders(instance, user);
|
|
2788
|
-
let options = { method: "GET", headers: headers };
|
|
2789
|
-
let listrootassignmentsEndpoint: string = azureConfig.azureListRootAssignments;
|
|
2790
|
-
listrootassignmentsEndpoint += "'";
|
|
2791
|
-
listrootassignmentsEndpoint += user.oid;
|
|
2792
|
-
listrootassignmentsEndpoint += "'";
|
|
2793
|
-
let response = await fetch(listrootassignmentsEndpoint, options);
|
|
2794
|
-
if (response.status == 200) {
|
|
2795
|
-
let data: any = await response.json();
|
|
2796
|
-
data = data;
|
|
2797
|
-
debugger;
|
|
2798
|
-
console.log("Successful call to Azure Resource Graph list root assignments");
|
|
2799
|
-
}
|
|
2800
|
-
else {
|
|
2801
|
-
console.log(await processErrors(response));
|
|
2802
|
-
return false;
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
catch (error: any) {
|
|
2806
|
-
console.log(error);
|
|
2807
|
-
return false;
|
|
2808
|
-
}
|
|
2809
|
-
return true;
|
|
2810
|
-
}
|
|
2811
|
-
export async function elevateGlobalAdminToUserAccessAdmin(instance: IPublicClientApplication, user: User): Promise<boolean> {
|
|
2812
|
-
// need a logged in user to call Azure REST API
|
|
2813
|
-
if (user == null || user.spacode == "") {
|
|
2814
|
-
return false;
|
|
2815
|
-
}
|
|
2816
|
-
// make Azure REST API call
|
|
2817
|
-
try {
|
|
2818
|
-
// create headers
|
|
2819
|
-
const headers = await azureDefineHeaders(instance, user);
|
|
2820
|
-
let options = { method: "POST", headers: headers };
|
|
2821
|
-
let elevateaccessEndpoint: string = azureConfig.azureElevateAccess;
|
|
2822
|
-
let response = await fetch(elevateaccessEndpoint, options);
|
|
2823
|
-
if (response.status == 200) {
|
|
2824
|
-
console.log("Successful call to Azure Resource Graph list root assignments");
|
|
2825
|
-
}
|
|
2826
|
-
else {
|
|
2827
|
-
console.log(await processErrors(response));
|
|
2828
|
-
return false;
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
catch (error: any) {
|
|
2832
|
-
console.log(error);
|
|
2833
|
-
return false;
|
|
2834
|
-
}
|
|
2835
|
-
return true;
|
|
2483
|
+
export async function elevateGlobalAdminToUserAccessAdmin(_instance: IPublicClientApplication, _user: User): Promise<boolean> {
|
|
2484
|
+
return await secureApiClient.elevateGlobalAdminToUserAccessAdmin();
|
|
2836
2485
|
}
|
|
2837
|
-
async function readResources(
|
|
2838
|
-
// need a logged in user to call Azure REST API
|
|
2839
|
-
let resources: ResourceNode[] = new Array<ResourceNode>();
|
|
2840
|
-
if (user == null || user.spacode == "") {
|
|
2841
|
-
return resources;
|
|
2842
|
-
}
|
|
2843
|
-
// make Azure REST API call
|
|
2486
|
+
async function readResources(_instance: IPublicClientApplication, _user: User): Promise<ResourceNode[]> {
|
|
2844
2487
|
try {
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
}
|
|
2859
|
-
else {
|
|
2860
|
-
console.log(await processErrors(response));
|
|
2861
|
-
return resources;
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
catch (error: any) {
|
|
2865
|
-
console.log(error);
|
|
2866
|
-
return resources;
|
|
2488
|
+
const backendResources = await secureApiClient.readResources();
|
|
2489
|
+
|
|
2490
|
+
// Convert backend Resource[] to ResourceNode[]
|
|
2491
|
+
// Note: The original implementation returned empty array, so we map basic fields
|
|
2492
|
+
const resourceNodes: ResourceNode[] = backendResources.map(r => {
|
|
2493
|
+
const node = new ResourceNode(r.type, r.name, 0); // cost not available from backend
|
|
2494
|
+
return node;
|
|
2495
|
+
});
|
|
2496
|
+
|
|
2497
|
+
return resourceNodes;
|
|
2498
|
+
} catch (error: any) {
|
|
2499
|
+
console.error('Error in readResources:', error);
|
|
2500
|
+
return [];
|
|
2867
2501
|
}
|
|
2868
|
-
return resources;
|
|
2869
2502
|
}
|
|
2870
2503
|
// ======================= HYBRIDSPA.TS -- Mindline SyncConfig API helper functions ===============================
|
|
2871
2504
|
function getAPIScope(user: User): string {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|