Merge pull request #1728 from russgove/dev

This commit is contained in:
Hugo Bernier 2021-02-23 00:38:05 -05:00 committed by GitHub
commit b58aab06cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 265 additions and 95 deletions

View File

@ -17,7 +17,7 @@ extensions:
## Summary ## Summary
React-securitygrid is an SPFX webpart that uses React and Office-UI-Fabric to render a grid showing which users have access to which lists/libraries/folders/files on a Web as shown here: React-securitygrid is an SPFX web part that uses React and Office-UI-Fabric to render a grid showing which users have access to which lists/libraries/folders/files on a Web as shown here:
![config panel](./src/images/MainDisplay.gif) ![config panel](./src/images/MainDisplay.gif)
@ -80,9 +80,14 @@ The admin can select lists and libraries below to have them included/excluded fr
This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/russgove/SPSecurity. This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/russgove/SPSecurity.
## Used SharePoint Framework Version ## Compatibility
![SPFx 1.10](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
![Node.js LTS 6.x | LTS 8.x](https://img.shields.io/badge/Node.js-LTS%206.x%20%7C%20LTS%208.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
![SPFx 1.10.0](https://img.shields.io/badge/version-1.10-green.svg)
## Applies to ## Applies to
@ -97,12 +102,13 @@ This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/rus
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
react-securitygrid | Russell Gove react-securitygrid | Russell Gove ([@russgove](https://twitter.com/russgove))
## Version history ## Version history
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0.4|February 22, 2021 | Added support for AD groups
1.0.0.3|October 28, 2020 | Update to office-ui-fabric-react 7.148.1, fixing icons and indentation for sub-folders 1.0.0.3|October 28, 2020 | Update to office-ui-fabric-react 7.148.1, fixing icons and indentation for sub-folders
1.0.0.2|April 5, 2021| Updates to SPFx 1.10; Allow display of multiple permissions 1.0.0.2|April 5, 2021| Updates to SPFx 1.10; Allow display of multiple permissions
1.0.0.1|April 25, 2018|Update to SPFx 1.4.1 1.0.0.1|April 25, 2018|Update to SPFx 1.4.1

View File

@ -5,7 +5,7 @@
"isDomainIsolated": false, "isDomainIsolated": false,
"name": "spsecurity-webpart-3-client-side-solution", "name": "spsecurity-webpart-3-client-side-solution",
"id": "788271fb-ee9b-40df-8381-eb3dc70d1982", "id": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"version": "1.0.0.3" "version": "1.0.4.0"
}, },
"paths": { "paths": {
"zippedPackage": "solution/spsecurity-webpart-3.sppkg" "zippedPackage": "solution/spsecurity-webpart-3.sppkg"

View File

@ -1,15 +1,15 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321, "port": 4321,
"initialPage": "https://localhost:5432/workbench", "initialPage": "https://localhost:5432/workbench",
"https": true, "https": true,
"api": { "api": {
"port": 5432, "port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/" "entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}, },
"serveConfigurations": { "serveConfigurations": {
"default": { "default": {
"pageUrl": "https://russellwgove.sharepoint.com/sites/workspaces/_layouts/15/workbench.aspx" "pageUrl": "https://russellwgove.sharepoint.com/sites/testazad/_layouts/15/workbench.aspx"
}
} }
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "spsecurity-webpart-3", "name": "spsecurity-webpart-3",
"version": "0.0.1", "version": "1.0.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1763,6 +1763,11 @@
"isomorphic-fetch": "^2.2.1" "isomorphic-fetch": "^2.2.1"
} }
}, },
"@microsoft/microsoft-graph-types": {
"version": "1.31.0",
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-1.31.0.tgz",
"integrity": "sha512-tx7EmPPaNU4ZYiUuhPYegnI+79Az2djzFmFoolFt/QLbI0jCyLu1OOWi9h7NSLAJo1Z7v4CgsACmy7Ewn4kUUg=="
},
"@microsoft/node-core-library": { "@microsoft/node-core-library": {
"version": "3.15.1", "version": "3.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.15.1.tgz", "resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.15.1.tgz",

View File

@ -1,7 +1,7 @@
{ {
"main": "lib/index.js", "main": "lib/index.js",
"name": "spsecurity-webpart-3", "name": "spsecurity-webpart-3",
"version": "0.0.1", "version": "1.0.4",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -10,6 +10,7 @@
"@types/react": "16.8.8" "@types/react": "16.8.8"
}, },
"dependencies": { "dependencies": {
"@microsoft/microsoft-graph-types": "^1.31.0",
"@pnp/common": "1.3.3", "@pnp/common": "1.3.3",
"@pnp/logging": "1.3.3", "@pnp/logging": "1.3.3",
"@pnp/odata": "1.3.3", "@pnp/odata": "1.3.3",

View File

@ -1,7 +1,11 @@
import { sp } from "@pnp/sp"; import { User } from "@microsoft/microsoft-graph-types";
import { find, indexOf, includes } from "lodash"; import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { IODataUser } from "@microsoft/sp-odata-types";
import { SPPermission } from "@microsoft/sp-page-context"; import { SPPermission } from "@microsoft/sp-page-context";
import { AadHttpClient, HttpClientResponse, IAadHttpClientOptions } from "@microsoft/sp-http"; //import * as MicrosoftGraph from "@microsoft/microsoft-graph-types"
import { sp } from "@pnp/sp";
import { SiteGroup } from "@pnp/sp/src/sitegroups";
import { filter, find, includes, indexOf, lowerCase } from "lodash";
export interface ISPSecurableObject { export interface ISPSecurableObject {
id: number; id: number;
@ -21,7 +25,18 @@ export class SPBasePermissions {
export enum securableType { export enum securableType {
List List
} }
export class ADGroupId {
public ADId: string; // the goid id in azure
public SPId: number; // the numeric id in the sharepoint users list
}
export class ADGroup {
public id: ADGroupId;
public members: Array<User>;
}
export class SPSiteGroup { export class SPSiteGroup {
public id: number; public id: number;
@ -29,15 +44,23 @@ export class SPSiteGroup {
public isHiddenInUI: boolean; public isHiddenInUI: boolean;
public isShareByEmailGuestUse: boolean; public isShareByEmailGuestUse: boolean;
public isSiteAdmin: boolean; public isSiteAdmin: boolean;
public userIds: number[]; public userIds: number[];// to switch to ad groups need to make this a string[] with the UPN
public adGroupIds: ADGroupId[];
public constructor(id: number, title: string) {
this.id = id;
this.title = title;
this.userIds = [];
this.adGroupIds = [];
}
} }
export class SPSiteUser { export class SPSiteUser {
public name: string; public name: string;
public id: number; public id?: number;
public userId: SPExternalUser; public userId?: SPExternalUser;
public upn: string; public upn: string;
public isSelected: boolean; //should user be shown in UI public isSelected: boolean; //should user be shown in UI
public principalType: number; //4=Security group, 1 = user, 2=DL, 8=SP Group public principalType: number; //4=Security group, 1 = user, 2=DL, 8=SP Group, IN HERE A -1 MEANS AD USER
public adId: string;//id if the user in AD
} }
export class SPRoleDefinition { export class SPRoleDefinition {
@ -60,6 +83,7 @@ export class SPSecurityInfo {
public siteUsers: SPSiteUser[]; public siteUsers: SPSiteUser[];
public siteGroups: SPSiteGroup[]; public siteGroups: SPSiteGroup[];
public roleDefinitions: SPRoleDefinition[]; public roleDefinitions: SPRoleDefinition[];
public adGroups: ADGroup[];
public lists: (SPList | SPListItem)[]; public lists: (SPList | SPListItem)[];
public constructor() { public constructor() {
@ -68,7 +92,7 @@ export class SPSecurityInfo {
this.roleDefinitions = new Array<SPRoleDefinition>(); this.roleDefinitions = new Array<SPRoleDefinition>();
this.siteUsers = new Array<SPSiteUser>(); this.siteUsers = new Array<SPSiteUser>();
this.lists = new Array<SPList>(); this.lists = new Array<SPList>();
this.adGroups = new Array<ADGroup>();
} }
} }
@ -100,6 +124,20 @@ export class SPListItem {
public level: number; public level: number;
} }
// export class ADGroup {
// public id: string;
// public parentId: string;
// public listTitle: string;
// public type: string;
// public itemCount: number;
// public title: string;
// public serverRelativeUrl: string;
// public roleAssignments: SPRoleAssignment[];
// public isExpanded: boolean;
// public hasBeenRetrieved: boolean;
// public level: number;
// }
export class SPExternalUser { export class SPExternalUser {
public nameId: string; public nameId: string;
public nameIdIssuer: string; public nameIdIssuer: string;
@ -111,21 +149,20 @@ export class SPRoleAssignment {
} }
export class Helpers { export class Helpers {
public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups): boolean { public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups, adGroups: ADGroup[]): boolean {
for (var securableObject of securableObjects) { for (var securableObject of securableObjects) {
for (var requestedpermission of requestedpermissions) { for (var requestedpermission of requestedpermissions) {
if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups)) { if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups, adGroups)) {
return true; return true;
} }
} }
} }
return false; return false;
} }
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups) { public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups, adGroups: ADGroup[]) {
console.log(`check user ${user.name} obj ${securableObject.title} perm ${requestedpermission.value.High}${requestedpermission.value.Low} `);
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups); const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups, adGroups);
for (const permission of permissions) { for (const permission of permissions) {
if ( if (
((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low)) ((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low))
@ -158,22 +195,16 @@ export class Helpers {
// } // }
return basePermissions; return basePermissions;
} }
public static getUserPermissionsForObject(securableObject, user, roles: SPRoleDefinition[], siteGroups: SPSiteGroup[]) { public static getUserPermissionsForObject(securableObject, user, roles: SPRoleDefinition[], siteGroups: SPSiteGroup[], adGroups: ADGroup[]) {
const roleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups); const userRoleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups, adGroups);
let roleDefinitionIds: number[] = []; let roleDefinitionIds: number[] = [];
for (const roleAssignment of roleAssignments) { for (const roleAssignment of userRoleAssignments) {
for (const roleDefinitionID of roleAssignment.roleDefinitionIds) { for (const roleDefinitionID of roleAssignment.roleDefinitionIds) {
roleDefinitionIds.push(roleDefinitionID); roleDefinitionIds.push(roleDefinitionID);
} }
} }
// for (var rax = 0; rax < roleAssignments.length; rax++) {
// for (var rdx = 0; rdx < roleAssignments[rax].roleDefinitionIds.length; rdx++) {
// roleDefinitionIds.push(roleAssignments[rax].roleDefinitionIds[rdx]);
// }
// }
var userPermissions = Helpers.getBasePermissionsForRoleDefinitiuonIds(roleDefinitionIds, roles); var userPermissions = Helpers.getBasePermissionsForRoleDefinitiuonIds(roleDefinitionIds, roles);
return userPermissions; return userPermissions;
@ -186,17 +217,39 @@ export class Helpers {
let group: SPSiteGroup = this.findGroup(groupId, groups); let group: SPSiteGroup = this.findGroup(groupId, groups);
return includes(group.userIds, userId); return includes(group.userIds, userId);
} }
public static userIsInGroupsNestAdGroups(userAdId: String, groupId: number, groups: SPSiteGroup[], adGroups: ADGroup[]): boolean {
let group: SPSiteGroup = this.findGroup(groupId, groups);
debugger;
for (var adGrouId of group.adGroupIds) {
var adGroup = find(adGroups, (adg) => { return adg.id === adGrouId; });
if (adGroup) {
if (find(adGroup.members, (aduser) => { return aduser.id === userAdId; })) {
return true;
}
} else {
debugger;
alert(`adGroup ${ADGroupId} was not in the collection of ad groups.`);
}
}
}
public static GetRoleAssignmentsForUser(securableObject: ISPSecurableObject, user: SPSiteUser, public static GetRoleAssignmentsForUser(securableObject: ISPSecurableObject, user: SPSiteUser,
groups: SPSiteGroup[]): SPRoleAssignment[] { groups: SPSiteGroup[], adGroups: ADGroup[]): SPRoleAssignment[] {
try { try {
let selectedRoleAssignments: SPRoleAssignment[] = []; let selectedRoleAssignments: SPRoleAssignment[] = [];
// for each role assignment, if the user is in the group, or its for this user, add it to his roleassignments
for (const roleAssignment of securableObject.roleAssignments) { for (const roleAssignment of securableObject.roleAssignments) {
let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; }); let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; });
if (group) { if (group) {
if (this.userIsInGroup(user.id, group.id, groups)) { if (this.userIsInGroup(user.id, group.id, groups)) { // this tests if a user is directly in the SP GROUP
selectedRoleAssignments.push(roleAssignment); selectedRoleAssignments.push(roleAssignment);
} }
if (this.userIsInGroupsNestAdGroups(user.adId, group.id, groups, adGroups)) { // this tests if a user is in an ad group thats in the SP GROUP
selectedRoleAssignments.push(roleAssignment);
}
} }
else { else {
// it must be a user // it must be a user
@ -205,6 +258,38 @@ export class Helpers {
} }
} }
} }
debugger;
if (user.adId) { // if user is referenced in any groups, we stored his ad id in his user record
for (var adgroup of adGroups) {// for all adGroups
if (find(adgroup.members, (member) => {
return member.id === user.adId;
}) != -1) { // if user is in the adgroup
// for each role assignment, if the adGroup is in the group, or its for this adgroup, add it to his roleassignments
for (const roleAssignment of securableObject.roleAssignments) {
if (adgroup.id.SPId === roleAssignment.principalId) {
selectedRoleAssignments.push(roleAssignment);
}
// debugger;
// let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; });
// if (group) {
// if (this.userIsInGroup(user.id, group.id, groups)) {
// selectedRoleAssignments.push(roleAssignment);
// }
// }
// else {
// // it must be a user
// if (user.id === roleAssignment.principalId) {
// selectedRoleAssignments.push(roleAssignment);
// }
// }
}
}
}
}
return selectedRoleAssignments; return selectedRoleAssignments;
} catch (exception) { } catch (exception) {
//debugger; //debugger;
@ -321,17 +406,17 @@ export default class SPSecurityService {
let securityInfo: SPSecurityInfo = new SPSecurityInfo(); let securityInfo: SPSecurityInfo = new SPSecurityInfo();
let batch: any = sp.createBatch(); let batch: any = sp.createBatch();
let errors: Array<string> = []; let errors: Array<string> = [];
debugger;
sp.web.siteUsers.inBatch(batch).get() sp.web.siteUsers.inBatch(batch).get()
.then((response) => { .then((response) => {
console.table(response);
securityInfo.siteUsers = response.map((u) => { securityInfo.siteUsers = response.map((u) => {
var upn: string = u.LoginName.split('|')[2];
let user: SPSiteUser = new SPSiteUser(); let user: SPSiteUser = new SPSiteUser();
user.isSelected = true; user.isSelected = true;
user.id = u.Id; user.id = u.Id;
user.name = u.Title; user.name = u.Title;
user.principalType = u.PrincipalType; user.principalType = u.PrincipalType;
user.upn = u.LoginName.split('|')[2]; user.upn = upn ? upn.toLocaleLowerCase() : u.Title;// switching key in react from id to upn. ensure upn is not undefined
if (u.UserId) { if (u.UserId) {
user.userId = new SPExternalUser(); user.userId = new SPExternalUser();
user.userId.nameId = u.UserId.NameId; user.userId.nameId = u.UserId.NameId;
@ -339,7 +424,9 @@ export default class SPSecurityService {
} }
return user; return user;
}); });
return securityInfo.siteUsers;
// securityInfo.siteUsers = securityInfo.siteUsers.filter((su) => { su.upn });
return securityInfo.siteUsers;// dont really need to return this// already set it on securityinfo
}).catch((error) => { }).catch((error) => {
debugger; debugger;
errors.push(`There was an error feting site users -- ${error.message}`); errors.push(`There was an error feting site users -- ${error.message}`);
@ -347,50 +434,47 @@ export default class SPSecurityService {
}); });
sp.web.siteGroups.filter(`Title ne 'Limited Access System Group'`).expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin") sp.web.siteGroups.filter(`Title ne 'Limited Access System Group'`).expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get() .inBatch(batch).get()
.then((response) => { .then(async (response) => {
let AdGroupPromises: Array<Promise<any>> = [];
// if group contains an ad group(PrincipalType=4) expand it // if group contains an ad group(PrincipalType=4) expand it
securityInfo.siteGroups = response.map((grp) => { securityInfo.siteGroups = response.map((grp) => {
let siteGroup: SPSiteGroup = new SPSiteGroup(); //
siteGroup.userIds = []; //IMPORTANT:
siteGroup.id = grp.Id; //For groups created with 'Anyone in the organization with the link'
siteGroup.title = grp.Title; //LoginName: "SharingLinks.cf28991a-7f40-49c8-a68e-f4fa143a094f.OrganizationEdit.2671b36d-1681-4e39-82dc-a9f11166517d",
//
//For groups create with SPecific People
//LoginName: "SharingLinks.3d634d86-7136-4d59-8acf-c87d9a2c7d98.Flexible.9368eb69-6ca4-4b55-85e5-148c3e48e520",
//
//need to check for other options. Seems funny that the one labeled 'Flexible' is for specific people.
//
//So we wil need to add code one day to decipher all thes sharing groups!
let siteGroup: SPSiteGroup = new SPSiteGroup(grp.Id, grp.Title);
for (let user of grp.Users) { for (let user of grp.Users) {
if (user.PrincipalType === 4) { if (user.PrincipalType === 4) { //4=Security group, 1 = user, 2=DL, 8=SP Group
// To make this work with AD groups, I need to stop using the integer UserId of the user as the var adgroupid = new ADGroupId();
// key to the Users list and use UPN/Email instead. Users in an AD group may not have a accessed the site adgroupid.ADId = user.LoginName.split('|')[2];//Loginname s c:0t,c|tenant|grpid for ad groups
// yet , and so will not be in the userinfo list. adgroupid.SPId = user.Id;
// I can get the users from the AD group using the graph HTTPClient. and add them to the Users array siteGroup.adGroupIds.push(adgroupid);
// in my state. Would also need to add a list of AD groups and their members to my state.
// then in DoesUserHavePermission method, Just inlude permissions of any ADQ Groups the user is in.
//
// Also should check for users that have been invited but not yet accessed the site.
// graphHttpClient.get("v1.0/groups?$filter=displayName eq '" + user.Title + "'&$expand=members", GraphHttpClient.configurations.v1).then((response2) => {
// response2.json().then((data) => {
// //debugger;
// });
// }).catch((err) => {
// });
} else { } else {
siteGroup.userIds.push(user.Id); siteGroup.userIds.push(user.Id);
} }
} }
return siteGroup; return siteGroup;
}); });
return Promise.all(AdGroupPromises).then(() => {
return securityInfo.siteGroups;
});
return securityInfo.siteGroups;// don't really need to return this// already set it on securityinfo
}).catch((error) => { }).catch((error) => {
//error fetching groups //error fetching groups
errors.push(`There was an error feting site Groups -- ${error.message}`); errors.push(`There was an error feting site Groups -- ${error.message}`);
//debugger; //debugger;
throw error; throw error;
}); });
sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get() sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get()
.then((response) => { .then((response) => {
securityInfo.roleDefinitions = response.map((rd) => { securityInfo.roleDefinitions = response.map((rd) => {
@ -408,8 +492,8 @@ export default class SPSecurityService {
return securityInfo.roleDefinitions; return securityInfo.roleDefinitions;
}).catch((error) => { }).catch((error) => {
//debugger; //debugger;
//error fetching roledefinitions //error fetching role definitions
errors.push(`There was an error fetcing role Definitions -- ${error.message}`); errors.push(`There was an error fetching role Definitions -- ${error.message}`);
throw error; throw error;
}); });
let filters: string[] = []; let filters: string[] = [];
@ -419,25 +503,24 @@ export default class SPSecurityService {
if (!showCatalogs) { if (!showCatalogs) {
filters.push("IsCatalog eq false"); filters.push("IsCatalog eq false");
} }
let filter: string = filters.join(" and "); let subFilter: string = filters.join(" and ");
sp.web.lists sp.web.lists
.expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member", .expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member",
"RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId") "RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId")
.filter(filter).inBatch(batch).get() .filter(subFilter).inBatch(batch).get()
.then((response) => { .then((response) => {
securityInfo.lists = response.map((listObject) => { securityInfo.lists = response.map((listObject) => {
let mylist: SPList = new SPList(); let mylist: SPList = new SPList();
mylist.isSelected = true;// Shoudl be shown in the UI, user can deslect it in the ui mylist.isSelected = true;// Should be shown in the UI, user can de-select it in the ui
mylist.title = listObject.Title; mylist.title = listObject.Title;
mylist.id = listObject.Id; mylist.id = listObject.Id;
mylist.hidden = listObject.Hidden; mylist.hidden = listObject.Hidden;
mylist.serverRelativeUrl = listObject.RootFolder.ServerRelativeUrl; mylist.serverRelativeUrl = listObject.RootFolder.ServerRelativeUrl;
mylist.type = securableType.List;// to differeentiate foldes from lists mylist.type = securableType.List;// to differentiate folders from lists
mylist.itemCount = listObject.ItemCount; mylist.itemCount = listObject.ItemCount;
mylist.isExpanded = false; mylist.isExpanded = false;
mylist.hasBeenRetrieved = false; mylist.hasBeenRetrieved = false;
mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject) => { mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject) => {
let roleAssignment: SPRoleAssignment = { let roleAssignment: SPRoleAssignment = {
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb) => { return rdb.Id; }), roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb) => { return rdb.Id; }),
principalId: roleAssignmentObject.PrincipalId principalId: roleAssignmentObject.PrincipalId
@ -454,13 +537,79 @@ export default class SPSecurityService {
throw error; throw error;
}); });
return batch.execute().then(() => {
// execute the batch to get sp stuff
return batch.execute().then(async () => {
// then get the ad stuff
var requests = [];
var adPromises: Array<Promise<void>> = [];
for (let sitegroup of securityInfo.siteGroups) {
for (let adGroupId of sitegroup.adGroupIds) {
// need to do this in batch
var adPromise = this.fetchAdGroup(aadHttpClient, adGroupId)
.then((adGroup) => {
securityInfo.adGroups.push(adGroup);
for (var adUser of adGroup.members) {
var siteUser = find(securityInfo.siteUsers, (su, key) => {
return su.upn === adUser.userPrincipalName.toLowerCase();
});
if (siteUser) {
siteUser.adId = adUser.id;
} else {
let user: SPSiteUser = new SPSiteUser();
user.adId = adUser.id; user.name = adUser.displayName + "*";
user.upn = adUser.userPrincipalName ? adUser.userPrincipalName : adUser.displayName;
user.isSelected = true;
user.principalType = -1;
securityInfo.siteUsers.push(user);
}
}
}
)
.catch((err) => {
debugger;
});
adPromises.push(adPromise);
}
}
await Promise.all(adPromises);
console.table(securityInfo.siteUsers);
debugger;
return securityInfo; return securityInfo;
}).catch((error) => { }).catch((error) => {
//debugger; debugger;
// error in batch // error in batch
throw errors; throw errors;
}); });
} }
private fetchAdGroup(aadHttpClient: AadHttpClient, adGrouId: ADGroupId): Promise<ADGroup> {
var aadHttpClientConfiguration: AadHttpClientConfiguration = new AadHttpClientConfiguration({}, {});
// note im not loading nested groups here. Will need to load them on the fly? or maybe here?nvrmind
return aadHttpClient.get(`https://graph.microsoft.com/v1.0/groups/${adGrouId.ADId}/transitiveMembers`, aadHttpClientConfiguration)
.then((adResponse) => {
return adResponse.json()
.then((data) => {
let adGroup: ADGroup = new ADGroup();
adGroup.id = adGrouId;
adGroup.members = data.value;
return adGroup;
}).catch((err) => {
debugger;
alert(`error converting to json`);
return null;
});
}).catch((err) => {
debugger;
//if 403 show message to grant security
alert(`grant app SharePoint Online Client Extensibility Web Application Principal graph permissions Group.Read.All & GroupMembers.Read.All`);
return null;
});
}
} }

View File

@ -1,4 +1,5 @@
import { Version } from "@microsoft/sp-core-library"; import { Version } from "@microsoft/sp-core-library";
import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { SPPermission } from "@microsoft/sp-page-context"; import { SPPermission } from "@microsoft/sp-page-context";
import { IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneCheckbox, PropertyPaneDropdown, PropertyPaneSlider, PropertyPaneTextField, PropertyPaneToggle } from "@microsoft/sp-property-pane"; import { IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneCheckbox, PropertyPaneDropdown, PropertyPaneSlider, PropertyPaneTextField, PropertyPaneToggle } from "@microsoft/sp-property-pane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base"; import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
@ -13,6 +14,7 @@ import { PropertyFieldSelectedPermissions } from "./containers/PropertyFieldSele
import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps"; import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps";
export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> { export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> {
private aadHttpClient: AadHttpClient;
public onInit(): Promise<void> { public onInit(): Promise<void> {
return super.onInit().then(_ => { return super.onInit().then(_ => {
sp.setup({ sp.setup({
@ -21,6 +23,13 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
defaultCachingTimeoutSeconds: 30, defaultCachingTimeoutSeconds: 30,
globalCacheDisable: true // or true to disable caching in case of debugging/testing globalCacheDisable: true // or true to disable caching in case of debugging/testing
}); });
}).then(_ => {
this.context.aadHttpClientFactory.getClient(`https://graph.microsoft.com`)
.then((client: AadHttpClient) => {
// Search for the users with givenName, surname, or displayName equal to the searchFor value
this.aadHttpClient = client;
});
}); });
} }
@ -43,7 +52,7 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
listTitleColumnWidth: this.properties.listTitleColumnWidth, listTitleColumnWidth: this.properties.listTitleColumnWidth,
users: this.properties.users, users: this.properties.users,
getPermissionTypes: this.getPermissionTypes, getPermissionTypes: this.getPermissionTypes,
aadHttpClient: null,//this.context.aadHttpClient, aadHttpClient: this.aadHttpClient,
domElement: this.domElement domElement: this.domElement
}; };

View File

@ -25,10 +25,10 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
private svc: SPSecurityService = new SPSecurityService("ss"); private svc: SPSecurityService = new SPSecurityService("ss");
private userSelection = new Selection(); private userSelection = new Selection();
private listSelection = new Selection(); private listSelection = new Selection();
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [] }, securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [], adGroups: [] },
//permission: this.props.permission, //permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions, selectedPermissions: this.props.selectedPermissions,
showUserPanel: false, showUserPanel: false,
@ -52,7 +52,7 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
this.renderUserSelected = this.renderUserSelected.bind(this); this.renderUserSelected = this.renderUserSelected.bind(this);
} }
public componentDidMount(): void { public componentDidMount(): void {
debugger;
initializeFileTypeIcons(); initializeFileTypeIcons();
} }
public componentDidUpdate(): void { public componentDidUpdate(): void {
@ -257,13 +257,13 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
public renderUserItem(item: any, index: number, column: IColumn, effectivePermissions: ISelectedPermission[]): any { public renderUserItem(item: any, index: number, column: IColumn, effectivePermissions: ISelectedPermission[]): any {
let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => { let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => {
return su.id.toString() === column.key; return su.upn.toString() === column.key;
}); });
// spin througg the selected permsiisopns and for the first hit, display that color. No Hit, then display empty // spin througg the selected permsiisopns and for the first hit, display that color. No Hit, then display empty
for (let selectedPermission of effectivePermissions ? effectivePermissions : []) { for (let selectedPermission of effectivePermissions ? effectivePermissions : []) {
if (Helpers.doesUserHavePermission(item, user, SPPermission[selectedPermission.permission], if (Helpers.doesUserHavePermission(item, user, SPPermission[selectedPermission.permission],
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups)) { this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups, this.state.securityInfo.adGroups)) {
return ( return (
<Icon iconName={selectedPermission.iconName} style={{ color: selectedPermission.color }} onClick={(e) => { <Icon iconName={selectedPermission.iconName} style={{ color: selectedPermission.color }} onClick={(e) => {
this.expandCollapseList(item); this.expandCollapseList(item);
@ -290,13 +290,13 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
for (let user of users) { for (let user of users) {
if (user.isSelected) { if (user.isSelected) {
if ( if (
(user.principalType === 1 && this.props.showUsers) ((user.principalType === 1 || user.principalType === -1) && this.props.showUsers)
|| ||
(user.principalType === 4 && this.props.showSecurityGroups) (user.principalType === 4 && this.props.showSecurityGroups)
) )
if (!this.props.showOnlyUsersWithPermission || Helpers.doesUserHaveAnyPermission(this.state.securityInfo.lists, user, effectivePermissions.map((sp) => { return SPPermission[sp.permission] }), this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups)) if (!this.props.showOnlyUsersWithPermission || Helpers.doesUserHaveAnyPermission(this.state.securityInfo.lists, user, effectivePermissions.map((sp) => { return SPPermission[sp.permission] }), this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups, this.state.securityInfo.adGroups))
columns.push({ columns.push({
key: user.id.toString(), key: user.upn,
name: this.state.showEmail ? user.upn : user.name, name: this.state.showEmail ? user.upn : user.name,
fieldName: "", fieldName: "",
minWidth: 20, minWidth: 20,