Merge pull request #1728 from russgove/dev
This commit is contained in:
commit
b58aab06cc
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue