@robertraaijmakers/pptb-securityplugin 0.1.0 → 0.1.2-beta.1
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/README.md +6 -22
- package/dist/README.md +62 -0
- package/dist/app.js +88 -41
- package/dist/app.js.map +2 -2
- package/dist/index.css +11 -1
- package/dist/index.html +9 -1
- package/dist/npm-shrinkwrap.json +490 -0
- package/package.json +14 -4
- package/docs/index.md +0 -56
package/README.md
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
# Security Roles Explorer
|
|
2
|
-
|
|
3
|
-
Power Platform ToolBox tool for inspecting and managing Dataverse security roles, table privileges, and user assignments.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Security Roles Explorer helps you quickly understand which roles grant access to which tables and lets you update privileges or user-role assignments in one place.
|
|
2
|
+
Power Platform ToolBox tool for inspecting and managing Dataverse security roles, table privileges, and user assignments. Helps you quickly understand which roles grant access to which tables and lets you update privileges or user-role assignments in one place.
|
|
8
3
|
|
|
9
4
|
## Features
|
|
10
|
-
|
|
11
5
|
- View role privileges by role or by table
|
|
12
6
|
- Batch cache of role privilege data for faster loading
|
|
13
7
|
- Sort and filter by privilege level
|
|
@@ -19,12 +13,10 @@ Security Roles Explorer helps you quickly understand which roles grant access to
|
|
|
19
13
|
- Light/dark theme support with a manual toggle
|
|
20
14
|
|
|
21
15
|
## Requirements
|
|
22
|
-
|
|
23
16
|
- Power Platform ToolBox (desktop app)
|
|
24
17
|
- Node.js 18+ (recommended)
|
|
25
18
|
|
|
26
19
|
## Development
|
|
27
|
-
|
|
28
20
|
Install dependencies:
|
|
29
21
|
|
|
30
22
|
```bash
|
|
@@ -43,24 +35,18 @@ Watch mode:
|
|
|
43
35
|
npm run build:watch
|
|
44
36
|
```
|
|
45
37
|
|
|
46
|
-
## Loading in ToolBox
|
|
47
|
-
|
|
38
|
+
## Loading in ToolBox (for developers)
|
|
48
39
|
1. Enable Debug Menu in ToolBox settings.
|
|
49
40
|
2. Use Debug > Load Local Tool and select this folder.
|
|
50
41
|
3. Close and reopen the tool to refresh changes.
|
|
51
42
|
|
|
52
43
|
## Usage
|
|
53
|
-
|
|
54
44
|
1. Select a connection in ToolBox.
|
|
55
|
-
2.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
3. Use the filters and sort controls to narrow results.
|
|
59
|
-
4. Change privilege levels in the grid and click Apply changes.
|
|
60
|
-
5. Use the Assign security roles tab to add/remove roles for users.
|
|
45
|
+
2. Use the filters and sort controls to narrow results.
|
|
46
|
+
3. Change privilege levels in the grid and click Apply changes.
|
|
47
|
+
4. Use the Assign security roles tab to add/remove roles for users.
|
|
61
48
|
|
|
62
49
|
## Contributing
|
|
63
|
-
|
|
64
50
|
1. Fork the repo.
|
|
65
51
|
2. Create a feature branch.
|
|
66
52
|
3. Make changes with small, focused commits.
|
|
@@ -68,11 +54,9 @@ npm run build:watch
|
|
|
68
54
|
5. Open a pull request with a clear description and screenshots if UI changes.
|
|
69
55
|
|
|
70
56
|
## Troubleshooting
|
|
71
|
-
|
|
72
57
|
- If the tool shows "Not connected", select a connection in ToolBox and reload.
|
|
73
58
|
- If role privileges fail to load, verify the connection user has the required security role permissions.
|
|
74
59
|
- If UI changes do not appear, ensure `npm run build` completed and reopen the tool.
|
|
75
60
|
|
|
76
61
|
## License
|
|
77
|
-
|
|
78
|
-
MIT
|
|
62
|
+
MIT
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Security Roles Explorer
|
|
2
|
+
Power Platform ToolBox tool for inspecting and managing Dataverse security roles, table privileges, and user assignments. Helps you quickly understand which roles grant access to which tables and lets you update privileges or user-role assignments in one place.
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
- View role privileges by role or by table
|
|
6
|
+
- Batch cache of role privilege data for faster loading
|
|
7
|
+
- Sort and filter by privilege level
|
|
8
|
+
- Rights filter (all / with rights / without rights)
|
|
9
|
+
- Role multi-select filter (entity mode)
|
|
10
|
+
- Apply or undo pending privilege changes
|
|
11
|
+
- Assign or remove roles for users (role -> users or user -> roles)
|
|
12
|
+
- Activity log with timestamps
|
|
13
|
+
- Light/dark theme support with a manual toggle
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
- Power Platform ToolBox (desktop app)
|
|
17
|
+
- Node.js 18+ (recommended)
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
Install dependencies:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Build once:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Watch mode:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run build:watch
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Loading in ToolBox (for developers)
|
|
39
|
+
1. Enable Debug Menu in ToolBox settings.
|
|
40
|
+
2. Use Debug > Load Local Tool and select this folder.
|
|
41
|
+
3. Close and reopen the tool to refresh changes.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
1. Select a connection in ToolBox.
|
|
45
|
+
2. Use the filters and sort controls to narrow results.
|
|
46
|
+
3. Change privilege levels in the grid and click Apply changes.
|
|
47
|
+
4. Use the Assign security roles tab to add/remove roles for users.
|
|
48
|
+
|
|
49
|
+
## Contributing
|
|
50
|
+
1. Fork the repo.
|
|
51
|
+
2. Create a feature branch.
|
|
52
|
+
3. Make changes with small, focused commits.
|
|
53
|
+
4. Run `npm run build` and verify in ToolBox.
|
|
54
|
+
5. Open a pull request with a clear description and screenshots if UI changes.
|
|
55
|
+
|
|
56
|
+
## Troubleshooting
|
|
57
|
+
- If the tool shows "Not connected", select a connection in ToolBox and reload.
|
|
58
|
+
- If role privileges fail to load, verify the connection user has the required security role permissions.
|
|
59
|
+
- If UI changes do not appear, ensure `npm run build` completed and reopen the tool.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
MIT
|
package/dist/app.js
CHANGED
|
@@ -14545,6 +14545,14 @@ ${logTarget.textContent}`;
|
|
|
14545
14545
|
|
|
14546
14546
|
// src/services/dataverseService.ts
|
|
14547
14547
|
var dataverseAPI = window.dataverseAPI;
|
|
14548
|
+
function buildPrivilegeReference(privilegeId) {
|
|
14549
|
+
const apiEndpoint = dataverseAPI?.apiEndpoint;
|
|
14550
|
+
if (!apiEndpoint) {
|
|
14551
|
+
return `privileges(${privilegeId})`;
|
|
14552
|
+
}
|
|
14553
|
+
const separator = apiEndpoint.endsWith("/") ? "" : "/";
|
|
14554
|
+
return `${apiEndpoint}${separator}privileges(${privilegeId})`;
|
|
14555
|
+
}
|
|
14548
14556
|
async function queryAll(odataQuery) {
|
|
14549
14557
|
const all = [];
|
|
14550
14558
|
let response = await dataverseAPI.queryData(odataQuery);
|
|
@@ -14675,6 +14683,35 @@ ${logTarget.textContent}`;
|
|
|
14675
14683
|
const response = await dataverseAPI.queryData(query);
|
|
14676
14684
|
return response?.RolePrivileges ?? response?.rolePrivileges ?? response?.value ?? [];
|
|
14677
14685
|
}
|
|
14686
|
+
async function addPrivilegesToRole(roleId, privileges) {
|
|
14687
|
+
if (privileges.length === 0) {
|
|
14688
|
+
return;
|
|
14689
|
+
}
|
|
14690
|
+
await dataverseAPI.execute({
|
|
14691
|
+
entityName: "role",
|
|
14692
|
+
entityId: roleId,
|
|
14693
|
+
operationName: "AddPrivilegesRole",
|
|
14694
|
+
operationType: "action",
|
|
14695
|
+
parameters: {
|
|
14696
|
+
Privileges: privileges
|
|
14697
|
+
}
|
|
14698
|
+
});
|
|
14699
|
+
}
|
|
14700
|
+
async function removePrivilegesFromRole(roleId, privilegeId) {
|
|
14701
|
+
if (!privilegeId) {
|
|
14702
|
+
return;
|
|
14703
|
+
}
|
|
14704
|
+
const privilegeReference = buildPrivilegeReference(privilegeId);
|
|
14705
|
+
await dataverseAPI.execute({
|
|
14706
|
+
entityName: "role",
|
|
14707
|
+
entityId: roleId,
|
|
14708
|
+
operationName: "RemovePrivilegeRole",
|
|
14709
|
+
operationType: "action",
|
|
14710
|
+
parameters: {
|
|
14711
|
+
Privilege: privilegeReference
|
|
14712
|
+
}
|
|
14713
|
+
});
|
|
14714
|
+
}
|
|
14678
14715
|
async function associateRoleToUser(userId, roleId) {
|
|
14679
14716
|
await dataverseAPI.associate(
|
|
14680
14717
|
"systemuser",
|
|
@@ -14741,6 +14778,7 @@ ${logTarget.textContent}`;
|
|
|
14741
14778
|
loadingRolesMetadata: "Loading roles and metadata",
|
|
14742
14779
|
loadingRolePrivileges: "Loading role privileges",
|
|
14743
14780
|
loadingRefreshingPrivileges: "Refreshing role privileges",
|
|
14781
|
+
loadingApplyingChanges: "Applying privilege changes",
|
|
14744
14782
|
loadingDashboardData: "Loading dashboard data",
|
|
14745
14783
|
loadingDashboardRolesTeams: "Loading roles and teams",
|
|
14746
14784
|
loadingDashboardPeople: "Loading users, business units, and team memberships",
|
|
@@ -14791,10 +14829,6 @@ ${logTarget.textContent}`;
|
|
|
14791
14829
|
title: "Update failed",
|
|
14792
14830
|
body: "Failed to apply privilege updates. See console for details."
|
|
14793
14831
|
},
|
|
14794
|
-
updated: {
|
|
14795
|
-
title: "Privileges updated",
|
|
14796
|
-
body: "Your changes have been queued for update."
|
|
14797
|
-
},
|
|
14798
14832
|
cacheFailed: {
|
|
14799
14833
|
title: "Cache failed",
|
|
14800
14834
|
body: "Could not cache role privileges. See console for details."
|
|
@@ -14846,9 +14880,6 @@ ${logTarget.textContent}`;
|
|
|
14846
14880
|
function formatMissingPrivilegeId(entityLogicalName, privilege) {
|
|
14847
14881
|
return `Missing privilege ID for ${entityLogicalName}:${privilege}`;
|
|
14848
14882
|
}
|
|
14849
|
-
function formatQueuedPrivilegeChange(privilege, entityLogicalName, level) {
|
|
14850
|
-
return `Queued ${privilege} change for ${entityLogicalName}: ${level}`;
|
|
14851
|
-
}
|
|
14852
14883
|
function formatNoPrivilegesForRole(roleName) {
|
|
14853
14884
|
return `No privileges returned for role ${roleName}.`;
|
|
14854
14885
|
}
|
|
@@ -14944,6 +14975,7 @@ ${logTarget.textContent}`;
|
|
|
14944
14975
|
direction: "asc"
|
|
14945
14976
|
},
|
|
14946
14977
|
assignmentFilter: "",
|
|
14978
|
+
assignmentSearch: "",
|
|
14947
14979
|
sort: {
|
|
14948
14980
|
column: "label",
|
|
14949
14981
|
direction: "asc"
|
|
@@ -15004,6 +15036,7 @@ ${logTarget.textContent}`;
|
|
|
15004
15036
|
document.querySelectorAll("[data-assign-sort]")
|
|
15005
15037
|
),
|
|
15006
15038
|
assignmentFilterAssigned: document.getElementById("assignment-filter-assigned"),
|
|
15039
|
+
assignmentSearch: document.getElementById("assignment-search"),
|
|
15007
15040
|
controlsPrivileges: document.getElementById("controls-privileges"),
|
|
15008
15041
|
controlsAssignments: document.getElementById("controls-assignments"),
|
|
15009
15042
|
controlsDashboard: document.getElementById("controls-dashboard"),
|
|
@@ -15424,6 +15457,17 @@ ${logTarget.textContent}`;
|
|
|
15424
15457
|
if (state.assignmentFilter === "not-assigned") {
|
|
15425
15458
|
return !item.assigned;
|
|
15426
15459
|
}
|
|
15460
|
+
if (state.assignmentSearch) {
|
|
15461
|
+
const rawTerm = state.assignmentSearch.toLowerCase();
|
|
15462
|
+
const term = rawTerm.replace(/\*/g, "").trim();
|
|
15463
|
+
if (term) {
|
|
15464
|
+
const labelMatch = item.label.toLowerCase().includes(term);
|
|
15465
|
+
const subLabelMatch = item.subLabel ? item.subLabel.toLowerCase().includes(term) : false;
|
|
15466
|
+
if (!labelMatch && !subLabelMatch) {
|
|
15467
|
+
return false;
|
|
15468
|
+
}
|
|
15469
|
+
}
|
|
15470
|
+
}
|
|
15427
15471
|
return true;
|
|
15428
15472
|
});
|
|
15429
15473
|
return [...filtered].sort((a, b) => {
|
|
@@ -15768,7 +15812,6 @@ ${logTarget.textContent}`;
|
|
|
15768
15812
|
const level = select.value;
|
|
15769
15813
|
const isPending = updatePendingChange(roleId, row.entityLogicalName, privilege, level);
|
|
15770
15814
|
setPendingClass(select, isPending);
|
|
15771
|
-
logMessage(formatQueuedPrivilegeChange(privilege, row.entityLogicalName, level));
|
|
15772
15815
|
});
|
|
15773
15816
|
select.disabled = !isRoleMode && !row.roleId;
|
|
15774
15817
|
applyLevelClass(select, select.value);
|
|
@@ -15929,18 +15972,18 @@ ${logTarget.textContent}`;
|
|
|
15929
15972
|
}
|
|
15930
15973
|
return "none";
|
|
15931
15974
|
}
|
|
15932
|
-
function
|
|
15975
|
+
function mapPrivilegeDepthLabel(level) {
|
|
15933
15976
|
switch (level) {
|
|
15934
15977
|
case "user":
|
|
15935
|
-
return
|
|
15978
|
+
return "Basic";
|
|
15936
15979
|
case "businessUnit":
|
|
15937
|
-
return
|
|
15980
|
+
return "Local";
|
|
15938
15981
|
case "parentChild":
|
|
15939
|
-
return
|
|
15982
|
+
return "Deep";
|
|
15940
15983
|
case "organization":
|
|
15941
|
-
return
|
|
15984
|
+
return "Global";
|
|
15942
15985
|
default:
|
|
15943
|
-
return
|
|
15986
|
+
return "None";
|
|
15944
15987
|
}
|
|
15945
15988
|
}
|
|
15946
15989
|
function mapOwnershipLabel(raw) {
|
|
@@ -17266,42 +17309,44 @@ ${logTarget.textContent}`;
|
|
|
17266
17309
|
if (currentLevel === change.level) {
|
|
17267
17310
|
continue;
|
|
17268
17311
|
}
|
|
17269
|
-
if (
|
|
17312
|
+
if (change.level === "none") {
|
|
17270
17313
|
if (!removesByRole.has(change.roleId)) {
|
|
17271
17314
|
removesByRole.set(change.roleId, []);
|
|
17272
17315
|
}
|
|
17273
17316
|
removesByRole.get(change.roleId).push(privilegeId);
|
|
17274
|
-
}
|
|
17275
|
-
if (change.level !== "none") {
|
|
17317
|
+
} else {
|
|
17276
17318
|
if (!addsByRole.has(change.roleId)) {
|
|
17277
17319
|
addsByRole.set(change.roleId, []);
|
|
17278
17320
|
}
|
|
17321
|
+
const privilegeInfo = state.privilegeInfoById.get(privilegeId);
|
|
17279
17322
|
addsByRole.get(change.roleId).push({
|
|
17280
17323
|
PrivilegeId: privilegeId,
|
|
17281
|
-
Depth:
|
|
17324
|
+
Depth: mapPrivilegeDepthLabel(change.level),
|
|
17325
|
+
PrivilegeName: privilegeInfo?.name
|
|
17282
17326
|
});
|
|
17283
17327
|
}
|
|
17284
17328
|
}
|
|
17329
|
+
const totalRemoveCalls = Array.from(removesByRole.values()).reduce(
|
|
17330
|
+
(total, privilegeIds) => total + privilegeIds.length,
|
|
17331
|
+
0
|
|
17332
|
+
);
|
|
17333
|
+
const totalAddCalls = addsByRole.size;
|
|
17334
|
+
const totalCalls = totalRemoveCalls + totalAddCalls;
|
|
17335
|
+
let completedCalls = 0;
|
|
17336
|
+
setLoading(true, UI_TEXT.loadingApplyingChanges);
|
|
17337
|
+
updateLoadingProgress(0, totalCalls, UI_TEXT.loadingApplyingChanges);
|
|
17285
17338
|
try {
|
|
17286
17339
|
for (const [roleId, privilegeIds] of removesByRole) {
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
PrivilegeIds: privilegeIds
|
|
17293
|
-
}
|
|
17294
|
-
});
|
|
17340
|
+
for (const privilegeId of privilegeIds) {
|
|
17341
|
+
await removePrivilegesFromRole(roleId, privilegeId);
|
|
17342
|
+
completedCalls += 1;
|
|
17343
|
+
updateLoadingProgress(completedCalls, totalCalls, UI_TEXT.loadingApplyingChanges);
|
|
17344
|
+
}
|
|
17295
17345
|
}
|
|
17296
17346
|
for (const [roleId, privileges] of addsByRole) {
|
|
17297
|
-
await
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
parameters: {
|
|
17301
|
-
RoleId: roleId,
|
|
17302
|
-
Privileges: privileges
|
|
17303
|
-
}
|
|
17304
|
-
});
|
|
17347
|
+
await addPrivilegesToRole(roleId, privileges);
|
|
17348
|
+
completedCalls += 1;
|
|
17349
|
+
updateLoadingProgress(completedCalls, totalCalls, UI_TEXT.loadingApplyingChanges);
|
|
17305
17350
|
}
|
|
17306
17351
|
for (const change of state.pendingChanges) {
|
|
17307
17352
|
if (!state.rolePrivileges.has(change.roleId)) {
|
|
@@ -17320,16 +17365,12 @@ ${logTarget.textContent}`;
|
|
|
17320
17365
|
duration: 3500
|
|
17321
17366
|
});
|
|
17322
17367
|
return;
|
|
17368
|
+
} finally {
|
|
17369
|
+
setLoading(false);
|
|
17323
17370
|
}
|
|
17324
17371
|
state.pendingChanges = [];
|
|
17325
|
-
await toolboxAPI2.utils.showNotification({
|
|
17326
|
-
title: NOTIFICATIONS.updated.title,
|
|
17327
|
-
body: NOTIFICATIONS.updated.body,
|
|
17328
|
-
type: "success",
|
|
17329
|
-
duration: 2500
|
|
17330
|
-
});
|
|
17331
17372
|
updatePendingUi();
|
|
17332
|
-
|
|
17373
|
+
await refreshPrivilegeView();
|
|
17333
17374
|
}
|
|
17334
17375
|
async function refreshData() {
|
|
17335
17376
|
if (state.refreshInProgress) {
|
|
@@ -17618,6 +17659,12 @@ ${logTarget.textContent}`;
|
|
|
17618
17659
|
renderAssignmentTable(state.assignmentItems);
|
|
17619
17660
|
});
|
|
17620
17661
|
}
|
|
17662
|
+
if (elements2.assignmentSearch) {
|
|
17663
|
+
elements2.assignmentSearch.addEventListener("input", () => {
|
|
17664
|
+
state.assignmentSearch = elements2.assignmentSearch.value.trim();
|
|
17665
|
+
renderAssignmentTable(state.assignmentItems);
|
|
17666
|
+
});
|
|
17667
|
+
}
|
|
17621
17668
|
for (const button of elements2.sortButtons) {
|
|
17622
17669
|
button.addEventListener("click", () => {
|
|
17623
17670
|
const column = button.dataset.sort || "label";
|