Upgrade to SPFx 1.20

This commit is contained in:
D'Andrea Nello 2024-10-20 18:52:55 +02:00
parent 88310656fe
commit 693640944f
27 changed files with 29439 additions and 21827 deletions

View File

@ -0,0 +1,5 @@
require('@rushstack/eslint-config/patch/modern-module-resolution');
module.exports = {
extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'],
parserOptions: { tsconfigRootDir: __dirname }
};

View File

@ -9,6 +9,7 @@ node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg
@ -30,3 +31,6 @@ obj
# Styles Generated Code
*.scss.ts
# heft folder
.heft

View File

@ -0,0 +1 @@
v18.20.4

View File

@ -3,10 +3,15 @@
"isDomainIsolated": false,
"isCreatingSolution": false,
"packageManager": "npm",
"version": "1.10.0",
"version": "1.20.0",
"libraryName": "spsecurity-webpart-3",
"libraryId": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"environment": "spo",
"componentType": "webpart"
"componentType": "webpart",
"nodeVersion": "18.20.4"
},
"sdkVersions": {
"@microsoft/teams-js": "2.24.0",
"@microsoft/microsoft-graph-client": "3.0.2"
}
}

View File

@ -87,8 +87,8 @@ This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/rus
| Every SPFx version is only compatible with specific version(s) of Node.js. In order to be able to build this sample, please ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.|
|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |
![SPFx 1.10](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
![Node.js v10 | v8](https://img.shields.io/badge/Node.js-v10%20%7C%20v8-green.svg)
![SPFx 1.20](https://img.shields.io/badge/SPFx-1.20.0-green.svg)
![Node.js v18.20.4](https://img.shields.io/badge/Node.js-%20v18.20.4-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg)
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
@ -102,7 +102,7 @@ This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/rus
## Prerequisites
> React, Office-UI-Fabric, sp-pnp-js, lodash
> React, Fluent UI React, sp-pnp-js, lodash
## Contributors
@ -112,6 +112,7 @@ This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/rus
Version|Date|Comments
-------|----|--------
1.0.6.0|Oct 20, 2024 | Upgrade to SPFx 1.20
1.0.5.0|March 6, 2021 | Added webApiPermission request
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

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -1,6 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spsecurity-webpart-3",
"accessKey": "<!-- ACCESS KEY -->"

View File

@ -6,7 +6,8 @@
"name": "spsecurity-webpart-3-client-side-solution",
"id": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"version": "1.0.4.0",
"webApiPermissionRequests": [{
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "Group.Read.All"
},
@ -14,7 +15,37 @@
"resource": "Microsoft Graph",
"scope": "GroupMember.Read.All"
}
],
"developer": {
"name": "Russel Gove",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": "https://github.com/russgove",
"mpnId": "Undefined-1.20.0"
},
"metadata": {
"shortDescription": {
"default": "Security Grid Web Part"
},
"longDescription": {
"default": "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"
},
"screenshotPaths": [],
"videoUrl": "",
"categories": []
},
"features": [
{
"title": "Security Grid Web Part Feature",
"description": "The feature that activates Security Grid Web Part from the react-securitygrid solution.",
"id": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"version": "1.0.4.0",
"componentIds": [
"41e37f03-2ea8-4f19-b77a-f2121a1e7c45"
]
}
]
},
"paths": {
"zippedPackage": "solution/spsecurity-webpart-3.sppkg"

View File

@ -0,0 +1,3 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json"
}

View File

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

View File

@ -8,4 +8,13 @@ build.addSuppression(`Warning - [sass] The local CSS class 'ms-DetailsHeader-cel
build.addSuppression(`Warning - [sass] The local CSS class 'ms-List-cell' is not camelCase and will not be type-safe.`);
build.addSuppression(`Warning - [sass] The local CSS class 'ms-DetailsRow' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,46 @@
{
"main": "lib/index.js",
"name": "spsecurity-webpart-3",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"resolutions": {
"@types/react": "16.8.8"
"node": ">=16.13.0 <17.0.0 || >=18.17.1 <19.0.0"
},
"dependencies": {
"@microsoft/microsoft-graph-types": "^1.31.0",
"@pnp/common": "1.3.3",
"@pnp/logging": "1.3.3",
"@pnp/odata": "1.3.3",
"@pnp/sp": "1.3.3",
"@pnp/spfx-property-controls": "1.0.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"@uifabric/file-type-icons": "7.6.16",
"@microsoft/sp-core-library": "1.20.0",
"@microsoft/sp-adaptive-card-extension-base": "1.20.0",
"@pnp/sp": "4.6.0",
"@pnp/spfx-property-controls": "3.18.0",
"@types/webpack-env": "1.15.2",
"@uifabric/file-type-icons": "7.10.11",
"lodash": "^4.17.4",
"natives": "^1.1.6",
"office-ui-fabric-react": "7.148.1",
"react": "16.8.5",
"react-dom": "16.8.5"
"@fluentui/react": "8.106.4",
"react": "17.0.1",
"react-dom": "17.0.1",
"tslib": "2.3.1"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.2": "0.6.8",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@microsoft/rush-stack-compiler-4.7": "0.1.0",
"@microsoft/eslint-config-spfx": "1.20.2",
"@microsoft/eslint-plugin-spfx": "1.20.2",
"@microsoft/sp-build-web": "1.20.2",
"@microsoft/sp-module-interfaces": "1.20.2",
"@rushstack/eslint-config": "4.0.1",
"eslint": "8.57.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"i": "0.3.7",
"npm": "6.14.6",
"tslint-microsoft-contrib": "5.0.0"
"@types/react": "17.0.45",
"@types/react-dom": "17.0.17",
"@types/webpack-env": "1.15.2",
"eslint-plugin-react-hooks": "4.3.0",
"ajv": "6.12.5",
"gulp": "4.0.2",
"tslint-microsoft-contrib": "5.0.0",
"typescript": "4.7.4"
},
"resolutions": {
"@microsoft/sp-http": "1.20.0"
},
"scripts": {
"build": "gulp bundle",

View File

@ -0,0 +1,20 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
// import pnp, pnp logging system, and any other selective imports needed
import { spfi, SPFI, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
import "@pnp/sp/site-users/web";
import "@pnp/sp/presets/all";
let _sp: SPFI = null;
export const getSP = (context?: WebPartContext): SPFI => {
if (context != null) {
_sp = spfi().using(SPFx(context));
}
return _sp;
};

View File

@ -1,41 +1,78 @@
import { User } from "@microsoft/microsoft-graph-types";
import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { IODataUser } from "@microsoft/sp-odata-types";
import { AadHttpClient, AadHttpClientConfiguration } from "@microsoft/sp-http";
import { SPPermission } from "@microsoft/sp-page-context";
//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";
import { SPFI } from '@pnp/sp';
import { getSP } from '../pnpjs-config';
import { find, includes } from "lodash";
interface SPRoleAssignmentObject {
PrincipalId: number;
RoleDefinitionBindings: Array<SPRoleDefinitionBindingObject>;
}
interface SPRoleDefinitionBindingObject {
Id: number;
}
interface SPListItemResponse {
GUID: string;
FileSystemObjectType: number;
Title: string;
ContentTypeId: string;
RoleAssignments: Array<SPRoleAssignmentObject>;
File?: {
Name: string;
ServerRelativeUrl: string;
};
Folder?: {
Name: string;
ServerRelativeUrl: string;
ItemCount: number;
};
}
interface SPUser {
Id: number;
LoginName: string;
Title: string;
PrincipalType: number;
UserId?: {
NameId: string;
NameIdIssuer: string;
};
}
interface ADGroupResponse {
value: Array<User>;
}
export interface ISPSecurableObject {
id: number;
id: string | number;
roleAssignments: SPRoleAssignment[];
}
export class SPBasePermissions {
public low: number;
public high: number;
public constructor(high: any, low: any) {
public constructor(high: string, low: string) {
this.high = parseInt(high, 10);
this.low = parseInt(low, 10);
}
}
}
}
export enum securableType {
List
}
export class ADGroupId {
public ADId: string; // the goid id in azure
public SPId: number; // the numeric id in the sharepoint users list
public ADId: string;
public SPId: number;
}
export class ADGroup {
public id: ADGroupId;
public members: Array<User>;
}
export class SPSiteGroup {
@ -44,23 +81,22 @@ export class SPSiteGroup {
public isHiddenInUI: boolean;
public isShareByEmailGuestUse: boolean;
public isSiteAdmin: boolean;
public userIds: number[];// to switch to ad groups need to make this a string[] with the UPN
public adGroupIds: ADGroupId[];
public userIds: number[] = [];
public adGroupIds: ADGroupId[] = [];
public constructor(id: number, title: string) {
this.id = id;
this.title = title;
this.userIds = [];
this.adGroupIds = [];
}
}
export class SPSiteUser {
public name: string;
public id?: number;
public userId?: SPExternalUser;
public upn: string;
public isSelected: boolean; //should user be shown in UI
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
public isSelected: boolean = false;
public principalType: number = 0;
public adId?: string;
}
export class SPRoleDefinition {
@ -76,82 +112,68 @@ export class SPRoleDefinition {
this.hidden = hidden;
this.name = name;
}
}
export class SPSecurityInfo {
public siteUsers: SPSiteUser[];
public siteGroups: SPSiteGroup[];
public roleDefinitions: SPRoleDefinition[];
public adGroups: ADGroup[];
public lists: (SPList | SPListItem)[];
public constructor() {
this.siteUsers = new Array<SPSiteUser>();
this.siteGroups = new Array<SPSiteGroup>();
this.roleDefinitions = new Array<SPRoleDefinition>();
this.siteUsers = new Array<SPSiteUser>();
this.lists = new Array<SPList>();
this.adGroups = new Array<ADGroup>();
}
public siteUsers: SPSiteUser[] = [];
public siteGroups: SPSiteGroup[] = [];
public roleDefinitions: SPRoleDefinition[] = [];
public adGroups: ADGroup[] = [];
public lists: (SPList | SPListItem)[] = [];
}
export class SPList {
public title: string;
public id: string;
public hidden: boolean; // this specifies if the list is a sharepoint hidden list
public serverRelativeUrl: string;
public type: securableType;
public itemCount: number;
public roleAssignments: SPRoleAssignment[];
public isExpanded: boolean;
public hasBeenRetrieved: boolean;
public isSelected: boolean; //Shoud list be shown in the UI
export class SPList implements ISPSecurableObject {
public title: string = "";
public id: string = "";
public hidden: boolean = false;
public serverRelativeUrl: string = "";
public type: securableType = securableType.List;
public itemCount: number = 0;
public roleAssignments: SPRoleAssignment[] = [];
public isExpanded: boolean = false;
public isFetched: boolean = false;
public hasBeenRetrieved: boolean = false;
public isSelected: boolean = false;
}
export class SPListItem {
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 SPListItem implements ISPSecurableObject {
public id: string = "";
public parentId: string = "";
public listTitle: string = "";
public type: string = "";
public itemCount: number = 0;
public title: string = "";
public serverRelativeUrl: string = "";
public roleAssignments: SPRoleAssignment[] = [];
public isExpanded: boolean = false;
public isFetched: boolean = false;
public isSelected: boolean = false;
public hasBeenRetrieved: boolean = false;
public level: number = 0;
public iconName: string = "";
}
// 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 {
public nameId: string;
public nameIdIssuer: string;
public nameId: string = "";
public nameIdIssuer: string = "";
}
export class SPRoleAssignment {
public roleDefinitionIds: number[] = [];
public principalId: number;
public principalId: number = 0;
}
export class Helpers {
public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups, adGroups: ADGroup[]): boolean {
for (var securableObject of securableObjects) {
for (var requestedpermission of requestedpermissions) {
public static doesUserHaveAnyPermission(
securableObjects: ISPSecurableObject[],
user: SPSiteUser,
requestedpermissions: SPPermission[],
roles: SPRoleDefinition[],
siteGroups: SPSiteGroup[],
adGroups: ADGroup[]
): boolean {
for (const securableObject of securableObjects) {
for (const requestedpermission of requestedpermissions) {
if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups, adGroups)) {
return true;
}
@ -159,14 +181,19 @@ export class Helpers {
}
return false;
}
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups, adGroups: ADGroup[]) {
public static doesUserHavePermission(
securableObject: ISPSecurableObject,
user: SPSiteUser,
requestedpermission: SPPermission,
roles: SPRoleDefinition[],
siteGroups: SPSiteGroup[],
adGroups: ADGroup[]
): boolean {
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups, adGroups);
for (const permission of permissions) {
if (
((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low))
&&
((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low)) &&
((permission.high & requestedpermission.value.High) === (requestedpermission.value.High))
) {
return true;
@ -175,10 +202,11 @@ export class Helpers {
return false;
}
public static getBasePermissionsForRoleDefinitiuonIds(selectedRoleDefinitionIds: number[],
roleDefinitions: SPRoleDefinition[]): Array<SPBasePermissions> {
let basePermissions = [];
public static getBasePermissionsForRoleDefinitionIds(
selectedRoleDefinitionIds: number[],
roleDefinitions: SPRoleDefinition[]
): Array<SPBasePermissions> {
const basePermissions: SPBasePermissions[] = [];
for (const selectedRoleDefinitionId of selectedRoleDefinitionIds) {
for (const roleDefinition of roleDefinitions) {
if (roleDefinition.id === selectedRoleDefinitionId) {
@ -186,430 +214,291 @@ export class Helpers {
}
}
}
// for (var rdx = 0; rdx < roleDefs.length; rdx++) {
// for (var rdi = 0; rdi < roleDefinitionIds.length; rdi++) {basePermission
// if (roleDefs[rdx].Id === roleDefinitionIds[rdi]) {
// basePermissions.push(roleDefs[rdx].BasePermissions);
// }
// }
// }
return basePermissions;
}
public static getUserPermissionsForObject(securableObject, user, roles: SPRoleDefinition[], siteGroups: SPSiteGroup[], adGroups: ADGroup[]) {
public static getUserPermissionsForObject(
securableObject: ISPSecurableObject,
user: SPSiteUser,
roles: SPRoleDefinition[],
siteGroups: SPSiteGroup[],
adGroups: ADGroup[]
): SPBasePermissions[] {
const userRoleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups, adGroups);
let roleDefinitionIds: number[] = [];
const roleDefinitionIds: number[] = [];
for (const roleAssignment of userRoleAssignments) {
for (const roleDefinitionID of roleAssignment.roleDefinitionIds) {
roleDefinitionIds.push(roleDefinitionID);
}
}
var userPermissions = Helpers.getBasePermissionsForRoleDefinitiuonIds(roleDefinitionIds, roles);
return userPermissions;
return Helpers.getBasePermissionsForRoleDefinitionIds(roleDefinitionIds, roles);
}
public static findGroup(groupId: number, groups: SPSiteGroup[]): SPSiteGroup {
return find(groups, (g) => { return g.id === groupId; });
public static GetRoleAssignmentsForUser(
securableObject: ISPSecurableObject,
user: SPSiteUser,
groups: SPSiteGroup[],
adGroups: ADGroup[]
): SPRoleAssignment[] {
const selectedRoleAssignments: SPRoleAssignment[] = [];
for (const roleAssignment of securableObject.roleAssignments) {
const group: SPSiteGroup | undefined = find(groups, (g) => g.id === roleAssignment.principalId);
if (group) {
if (this.userIsInGroup(user.id!, group.id, groups)) {
selectedRoleAssignments.push(roleAssignment);
}
if (this.userIsInGroupsNestAdGroups(user.adId!, group.id, groups, adGroups)) {
selectedRoleAssignments.push(roleAssignment);
}
} else if (user.id === roleAssignment.principalId) {
selectedRoleAssignments.push(roleAssignment);
}
}
return selectedRoleAssignments;
}
public static userIsInGroup(userId: number, groupId: number, groups: SPSiteGroup[]): boolean {
let group: SPSiteGroup = this.findGroup(groupId, groups);
const group: SPSiteGroup = this.findGroup(groupId, groups);
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; })) {
public static userIsInGroupsNestAdGroups(
userAdId: string,
groupId: number,
groups: SPSiteGroup[],
adGroups: ADGroup[]
): boolean {
const group: SPSiteGroup = this.findGroup(groupId, groups);
for (const adGrouId of group.adGroupIds) {
const adGroup = find(adGroups, (adg) => adg.id === adGrouId);
if (adGroup && find(adGroup.members, (aduser) => aduser.id === userAdId)) {
return true;
}
} else {
debugger;
alert(`adGroup ${ADGroupId} was not in the collection of ad groups.`);
}
return false;
}
}
}
public static GetRoleAssignmentsForUser(securableObject: ISPSecurableObject, user: SPSiteUser,
groups: SPSiteGroup[], adGroups: ADGroup[]): SPRoleAssignment[] {
try {
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) {
let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; });
if (group) {
if (this.userIsInGroup(user.id, group.id, groups)) { // this tests if a user is directly in the SP GROUP
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 {
// it must be a user
if (user.id === roleAssignment.principalId) {
selectedRoleAssignments.push(roleAssignment);
}
}
}
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);
// }
// }
public static findGroup(groupId: number, groups: SPSiteGroup[]): SPSiteGroup {
return find(groups, (g) => g.id === groupId)!;
}
}
}
}
return selectedRoleAssignments;
} catch (exception) {
//debugger;
console.error(exception);
}
}
}
export default class SPSecurityService {
public siteUrl: string;
private sp: SPFI;
public constructor(siteUrl: string) {
this.siteUrl = siteUrl;
this.sp = getSP();
}
public loadFolderRoleAssigmentsDefinitionsMembers(listTitle, folderServerRelativeUrl,
parentId: string, level: number, forceReload: boolean): Promise<SPListItem[]> {
// pnp.sp.web.lists.getByTitle("Config3").getItemsByCAMLQuery(caml, "RoleAssignments").then(show);
let caml: any = {
ViewXml: "<View Scope='RecursiveAll'>" +
" <Query>" +
"<Where>" +
" <Eq>" +
" <FieldRef Name='FileDirRef'/>" +
" <Value Type='Lookup'>" +
folderServerRelativeUrl +
" </Value>" +
" </Eq>" +
" </Where>" +
" </Query>" +
// " <QueryOptions>"+
// "<ViewAttributes Scope='RecursiveAll' />" +
// "<OptimizeFor>FolderUrls</OptimizeFor>"+
// "</QueryOptions>"+
" </View>"
// Adjust the loadFolderRoleAssignmentsDefinitionsMembers method with proper types
public loadFolderRoleAssignmentsDefinitionsMembers(
listTitle: string,
folderServerRelativeUrl: string,
parentId: string,
level: number
): Promise<SPListItem[]> {
const caml: { ViewXml: string } = {
ViewXml: "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='FileDirRef'/><Value Type='Lookup'>" + folderServerRelativeUrl + "</Value></Eq></Where></Query></View>"
};
return sp.web.lists.getByTitle(listTitle).getItemsByCAMLQuery(caml, "ContentType", "Folder", "Folder/ParentFolder", "File",
"File/ParentFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member",
"RoleAssignments/Member/Users", "RoleAssignments/Member/Groups")
.then((response) => {
let itemsToAdd: SPListItem[] = [];
for (let listItem of response) {
let itemToAdd: SPListItem = new SPListItem();
return this.sp.web.lists
.getByTitle(listTitle)
.getItemsByCAMLQuery(caml, "ContentTypeId", "File", "Folder", "Folder/ParentFolder", "File/ParentFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member", "RoleAssignments/Member/Users", "RoleAssignments/Member/Groups")
.then((response: SPListItemResponse[]) => { // Now typed correctly
const itemsToAdd: SPListItem[] = response.map((listItem: SPListItemResponse) => {
const itemToAdd: SPListItem = new SPListItem();
itemToAdd.id = listItem.GUID;
itemToAdd.parentId = parentId;
itemToAdd.level = level;
itemToAdd.listTitle = listTitle;
itemToAdd.type = listItem.ContentType.Name;
itemToAdd.isExpanded = false;
itemToAdd.hasBeenRetrieved = false;
itemToAdd.roleAssignments = [];
if (listItem.ContentType.Name === "Folder") { // its a folder
// Use FileSystemObjectType to distinguish between folder and file/list item
if (listItem.FileSystemObjectType === 1 && listItem.Folder) {
itemToAdd.type = 'Folder';
itemToAdd.title = listItem.Folder.Name;
itemToAdd.serverRelativeUrl = listItem.Folder.ServerRelativeUrl;
itemToAdd.itemCount = listItem.Folder.ItemCount;
} else {
if (listItem.File) {// its a file
} else if (listItem.File) {
itemToAdd.type = 'File';
itemToAdd.title = listItem.File.Name;
itemToAdd.serverRelativeUrl = listItem.File.ServerRelativeUrl;
} else { // its a listitem
} else {
itemToAdd.type = 'ListItem';
itemToAdd.title = listItem.Title;
}
}
for (let roleAssignmentObject of listItem.RoleAssignments) {
let roleAssignment: SPRoleAssignment = {
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb) => { return rdb.Id; }),
principalId: roleAssignmentObject.PrincipalId
};
// if (roleAssignmentObject.Member.UserId) {
// roleAssignment.userId = new SPExternalUser();
// roleAssignment.userId.nameId = roleAssignmentObject.Member.UserId.NameId;
// roleAssignment.userId.nameIdIssuer = roleAssignmentObject.Member.UserId.NameIdIssuer;
// // roleAssignment.userId = roleAssignmentObject.Member.UserId;
// }
// if (roleAssignmentObject.Member.Users) {
// for (let roleAssignmentMemberUser of roleAssignmentObject.Member.Users) {
// roleAssignment.users.push(roleAssignmentMemberUser.Id);
// }
// }
// if (roleAssignmentObject.Member.Groups) {
// for (let roleAssignmentMemberGroup of roleAssignmentObject.Member.Groups) {
// roleAssignment.groups.push(roleAssignmentMemberGroup.Id);
// }
// }
// for (let roleDefinitionBinding of roleAssignmentObject.RoleDefinitionBindings) {
// roleAssignment.roleDefinitionIds.push(roleDefinitionBinding.Id);
// }
itemToAdd.roleAssignments.push(roleAssignment);
}
itemsToAdd.push(itemToAdd);
}
itemToAdd.isExpanded = false;
itemToAdd.hasBeenRetrieved = false;
itemToAdd.roleAssignments = listItem.RoleAssignments.map((roleAssignmentObject: SPRoleAssignmentObject) => ({
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb: SPRoleDefinitionBindingObject) => rdb.Id),
principalId: roleAssignmentObject.PrincipalId,
}));
return itemToAdd;
});
return itemsToAdd;
});
}
// public async getMembersOfAdGroup(aadHttpClient: AadHttpClient, groupName: string): Promise<any> {
// return aadHttpClient.get("v1.0/groups?$filter=displayName eq '" + groupName + "'&$expand=members",
// AadHttpClient.configurations.v1).then((response) => {
// response.json().then((data) => {
// debugger;
// });
// }).catch((err) => {
// console.log(err);
// });
// }
/// Loads data for intial display
public loadData(showHiddenLists: boolean, showCatalogs: boolean, aadHttpClient: AadHttpClient, forceReload: boolean): Promise<SPSecurityInfo> {
let securityInfo: SPSecurityInfo = new SPSecurityInfo();
let batch: any = sp.createBatch();
let errors: Array<string> = [];
debugger;
sp.web.siteUsers.inBatch(batch).get()
.then((response) => {
securityInfo.siteUsers = response.map((u) => {
var upn: string = u.LoginName.split('|')[2];
let user: SPSiteUser = new SPSiteUser();
public async loadData(showHiddenLists: boolean, showCatalogs: boolean, aadHttpClient: AadHttpClient): Promise<SPSecurityInfo> {
const securityInfo: SPSecurityInfo = new SPSecurityInfo();
const errors: string[] = [];
// Get site users
try {
const siteUsersResponse = await this.sp.web.siteUsers();
securityInfo.siteUsers = siteUsersResponse.map((u: SPUser) => {
const upn: string = u.LoginName.split('|')[2];
const user: SPSiteUser = new SPSiteUser();
user.isSelected = true;
user.id = u.Id;
user.name = u.Title;
user.principalType = u.PrincipalType;
user.upn = upn ? upn.toLocaleLowerCase() : u.Title;// switching key in react from id to upn. ensure upn is not undefined
user.upn = upn ? upn.toLowerCase() : u.Title;
if (u.UserId) {
user.userId = new SPExternalUser();
user.userId.nameId = u.UserId.NameId;
user.userId.nameIdIssuer = u.UserId.NameIdIssuer;
user.userId = { nameId: u.UserId.NameId, nameIdIssuer: u.UserId.NameIdIssuer };
}
return user;
});
} catch (err) {
errors.push(`There was an error fetching site users -- ${err.message}`);
}
// securityInfo.siteUsers = securityInfo.siteUsers.filter((su) => { su.upn });
return securityInfo.siteUsers;// dont really need to return this// already set it on securityinfo
}).catch((error) => {
debugger;
errors.push(`There was an error feting site users -- ${error.message}`);
throw error;
});
sp.web.siteGroups.filter(`Title ne 'Limited Access System Group'`).expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get()
.then(async (response) => {
// Get site groups with users, filtering out 'Limited Access System Group'
try {
const siteGroupsResponse = await this.sp.web.siteGroups
.filter(`Title ne 'Limited Access System Group'`) // Add the filter to exclude this group
.expand("Users") // Expand users for each group
.select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "Users/Id", "Users/LoginName", "Users/PrincipalType")(); // Select required fields
// if group contains an ad group(PrincipalType=4) expand it
securityInfo.siteGroups = response.map((grp) => {
//
//IMPORTANT:
//For groups created with 'Anyone in the organization with the link'
//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) {
if (user.PrincipalType === 4) { //4=Security group, 1 = user, 2=DL, 8=SP Group
var adgroupid = new ADGroupId();
adgroupid.ADId = user.LoginName.split('|')[2];//Loginname s c:0t,c|tenant|grpid for ad groups
adgroupid.SPId = user.Id;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
securityInfo.siteGroups = siteGroupsResponse.map((grp:any) => {
const siteGroup: SPSiteGroup = new SPSiteGroup(grp.Id, grp.Title);
for (const user of grp.Users) {
if (user.PrincipalType === 4) { // 4 = Security group
const adgroupid = new ADGroupId();
adgroupid.ADId = user.LoginName.split('|')[2]; // Ensure ADId is a string
adgroupid.SPId = user.Id.toString(); // Convert SPId to string
siteGroup.adGroupIds.push(adgroupid);
} else {
siteGroup.userIds.push(user.Id);
siteGroup.userIds.push(user.Id); // SPId is numeric, no conversion needed here
}
}
return siteGroup;
});
} catch (err) {
errors.push(`There was an error fetching site groups -- ${err.message}`);
}
return securityInfo.siteGroups;// don't really need to return this// already set it on securityinfo
}).catch((error) => {
//error fetching groups
errors.push(`There was an error feting site Groups -- ${error.message}`);
//debugger;
throw error;
// Get role definitions
try {
const roleDefinitionsResponse = await this.sp.web.roleDefinitions.expand("BasePermissions")();
securityInfo.roleDefinitions = roleDefinitionsResponse.map((rd) => {
const bp: SPBasePermissions = new SPBasePermissions(rd.BasePermissions.High.toString(), rd.BasePermissions.Low.toString());
return new SPRoleDefinition(rd.Id, bp, rd.Description, rd.Hidden, rd.Name);
});
} catch (err) {
errors.push(`There was an error fetching role definitions -- ${err.message}`);
}
sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get()
.then((response) => {
securityInfo.roleDefinitions = response.map((rd) => {
const bp: SPBasePermissions = new SPBasePermissions(rd.BasePermissions.High, rd.BasePermissions.Low);
const roleDefinition: SPRoleDefinition = new SPRoleDefinition(
parseInt(rd.Id, 10),
bp,
rd.Description,
rd.Hidden,
rd.Name);
return roleDefinition;
});
return securityInfo.roleDefinitions;
}).catch((error) => {
//debugger;
//error fetching role definitions
errors.push(`There was an error fetching role Definitions -- ${error.message}`);
throw error;
});
let filters: string[] = [];
// Get lists with RoleAssignments, RootFolder, etc.
try {
const filters: string[] = [];
if (!showHiddenLists) {
filters.push("Hidden eq false");
}
if (!showCatalogs) {
filters.push("IsCatalog eq false");
}
let subFilter: string = filters.join(" and ");
sp.web.lists
.expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member",
"RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId")
.filter(subFilter).inBatch(batch).get()
.then((response) => {
securityInfo.lists = response.map((listObject) => {
let mylist: SPList = new SPList();
mylist.isSelected = true;// Should be shown in the UI, user can de-select it in the ui
const subFilter = filters.join(" and ");
const listsResponse = await this.sp.web.lists
.expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member", "RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId")
.filter(subFilter)();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
securityInfo.lists = listsResponse.map((listObject: any) => {
const mylist: SPList = new SPList();
mylist.isSelected = true;
mylist.title = listObject.Title;
mylist.id = listObject.Id;
mylist.hidden = listObject.Hidden;
mylist.serverRelativeUrl = listObject.RootFolder.ServerRelativeUrl;
mylist.type = securableType.List;// to differentiate folders from lists
mylist.itemCount = listObject.ItemCount;
mylist.isExpanded = false;
mylist.hasBeenRetrieved = false;
mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject) => {
let roleAssignment: SPRoleAssignment = {
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb) => { return rdb.Id; }),
mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject: SPRoleAssignmentObject) => ({
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb: SPRoleDefinitionBindingObject) => rdb.Id),
principalId: roleAssignmentObject.PrincipalId
};
return roleAssignment;
});
}));
return mylist;
});
}).catch((error) => {
//debugger;
errors.push(`There was an error fetching lists -- ${error.message} `);
//error fetching lists
throw error;
} catch (err) {
errors.push(`There was an error fetching lists -- ${err.message}`);
}
});
// 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) => {
// Fetch AD groups and add to securityInfo
const adPromises: Array<Promise<void>> = [];
for (const sitegroup of securityInfo.siteGroups) {
for (const adGroupId of sitegroup.adGroupIds) {
const adPromise = this.fetchAdGroup(aadHttpClient, adGroupId)
.then((adGroup: ADGroup) => {
securityInfo.adGroups.push(adGroup);
for (var adUser of adGroup.members) {
var siteUser = find(securityInfo.siteUsers, (su, key) => {
return su.upn === adUser.userPrincipalName.toLowerCase();
});
for (const adUser of adGroup.members) {
const siteUser = find(securityInfo.siteUsers, (su) => 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;
const user: SPSiteUser = new SPSiteUser();
user.adId = adUser.id;
user.name = adUser.displayName + "*";
user.upn = adUser.userPrincipalName || adUser.displayName;
user.isSelected = true;
user.principalType = -1;
securityInfo.siteUsers.push(user);
}
}
}
)
.catch((err) => {
debugger;
}).catch((err: Error) => {
errors.push(`There was an error fetching AD groups -- ${err.message}`);
});
adPromises.push(adPromise);
}
}
}
}
await Promise.all(adPromises);
console.table(securityInfo.siteUsers);
debugger;
return securityInfo;
}).catch((error) => {
debugger;
// error in batch
throw errors;
});
// Final error check
if (errors.length > 0) {
console.log("Errors encountered:", errors);
} else {
console.log("No errors encountered, proceeding.");
}
//console.table(securityInfo.siteUsers);
return securityInfo;
}
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
const aadHttpClientConfiguration: AadHttpClientConfiguration = new AadHttpClientConfiguration({}, {});
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();
.then((data: ADGroupResponse) => {
const adGroup: ADGroup = new ADGroup();
adGroup.id = adGrouId;
adGroup.members = data.value;
return adGroup;
}).catch((err) => {
debugger;
alert(`error converting to json`);
}).catch(() => {
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`);
}).catch(() => {
alert(`Grant app SharePoint Online Client Extensibility Web Application Principal graph permissions Group.Read.All & GroupMembers.Read.All`);
return null;
});
}
}

View File

@ -23,7 +23,5 @@ export interface ISpSecurityWebPartProps {
showEmail: boolean; //0 show name, 1 show email
showSecurityGroups: boolean; // show PrincipalType=4
showUsers: boolean; // show PrincipalType=1
showOnlyUsersWithPermission;// toggle to show everyone, or justy the users who have the permissions
showOnlyUsersWithPermission: boolean;// toggle to show everyone, or justy the users who have the permissions
}

View File

@ -1,9 +1,10 @@
import { Version } from "@microsoft/sp-core-library";
import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { AadHttpClient } from "@microsoft/sp-http";
import { SPPermission } from "@microsoft/sp-page-context";
import { IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneCheckbox, PropertyPaneDropdown, PropertyPaneSlider, PropertyPaneTextField, PropertyPaneToggle } from "@microsoft/sp-property-pane";
import { IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneCheckbox, PropertyPaneSlider, PropertyPaneToggle } from "@microsoft/sp-property-pane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { sp } from "@pnp/sp";
import { getSP } from '../../pnpjs-config';
import { SPFI } from "@pnp/sp";
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
import * as React from "react";
import * as ReactDom from "react-dom";
@ -14,30 +15,24 @@ import { PropertyFieldSelectedPermissions } from "./containers/PropertyFieldSele
import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps";
export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> {
private _sp: SPFI = null;
private aadHttpClient: AadHttpClient;
public onInit(): Promise<void> {
return super.onInit().then(_ => {
sp.setup({
spfxContext: this.context,
defaultCachingStore: "session", // or "local"
defaultCachingTimeoutSeconds: 30,
globalCacheDisable: true // or true to disable caching in case of debugging/testing
});
}).then(_ => {
return super.onInit().then(() => {
this._sp = getSP(this.context);
}).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
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((client: any) => {
this.aadHttpClient = client;
});
});
}
public render(): void {
const props: ISpSecurityProps = {
//permission: this.properties.permission, // old way
selectedPermissions: this.properties.selectedPermissions.map((spp) => { return { ...spp, isChecked: true }; }),
selectedPermissions: this.properties.selectedPermissions.map(spp => ({ ...spp, isChecked: true })),
showHiddenLists: this.properties.showHiddenLists,
showCatalogs: this.properties.showCatalogs,
showEmail: this.properties.showEmail,
@ -51,11 +46,10 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
adminSelectedLists: this.properties.adminSelectedLists,
listTitleColumnWidth: this.properties.listTitleColumnWidth,
users: this.properties.users,
getPermissionTypes: this.getPermissionTypes,
aadHttpClient: this.aadHttpClient,
domElement: this.domElement
getPermissionTypes: this.getPermissionTypes.bind(this),
aadHttpClient: this.aadHttpClient
};
const element: React.ReactElement<ISpSecurityProps> = React.createElement(
SpSecurity, props
);
@ -63,14 +57,10 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
ReactDom.render(element, this.domElement);
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
public getPermissionTypes(): IPropertyPaneDropdownOption[] {
let perms = new Array();
const perms: IPropertyPaneDropdownOption[] = [];
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
if (typeof SPPermission[perm as keyof typeof SPPermission] === "object") {
perms.push({
text: perm,
key: perm
@ -79,14 +69,11 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
}
return perms;
}
private onPropertyChange(propertyPath: string, oldValue: any, newValue: any) {
//debugger;
// does this get oldvalue and new value?
private onPropertyChange(propertyPath: string, newValue: unknown): void {
switch (propertyPath) {
case "SelectedPermissions":
this.properties.selectedPermissions = newValue;
this.properties.selectedPermissions = newValue as ISpSecurityWebPartProps['selectedPermissions'];
this.context.propertyPane.refresh();
this.render();
break;
@ -106,25 +93,19 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
{
groupName: "Permission Settings",
groupFields: [
PropertyFieldSelectedPermissions("SelectedPermissions", {
label: "Selected Permissions and Colors",
onPropertyChange: this.onPropertyChange.bind(this),
getSelectedPermissions: () => {
return this.properties.selectedPermissions || [];
},
getSelectedPermissions: () => this.properties.selectedPermissions || [],
}),
PropertyPaneCheckbox("letUserSelectPermission", {
text: "Let user select Permission"
}),
]
},
{
groupName: "User Settings",
groupFields: [
PropertyPaneToggle("showEmail", {
label: "Show Email or Name",
onText: "Show Email",
@ -166,22 +147,16 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
{
groupName: "Permission Settings",
groupFields: [
PropertyFieldSelectedPermissions("SelectedPermissions", {
label: "Selected Permissions and Colors",
onPropertyChange: this.onPropertyChange.bind(this),
getSelectedPermissions: () => {
return this.properties.selectedPermissions || [];
},
getSelectedPermissions: () => this.properties.selectedPermissions || [],
}),
PropertyPaneCheckbox("letUserSelectPermission", {
text: "Let user select Permission"
}),
]
},
{
groupName: "Display Settings",
groupFields: [
@ -211,18 +186,15 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
PropertyPaneCheckbox("letUserSelectLists", {
text: "Let user select Lists"
}),
]
},
{
groupName: "Select Lists",
groupFields: [
PropertyPaneToggle("includeAdminSelectedLists", {
label: "Inclued/exclude selected lists",
label: "Include/exclude selected lists",
onText: "Include selected lists",
offText: "Exclude selected lists",
}),
PropertyFieldListPicker("adminSelectedLists", {
label: 'Select lists to include/exclude',
@ -230,7 +202,7 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
includeHidden: this.properties.showHiddenLists,
orderBy: PropertyFieldListPickerOrderBy.Title,
disabled: false,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
onPropertyChange: this.onPropertyChange.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: null,

View File

@ -1,12 +1,11 @@
import { SPSiteUser } from "../../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
import { } from "@microsoft/sp-webpart-base";
import { IPropertyPaneDropdownOption, PropertyPaneDropdown } from "@microsoft/sp-property-pane";
import { IPropertyPaneDropdownOption } from "@microsoft/sp-property-pane";
import { AadHttpClient } from "@microsoft/sp-http";
import {ISelectedPermission} from "../ISpSecurityWebPartProps";
export interface ISpSecurityProps {
users: SPSiteUser[];
//permission: string;
selectedPermissions:ISelectedPermission[];
showHiddenLists: boolean;
showCatalogs:boolean;
@ -22,7 +21,6 @@ export interface ISpSecurityProps {
showSecurityGroups:boolean; // show PrincipalType=4
showUsers:boolean; // show PrincipalType=1
showOnlyUsersWithPermission:boolean; //// toggle to show everyone, or justy the users who have the permissions
domElement:any; // needed to disable button postback after render on classic pages
//domElement:any; // needed to disable button postback after render on classic pages
}

View File

@ -1,39 +1,28 @@
import * as React from 'react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Stack, StackItem, Alignment } from 'office-ui-fabric-react/lib/Stack';
import { IconButton, DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Stack } from '@fluentui/react/lib/Stack';
import { DefaultButton } from '@fluentui/react/lib/Button';
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
export interface ILegendProps {
selectedPermissions: Array<ISelectedPermission>;
selectedPermissions: ISelectedPermission[];
checkUncheckPermission: (perm: ISelectedPermission) => void;
}
export function Legend(props: ILegendProps): JSX.Element {
//debugger;
return (<Stack horizontal verticalFill wrap={true} gap={3}>
{
props.selectedPermissions.map((sp) =>
<div>
export function Legend(props: ILegendProps): JSX.Element {
return (
<Stack horizontal verticalFill wrap={true} tokens={{ childrenGap: 3 }}>
{props.selectedPermissions.map((sp) => (
<div key={sp.freindlyName}>
<DefaultButton
text={sp.freindlyName}
onClick={(e) => {
props.checkUncheckPermission(sp);
}}
onClick={() => props.checkUncheckPermission(sp)}
checked={sp.isChecked}
title={sp.freindlyName}
iconProps={{ iconName: sp.iconName, style: { color: sp.color } }}>
{sp.freindlyName}
</DefaultButton>
&nbsp;
</div>
)
}
))}
</Stack>
);
// return (<div>
// {
// props.selectedPermissions.map((sp)=><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{sp.freindlyName}&nbsp;:&nbsp;</span><span><Icon iconName={sp.iconName} style={{color:sp.color?sp.color:"FFFFFF"}} /></span></span> )
// }
// </div>
// );
}

View File

@ -1,4 +1,5 @@
@import "~office-ui-fabric-react/dist/sass/References.scss";
@import '~@fluentui/react/dist/sass/References.scss';
.spSecurity {
}

View File

@ -1,616 +1,486 @@
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { SPPermission } from "@microsoft/sp-page-context";
import * as React from 'react';
import { useState, useEffect } from 'react';
import { SPPermission } from '@microsoft/sp-page-context';
import { getFileTypeIconProps, initializeFileTypeIcons } from '@uifabric/file-type-icons';
import { filter, find, findIndex } from "lodash";
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { CommandBar } from "office-ui-fabric-react/lib/CommandBar";
import { ContextualMenuItemType, IContextualMenuItem } from "office-ui-fabric-react/lib/ContextualMenu";
import { DetailsList, IColumn, Selection, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Panel, PanelType } from "office-ui-fabric-react/lib/Panel";
import { Spinner } from "office-ui-fabric-react/lib/Spinner";
import * as React from "react";
import { filter, find, findIndex } from 'lodash';
import { Checkbox } from '@fluentui/react/lib/Checkbox';
import { CommandBar } from '@fluentui/react/lib/CommandBar';
import { ContextualMenuItemType, IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu';
import { DetailsList, IColumn, Selection, SelectionMode } from '@fluentui/react/lib/DetailsList';
import { Icon } from '@fluentui/react/lib/Icon';
import { Panel, PanelType } from '@fluentui/react/lib/Panel';
import { Spinner } from '@fluentui/react/lib/Spinner';
import SPSecurityService from '../../SPSecurityService';
import { Helpers, SPList, SPListItem, SPSiteUser } from '../../SPSecurityService';
import SelectedPermissionsPanel from '../containers/SelectedPermissionsPanel';
import { ISelectedPermission } from '../ISpSecurityWebPartProps';
import { ISpSecurityProps } from './ISpSecurityProps';
import { ISpSecurityState } from './ISpSecurityState';
import { Legend } from './Legend';
import styles from './SpSecurity.module.scss';
import SPSecurityService from "../../SPSecurityService";
import { Helpers, SPList, SPListItem, SPSiteUser } from "../../SPSecurityService";
import SelectedPermissionsPanel from "../containers/SelectedPermissionsPanel";
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import { ISpSecurityProps } from "./ISpSecurityProps";
import { ISpSecurityState } from "./ISpSecurityState";
import { Legend } from "./Legend";
import styles from "./SpSecurity.module.scss";
/* tslint:disable */
export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSecurityState> {
private svc: SPSecurityService = new SPSecurityService("ss");
private userSelection = new Selection();
private listSelection = new Selection();
constructor(props: any) {
super(props);
this.state = {
const SpSecurity: React.FC<ISpSecurityProps> = (props) => {
const [state, setState] = useState<ISpSecurityState>({
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [], adGroups: [] },
//permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions,
selectedPermissions: props.selectedPermissions,
showUserPanel: false,
showListPanel: false,
showEmail: this.props.showEmail,
showEmail: props.showEmail,
securityInfoLoaded: false,
showPermissionsPanel: false,
errors: []
errors: [],
});
};
this.expandCollapseList = this.expandCollapseList.bind(this);
this.collapseList = this.collapseList.bind(this);
this.collapseItem = this.collapseItem.bind(this);
this.expandList = this.expandList.bind(this);
this.renderTitle = this.renderTitle.bind(this);
this.renderUserItem = this.renderUserItem.bind(this);
this.getPermissionLevels = this.getPermissionLevels.bind(this);
this.selectUser = this.selectUser.bind(this);
this.parentIsExpanded = this.parentIsExpanded.bind(this);
this.renderUserSelected = this.renderUserSelected.bind(this);
}
public componentDidMount(): void {
const svc = new SPSecurityService('ss');
const userSelection = new Selection();
const listSelection = new Selection();
useEffect(() => {
initializeFileTypeIcons();
}
public componentDidUpdate(): void {
// disable postback of buttons. see https://github.com/SharePoint/sp-dev-docs/issues/492
if (Environment.type === EnvironmentType.ClassicSharePoint) {
const buttons: NodeListOf<HTMLButtonElement> = this.props.domElement.getElementsByTagName('button');
for (let i: number = 0; i < buttons.length; i++) {
if (buttons[i]) {
// Disable the button onclick postback
buttons[i].onclick = function () {
return false;
};
}
}
}
}
public componentWillReceiveProps(newProps: ISpSecurityProps) {
this.setState((current) => ({
...current,
selectedPermissions: newProps.selectedPermissions,
showEmail: newProps.showEmail
}));
}
public componentWillMount(): void {
//debugger;
this.svc.loadData(this.props.showHiddenLists, this.props.showCatalogs, this.props.aadHttpClient, false)
svc.loadData(props.showHiddenLists, props.showCatalogs, props.aadHttpClient)
.then((response) => {
const state: ISpSecurityState = {
securityInfo: response,
// permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions ? this.props.selectedPermissions : [],
showUserPanel: false,
showListPanel: false,
showPermissionsPanel: false,
showEmail: this.props.showEmail,
const filteredLists = response.lists.filter((list) =>
props.includeAdminSelectedLists
? !!find(props.adminSelectedLists, (asl) => list.id === asl)
: !find(props.adminSelectedLists, (asl) => list.id === asl)
);
setState((prevState) => ({
...prevState,
securityInfo: { ...response, lists: filteredLists },
selectedPermissions: props.selectedPermissions ?? [],
showEmail: props.showEmail,
securityInfoLoaded: true,
errors: []
};
// inlclude\exclude lists selected in property pane
//debugger;
state.securityInfo.lists = state.securityInfo.lists.filter((list) => {
if (this.props.includeAdminSelectedLists) { // include the lists
if (find(this.props.adminSelectedLists, (asl) => { return list.id === asl; })) {
return true;
} else {
return false;
}
} else { // exclude the lists
if (find(this.props.adminSelectedLists, (asl) => { return list.id === asl; })) {
return false;
}
else {
return true;
}
}
});
this.setState(state);
}).catch((errors: Array<string>) => {
this.setState((current) => ({ ...current, errors: errors, securityInfoLoaded: true }))
//debugger;
}));
})
.catch((errors: string[]) => {
setState((prevState) => ({ ...prevState, errors, securityInfoLoaded: true }));
});
}, [props]);
useEffect(() => {
if (props.selectedPermissions !== state.selectedPermissions || props.showEmail !== state.showEmail) {
setState((prevState) => ({
...prevState,
selectedPermissions: props.selectedPermissions,
showEmail: props.showEmail,
}));
}
public expandList(item: any): any {
if (item instanceof SPListItem && !item.serverRelativeUrl) { // its a listitem. nothing to do
return;
}
}, [props.selectedPermissions, props.showEmail]);
const expandList = (item: SPList | SPListItem): void => {
if (item instanceof SPListItem && !item.serverRelativeUrl) return;
if (item.isFetched) {
item.isExpanded = true;
this.setState(this.state);
setState({ ...state });
} else {
const level = item instanceof SPListItem ? item.level + 1 : 1;
const listTitle = item instanceof SPListItem ? item.listTitle : item.title;
}
else {
// get the items in the list/folder;
let level: number;
let listTitle: string;
if (item instanceof SPListItem) {
level = item.level + 1;
listTitle = item.listTitle;
}
else {
level = 1;
listTitle = item.title;
}
this.svc.loadFolderRoleAssigmentsDefinitionsMembers(listTitle, item.serverRelativeUrl, item.id, level, true).then((response) => {
// add them to the list after the parent
let position: number = findIndex(this.state.securityInfo.lists, (stateitem) => {
return stateitem.id === item.id;
});
this.state.securityInfo.lists.splice(position + 1, 0, ...response);
svc.loadFolderRoleAssignmentsDefinitionsMembers(listTitle, item.serverRelativeUrl, item.id, level)
.then((response) => {
const position = findIndex(state.securityInfo.lists, (stateItem) => stateItem.id === item.id);
const updatedLists = [...state.securityInfo.lists];
updatedLists.splice(position + 1, 0, ...response);
item.isExpanded = true;
item.isFetched = true;
this.setState(this.state);
}).catch((error) => {
let errors = this.state.errors;
errors.push(`There was an error fetching site users -- ${error.message}`);
this.setState((current) => ({ ...current, errors: errors }))
setState((prevState) => ({ ...prevState, securityInfo: { ...prevState.securityInfo, lists: updatedLists } }));
})
.catch((error: Error) => {
const errors = [...state.errors, `There was an error fetching site users -- ${error.message}`];
setState((prevState) => ({ ...prevState, errors }));
});
}
}
public collapseItem(itemId: string) {
let children = filter(this.state.securityInfo.lists, (otheritem) => {
return otheritem instanceof SPListItem && otheritem.parentId === itemId;
});
for (let childitem of children) {
childitem.isExpanded = false;
this.collapseItem(childitem.id);
};
}
public collapseList(item: any): any {
item.isExpanded = false;
this.collapseItem(item.id);
this.setState(this.state);
}
public expandCollapseList(item?: any, index?: number, column?: IColumn): any {
if (item.itemCount === 0) {
return;
}
if (item.isExpanded) {
this.collapseList(item);
}
else {
this.expandList(item);
}
}
public parentIsExpanded(item: SPListItem): boolean {
let parent = find(this.state.securityInfo.lists, (otheritem) => {
return otheritem.id === item.parentId;
const collapseItem = (itemId: string) => {
const children = filter(state.securityInfo.lists, (otherItem) => otherItem instanceof SPListItem && otherItem.parentId === itemId);
children.forEach((childItem) => {
childItem.isExpanded = false;
collapseItem(childItem.id);
});
return parent.isExpanded;
};
}
const collapseList = (item: SPList | SPListItem): void => {
item.isExpanded = false;
collapseItem(item.id);
setState({ ...state });
};
public renderItemTitle(item?: any, index?: number, column?: IColumn): any {
const extension = item.title.split('.').pop();
debugger;
const style = { marginLeft: item.level * 20 + 'px' }
return (
<div className={styles.itemTitle} style={style}>
<Icon {...getFileTypeIconProps({ extension: extension, size: 16 })} />
<span>&nbsp;{item.title}</span>
</div>);
const expandCollapseList = (item: SPList | SPListItem): void => {
if (item.itemCount === 0) return;
if (item.isExpanded) {
collapseList(item);
} else {
expandList(item);
}
public renderListTitle(item?: any, index?: number, column?: IColumn): any {
const iconName = item.itemCount > 0 ?
'FabricFormLibrary' :
'FabricFolder';
};
const renderTitle = (item: SPList | SPListItem): JSX.Element => {
if (item instanceof SPList) {
return renderListTitle(item);
} else if (item.type === 'Folder') {
return renderFolderTitle(item);
} else {
return renderItemTitle(item);
}
};
const renderListTitle = (item: SPList): JSX.Element => {
const iconName = item.itemCount > 0 ? 'FabricFormLibrary' : 'FabricFolder';
return (
<div className={styles.itemTitle} onClick={(e) => {
this.expandCollapseList(item);
}}>
<div className={styles.itemTitle} onClick={() => expandCollapseList(item)}>
<Icon iconName={iconName} className={styles.themecolor} />
<span>&nbsp;{item.title}</span>
</div>);
}
public renderFolderTitle(item?: any, index?: number, column?: IColumn): any {
const style = { marginLeft: item.level * 20 + 'px' }
const iconName = item.itemCount > 0 ?
'FabricFormLibrary' :
'FabricFolder';
</div>
);
};
const renderFolderTitle = (item: SPListItem): JSX.Element => {
const style = { marginLeft: `${item.level * 20}px` };
const iconName = item.itemCount > 0 ? 'FabricFormLibrary' : 'FabricFolder';
return (
<div className={styles.itemTitle} onClick={(e) => {
this.expandCollapseList(item);
}}>
<div className={styles.itemTitle} onClick={() => expandCollapseList(item)}>
<Icon iconName={iconName} style={style} className={styles.themecolor} />
<span>&nbsp;{item.title}</span>
</div>);
}
public renderTitle(item?: any, index?: number, column?: IColumn): any {
if (item instanceof SPList) {
return this.renderListTitle(item, index, column);
} else {
if (item.type === "Folder") {
return this.renderFolderTitle(item, index, column);
} else {
return this.renderItemTitle(item, index, column);
}
}
}
public renderUserItem(item: any, index: number, column: IColumn, effectivePermissions: ISelectedPermission[]): any {
let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => {
return su.upn.toString() === column.key;
});
// spin througg the selected permsiisopns and for the first hit, display that color. No Hit, then display empty
for (let selectedPermission of effectivePermissions ? effectivePermissions : []) {
if (Helpers.doesUserHavePermission(item, user, SPPermission[selectedPermission.permission],
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups, this.state.securityInfo.adGroups)) {
return (
<Icon iconName={selectedPermission.iconName} style={{ color: selectedPermission.color }} onClick={(e) => {
this.expandCollapseList(item);
}} />
</div>
);
}
}
// no hits
};
const renderItemTitle = (item: SPListItem): JSX.Element => {
const extension = item.title.split('.').pop();
const style = { marginLeft: `${item.level * 20}px` };
return (
<Icon iconName={item.iconName} onClick={(e) => {
this.expandCollapseList(item);
}} />
<div className={styles.itemTitle} style={style}>
<Icon {...getFileTypeIconProps({ extension, size: 16 })} />
<span>&nbsp;{item.title}</span>
</div>
);
};
}
public renderUserSelected(item?: SPSiteUser, index?: number, column?: IColumn): any {
return (
<Checkbox checked={item.isSelected} />
)
}
public addUserColumns(columns: IColumn[], users: SPSiteUser[], effectivePermissions: ISelectedPermission[]): IColumn[] {
for (let user of users) {
const addUserColumns = (
columns: IColumn[],
users: SPSiteUser[],
effectivePermissions: ISelectedPermission[]
): IColumn[] => {
for (const user of users) {
if (user.isSelected) {
if (
((user.principalType === 1 || user.principalType === -1) && this.props.showUsers)
||
(user.principalType === 4 && this.props.showSecurityGroups)
((user.principalType === 1 || user.principalType === -1) && props.showUsers) ||
(user.principalType === 4 && props.showSecurityGroups)
) {
if (
!props.showOnlyUsersWithPermission ||
Helpers.doesUserHaveAnyPermission(
state.securityInfo.lists,
user,
effectivePermissions.map(sp => {
const permissionKey = sp.permission as keyof typeof SPPermission;
return SPPermission[permissionKey];
}),
state.securityInfo.roleDefinitions,
state.securityInfo.siteGroups,
state.securityInfo.adGroups
)
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({
key: user.upn,
name: this.state.showEmail ? user.upn : user.name,
name: state.showEmail ? user.upn : user.name,
fieldName: "",
minWidth: 20,
maxWidth: 20,
onRender: (item?: any, index?: number, column?: IColumn) => {
//debugger;
return this.renderUserItem(item, index, column, effectivePermissions);
},
onRender: (item: SPListItem, index?: number, column?: IColumn) =>
renderUserItem(item, index!, column!, effectivePermissions),
headerClassName: styles.rotatedColumnHeader,
});
}
}
}
}
return columns;
}
public getPermissionLevels(): IContextualMenuItem[] {
return this.props.getPermissionTypes().map(pt => {
return {
key: pt.text,
name: pt.text,
onClick: (event, item) => {
this.setState((current) => ({ ...current, permission: item.name }));
}
}
})
}
private setShowUserPanel(showPanel: boolean): () => void {
return (): void => {
this.setState((current) => ({ ...current, showUserPanel: showPanel }));
};
}
private selectUser(userid: number, value: boolean) {
find(this.state.securityInfo.siteUsers, (su) => {
return (su.id === userid)
}).isSelected = value;
this.setState(this.state);
}
private setShowListPanel(showPanel: boolean): () => void {
return (): void => {
this.setState((current) => ({ ...current, showListPanel: showPanel }));
};
}
private selectList(listid: string, value: boolean) {
let list: SPList = find(this.state.securityInfo.lists, (su) => {
return (su.id === listid)
}) as SPList;
list.isSelected = value;
this.setState(this.state);
}
private checkUncheckPermission(perm: ISelectedPermission) {
//debugger;
var sps = this.state.selectedPermissions;
const idx = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == perm.permission; });
if (idx != -1) {
sps[idx].isChecked = !sps[idx].isChecked
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
public render(): React.ReactElement<ISpSecurityProps> {
if (!this.state.securityInfoLoaded) {
return (
<div >
<Spinner label={'Fetching security information, please wait...'} />
</div>
);
}
let userPanelCommands: IContextualMenuItem[] = [];
userPanelCommands.push({
icon: "BoxAdditionSolid",
key: "Add All Users",
name: "Add All Users",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.siteUsers) {
item.isSelected = true;
}
this.setState(this.state);
}
});
userPanelCommands.push({
icon: "BoxSubtractionSolid",
key: "RemoveAllUsers",
name: "Remove All Users",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.siteUsers) {
item.isSelected = false;
}
this.setState(this.state);
}
});
let listPanelCommands: IContextualMenuItem[] = [];
listPanelCommands.push({
const listPanelCommands: IContextualMenuItem[] = [
{
icon: "BoxAdditionSolid",
key: "Add All Lists",
name: "Add All Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.lists) {
onClick: () => {
const updatedLists = state.securityInfo.lists.map((item) => {
if (item instanceof SPList) {
item.isSelected = true;
}
}
this.setState(this.state);
}
return item;
});
listPanelCommands.push({
setState((prevState) => ({
...prevState,
securityInfo: {
...prevState.securityInfo,
lists: updatedLists,
},
}));
},
},
{
icon: "BoxSubtractionSolid",
key: "Remove All Lists",
name: "Remove All Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.lists) {
onClick: () => {
const updatedLists = state.securityInfo.lists.map((item) => {
if (item instanceof SPList) {
item.isSelected = false;
}
}
this.setState(this.state);
}
return item;
});
let commands: IContextualMenuItem[] = [];
if (this.props.letUserSelectPermission) {
setState((prevState) => ({
...prevState,
securityInfo: {
...prevState.securityInfo,
lists: updatedLists,
},
}));
},
},
];
commands.push({
icon: "AzureKeyVault",
key: "SecurityLevel2",
name: "Permission",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
this.setState((current) => ({ ...current, showPermissionsPanel: !current.showPermissionsPanel }));
}
}
const renderUserItem = (
item: SPListItem,
index: number,
column: IColumn,
effectivePermissions: ISelectedPermission[]
): JSX.Element => {
const user = find(state.securityInfo.siteUsers, (su) => su.upn.toString() === column.key);
for (const selectedPermission of effectivePermissions) {
const permissionKey = selectedPermission.permission as keyof typeof SPPermission;
if (user && Helpers.doesUserHavePermission(
item,
user,
SPPermission[permissionKey],
state.securityInfo.roleDefinitions,
state.securityInfo.siteGroups,
state.securityInfo.adGroups
)) {
return (
<Icon
iconName={selectedPermission.iconName}
style={{ color: selectedPermission.color }}
onClick={() => expandCollapseList(item)}
/>
);
}
if (this.props.letUserSelectUsers) {
}
return (
<Icon iconName={item.iconName} onClick={() => expandCollapseList(item)} />
);
};
if (!state.securityInfoLoaded) {
return (
<div>
<Spinner label={'Fetching security information, please wait...'} />
</div>
);
}
// Define the commands for users, lists, permissions, and display mode
const commands: IContextualMenuItem[] = [];
// User selection command
if (props.letUserSelectUsers) {
commands.push({
icon: "People",
key: "Users",
name: "Users",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
this.setState((current) => ({ ...current, showUserPanel: !current.showUserPanel }));
onClick: () => {
setState((current) => ({ ...current, showUserPanel: !current.showUserPanel }));
}
});
}
if (this.props.letUserSelectLists) {
// List selection command
if (props.letUserSelectLists) {
commands.push({
icon: "PageListSolid",
key: "Lists",
name: "Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
this.setState((current) => ({ ...current, showListPanel: !current.showListPanel }));
onClick: () => {
setState((current) => ({ ...current, showListPanel: !current.showListPanel }));
}
});
}
commands.push({
// Permission selection command
if (props.letUserSelectPermission) {
commands.push({
icon: "AzureKeyVault",
key: "SecurityLevel2",
name: "Permission",
itemType: ContextualMenuItemType.Normal,
onClick: () => {
setState((current) => ({ ...current, showPermissionsPanel: !current.showPermissionsPanel }));
}
});
}
// Display mode command
commands.push({
key: "DisplayMode",
title: "DisplayMode",
name: this.state.showEmail ? "Show Email" : "Show Name",
name: state.showEmail ? "Show Email" : "Show Name",
itemType: ContextualMenuItemType.Normal,
subMenuProps: {
items: [{
key: "ShowName",
name: "Show Name",
onClick: (event, item) => {
this.setState((current) => ({ ...current, showEmail: false }));
onClick: () => {
setState((current) => ({ ...current, showEmail: false }));
}
},
{
key: "ShowEmail",
name: "Show Email",
onClick: (event, item) => {
this.setState((current) => ({ ...current, showEmail: true }));
onClick: () => {
setState((current) => ({ ...current, showEmail: true }));
}
}]
}
});
let effectivePermissions = this.state.selectedPermissions.filter((sp) => { return sp.isChecked; })
let columns: Array<IColumn> = [
const effectivePermissions = state.selectedPermissions.filter((sp) => sp.isChecked);
const columns: IColumn[] = [
{
key: "title", name: "Title", isResizable: true, fieldName: "title",
minWidth: this.props.listTitleColumnWidth, maxWidth: this.props.listTitleColumnWidth,
onRender: this.renderTitle, isRowHeader: true
},
];
let displayColumns: IColumn[] = this.addUserColumns(columns, this.state.securityInfo.siteUsers, effectivePermissions);
let displayItems: (SPList | SPListItem)[] = filter(this.state.securityInfo.lists, (item) => {
return (
(item instanceof SPList && item.isSelected)
||
((item instanceof SPListItem) && (this.parentIsExpanded(item)))
)
})
let errorMessages = [];
for (let error of this.state.errors) {
errorMessages.push(<li>{error}</li>)
key: "title",
name: "Title",
isResizable: true,
fieldName: "title",
minWidth: props.listTitleColumnWidth,
maxWidth: props.listTitleColumnWidth,
onRender: renderTitle,
isRowHeader: true
}
];
const displayColumns: IColumn[] = addUserColumns(columns, state.securityInfo.siteUsers, effectivePermissions);
const displayItems: (SPList | SPListItem)[] = filter(state.securityInfo.lists, (item) => {
return (
(item instanceof SPList && item.isSelected) ||
((item instanceof SPListItem) && item.parentId && find(state.securityInfo.lists, l => l.id === item.parentId)?.isExpanded)
);
});
return (
<div >
<ul>{errorMessages}</ul>
<CommandBar
items={commands}
/>
<>
<CommandBar items={commands} />
<br />
<Legend
selectedPermissions={this.state.selectedPermissions}
selectedPermissions={state.selectedPermissions}
checkUncheckPermission={(e) => {
//debugger;
this.checkUncheckPermission(e);
}
const sps = [...state.selectedPermissions];
const idx = findIndex(sps, (sp) => sp.permission === e.permission);
if (idx !== -1) {
sps[idx].isChecked = !sps[idx].isChecked;
setState((prevState) => ({ ...prevState, selectedPermissions: sps }));
}
}}
/>
<DetailsList
items={displayItems}
columns={displayColumns}
selectionMode={SelectionMode.none}
className={styles.SPFXSecurityGrid}
/>
<SelectedPermissionsPanel
isOpen={this.state.showPermissionsPanel}
SelectedPermissions={this.props.selectedPermissions}
onPropertyChange={(propertyName: string, oldValue: Array<ISelectedPermission>, newValue: Array<ISelectedPermission>) => {
this.setState((current) => ({ ...current, selectedPermissions: newValue }));
isOpen={state.showPermissionsPanel}
SelectedPermissions={props.selectedPermissions}
onPropertyChange={(propertyName, oldValue, newValue) => {
setState((prevState) => ({ ...prevState, selectedPermissions: newValue }));
}}
closePanel={() => {
this.setState((current) => ({ ...current, showPermissionsPanel: false }));
setState((prevState) => ({ ...prevState, showPermissionsPanel: false }));
}}
/>
<Panel
isBlocking={false}
isOpen={state.showUserPanel}
onDismiss={() => setState((prevState) => ({ ...prevState, showUserPanel: false }))}
type={PanelType.medium}
headerText="Select Users"
closeButtonAriaLabel="Close"
>
</SelectedPermissionsPanel>
<Panel
isBlocking={false}
isOpen={this.state.showUserPanel}
onDismiss={this.setShowUserPanel(false)}
type={PanelType.medium}
headerText='Select Users'
closeButtonAriaLabel='Close'>
<CommandBar items={userPanelCommands} />
<DetailsList
selection={this.userSelection}
selectionMode={SelectionMode.none}
items={this.state.securityInfo.siteUsers}
columns={[
{
key: "isSelected", name: "Select", fieldName: "isSelected", minWidth: 30, onRender: (item) => <Checkbox
checked={item.isSelected}
onChange={(element, value) => { this.selectUser(item.id, value); }}
/>
},
{ key: "id", name: "Name", fieldName: "name", minWidth: 400 },
]}
/>
</Panel>
<Panel
isBlocking={false}
isOpen={this.state.showListPanel}
onDismiss={this.setShowListPanel(false)}
type={PanelType.medium}
headerText='Select Lists'
closeButtonAriaLabel='Close'>
<CommandBar items={listPanelCommands} />
<DetailsList
selection={this.listSelection}
selection={userSelection}
selectionMode={SelectionMode.none}
items={filter(this.state.securityInfo.lists, (l) => {
return l instanceof SPList;
})}
items={state.securityInfo.siteUsers}
columns={[
{
key: "isSelected", name: "Select", fieldName: "isSelected", minWidth: 30,
onRender: (item) => <Checkbox
key: "isSelected",
name: "Select",
fieldName: "isSelected",
minWidth: 30,
onRender: (item) => (
<Checkbox
checked={item.isSelected}
onChange={(element, value) => { this.selectList(item.id, value); }}
onChange={(element, value) => {
const user = find(state.securityInfo.siteUsers, (su) => su.id === item.id);
if (user) {
user.isSelected = value!;
setState((prevState) => ({ ...prevState }));
}
}}
/>
),
},
{ key: "id", name: "Title", fieldName: "title", minWidth: 500 },
{ key: "id", name: "Name", fieldName: "name", minWidth: 400 }
]}
/>
</Panel>
</div>
)
}
<Panel
isBlocking={false}
isOpen={state.showListPanel}
onDismiss={() => setState((prevState) => ({ ...prevState, showListPanel: false }))}
type={PanelType.medium}
headerText="Select Lists"
closeButtonAriaLabel="Close"
>
<CommandBar items={listPanelCommands} />
<DetailsList
selection={listSelection}
selectionMode={SelectionMode.none}
items={filter(state.securityInfo.lists, (l) => l instanceof SPList)}
columns={[
{
key: "isSelected",
name: "Select",
fieldName: "isSelected",
minWidth: 30,
onRender: (item) => (
<Checkbox
checked={item.isSelected}
onChange={(element, value) => {
const list = find(state.securityInfo.lists, (l) => l.id === item.id);
if (list) {
list.isSelected = value!;
setState((prevState) => ({ ...prevState }));
}
}}
/>
),
},
{ key: "id", name: "Title", fieldName: "title", minWidth: 500 }
]}
/>
</Panel>
</>
);
};
export default SpSecurity;

View File

@ -1,92 +1,47 @@
import * as React from 'react';
import { Button, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { ColorPicker } from "office-ui-fabric-react/lib/ColorPicker";
import { IColor } from "office-ui-fabric-react/lib/Color";
import { Dialog } from "office-ui-fabric-react/lib/Dialog";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { ComboBox, IComboBox, IComboBoxOption, } from "office-ui-fabric-react/lib/ComboBox";
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button, PrimaryButton } from '@fluentui/react/lib/Button';
import { Dropdown, IDropdownOption } from "@fluentui/react/lib/Dropdown";
import { ColorPicker } from "@fluentui/react/lib/ColorPicker";
import { IColor } from "@fluentui/react/lib/Color";
import { Dialog } from "@fluentui/react/lib/Dialog";
import { TextField } from "@fluentui/react/lib/TextField";
import { Icon } from "@fluentui/react/lib/Icon";
import { ComboBox, IComboBoxOption } from "@fluentui/react/lib/ComboBox";
import { Label } from '@fluentui/react/lib/Label';
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import { SPPermission } from "@microsoft/sp-page-context";
import { findIndex } from "lodash";
export interface IColorIconSelectorPanelProps {
isOpen: boolean;
onPermissionChange(color: ISelectedPermission): void;
closePanel(): void;
onPermissionChange: (perm: ISelectedPermission) => void;
closePanel: () => void;
title: string;
subText: string;
currentPerm: ISelectedPermission;
SelectedPermissions: ISelectedPermission[];
}
export interface IColorIconSelectionPanelState {
currentPerm: ISelectedPermission;
}
export default class ColorIconSelectionPanel extends React.Component<IColorIconSelectorPanelProps, IColorIconSelectionPanelState> {
private iconNames = [
"Add",
"Back",
"BlockContact",
"Cancel",
"Cat",
"CheckBox",
"CheckBoxComposite",
"CheckMark",
"ChevronDown",
"ChevronUp",
"CircleFill",
"CircleRing",
"Clear",
"Contact",
"Contrast",
"Delete",
"Design",
"eDiscovery",
"Edit",
"EditSolid12",
"Emoji2",
"Error",
"FavoriteStar",
"FavoriteStarFill",
"Glasses",
"HomeSolid",
"IncidentTriangle",
"LaptopSecure",
"LifeSaver",
"LifeSaverLock",
"Lock",
"LockSolid",
"Mail",
"More",
"PageLink",
"People",
"PeopleAdd",
"Permissions",
"PinSolid12",
"RedEye",
"Sad",
"SecurityGroup",
"Settings",
"Share",
"Sync",
"TriangleSolid",
"UnEditable",
"UnEditable2",
"Unlock",
"UserWarning",
"View",
private iconNames: string[] = [
"Add", "Back", "BlockContact", "Cancel", "Cat", "CheckBox", "CheckBoxComposite", "CheckMark",
"ChevronDown", "ChevronUp", "CircleFill", "CircleRing", "Clear", "Contact", "Contrast", "Delete",
"Design", "eDiscovery", "Edit", "EditSolid12", "Emoji2", "Error", "FavoriteStar", "FavoriteStarFill",
"Glasses", "HomeSolid", "IncidentTriangle", "LaptopSecure", "LifeSaver", "LifeSaverLock", "Lock",
"LockSolid", "Mail", "More", "PageLink", "People", "PeopleAdd", "Permissions", "PinSolid12", "RedEye",
"Sad", "SecurityGroup", "Settings", "Share", "Sync", "TriangleSolid", "UnEditable", "UnEditable2",
"Unlock", "UserWarning", "View"
];
constructor(props: IColorIconSelectorPanelProps) {
super(props);
//debugger;
this.state = {
currentPerm: this.props.currentPerm
currentPerm: { ...this.props.currentPerm } // Ensure immutability of props
};
}
@ -95,114 +50,97 @@ export default class ColorIconSelectionPanel extends React.Component<IColorIconS
this.onClosePanel();
}
private onClosePanel(element?: any): void {
private onClosePanel(): void {
this.props.closePanel();
}
private renderIcon(props?, defaultRender?: (props?) => JSX.Element | null): JSX.Element {
return (<div>
<Icon iconName={props.text} /> {props.text}
</div>);
private renderIcon(option?: IComboBoxOption): JSX.Element {
return (
<div>
<Icon iconName={option?.text} /> {option?.text}
</div>
);
}
private getIconOptions(): IComboBoxOption[] {
let options: IComboBoxOption[] = [];
for (var iconName of this.iconNames) {
var option: IComboBoxOption = {
key: iconName, text: iconName,
};
options.push(option);
}
return options;
return this.iconNames.map(iconName => ({
key: iconName,
text: iconName
}));
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array<IDropdownOption>();
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
perms.push({
text: perm,
key: perm,
disabled: findIndex(this.props.SelectedPermissions, (sp: ISelectedPermission) => { return sp.permission == perm; }) !== -1
});
return Object.keys(SPPermission).map((perm) => {
const isDisabled = findIndex(this.props.SelectedPermissions, sp => sp.permission === perm) !== -1;
if (typeof SPPermission[perm as keyof typeof SPPermission] === "object") {
return { text: perm, key: perm, disabled: isDisabled };
}
return null;
}).filter(Boolean) as IDropdownOption[]; // Remove null values
}
return perms;
}
public render(): JSX.Element {
const { currentPerm } = this.state;
//Renders content
return (
<Dialog
isOpen={this.props.isOpen}
onDismiss={() => this.onClosePanel()}
onDismiss={this.onClosePanel.bind(this)}
title={this.props.title}
subText={this.props.subText}
>
<Dropdown label="Permission"
<Dropdown
label="Permission"
options={this.getPermissionTypes()}
defaultSelectedKey={this.state.currentPerm.permission}
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number) => {
this.state.currentPerm.permission = option.text;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</Dropdown>
<TextField label="Friendly Name"
defaultValue={this.state.currentPerm.freindlyName}
onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
this.state.currentPerm.freindlyName = newValue;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</TextField>
<Label>Color:</Label>
<ColorPicker
color={this.state.currentPerm.color}
onChange={(event: React.FormEvent<HTMLDivElement>, color: IColor) => {
this.state.currentPerm.color = color.str;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</ColorPicker>
<ComboBox allowFreeform={true}
label="Select Icon(or enter name of a Fabric Icon):"
options={this.getIconOptions()}
onRenderOption={this.renderIcon}
text={this.state.currentPerm.iconName}
onChange={(event: React.FormEvent<IComboBox>, option?: IComboBoxOption, index?: number, value?: string) => {
//debugger;
this.state.currentPerm.iconName = value ? value : option.text;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
defaultSelectedKey={currentPerm.permission}
onChange={(event, option) => {
const updatedPerm = { ...currentPerm, permission: option?.text || '' };
this.setState({ currentPerm: updatedPerm });
}}
/>
<TextField
label="Freindly Name"
defaultValue={currentPerm.freindlyName}
onChange={(event, newValue) => {
const updatedPerm = { ...currentPerm, freindlyName: newValue || '' };
this.setState({ currentPerm: updatedPerm });
}}
/>
<Label>Color:</Label>
<ColorPicker
color={currentPerm.color}
onChange={(event, color: IColor) => {
const updatedPerm = { ...currentPerm, color: color.str };
this.setState({ currentPerm: updatedPerm });
}}
/>
<ComboBox
allowFreeform
label="Select Icon (or enter name of a Fabric Icon):"
options={this.getIconOptions()}
onRenderOption={this.renderIcon.bind(this)}
text={currentPerm.iconName}
onChange={(event, option, index, value) => {
const updatedPerm = { ...currentPerm, iconName: value || option?.text || '' };
this.setState({ currentPerm: updatedPerm });
}}
/>
<Label>Display:</Label>
<Icon iconName={this.state.currentPerm.iconName} style={{ color: this.state.currentPerm.color }} />
<br></br>
<br></br>
<Button onClick={() => {
this.onClosePanel();
}}>Cancel</Button>
<Icon iconName={currentPerm.iconName} style={{ color: currentPerm.color }} />
<br />
<Button onClick={this.onClosePanel.bind(this)}>Cancel</Button>
<PrimaryButton
disabled={
this.state.currentPerm.color == null ||
this.state.currentPerm.freindlyName == null ||
this.state.currentPerm.iconName == null ||
this.state.currentPerm.permission == null
!currentPerm.color ||
!currentPerm.freindlyName ||
!currentPerm.iconName ||
!currentPerm.permission
}
onClick={() => {
//debugger;
this.saveChanges();
}}
>Save</PrimaryButton>
onClick={this.saveChanges.bind(this)}
>
Save
</PrimaryButton>
</Dialog>
);
}
}

View File

@ -6,56 +6,55 @@ import PropertyFieldSelectedPermissionsHost, { IPropertyFieldSelectedPermissions
export interface IPropertyFieldSelectedPermissionsProps {
label: string;
initialValue?: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
getSelectedPermissions: () => Array<ISelectedPermission>;
initialValue?: ISelectedPermission[]; // Updated type here
onPropertyChange(propertyPath: string, oldValue: ISelectedPermission[], newValue: ISelectedPermission[]): void; // Updated type here
getSelectedPermissions: () => ISelectedPermission[];
}
export interface IPropertyFieldSelectedPermissionsPropsInternal extends IPropertyPaneCustomFieldProps {
label: string;
initialValue?: Array<ISelectedPermission>;
initialValue?: ISelectedPermission[];
targetProperty: string;
onRender(elem: HTMLElement): void;
onDispose(elem: HTMLElement): void;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
SelectedPermissions: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: ISelectedPermission[], newValue: ISelectedPermission[]): void; // Updated type here
SelectedPermissions: ISelectedPermission[];
}
class PropertyFieldSelectedPermissionsBuilder implements IPropertyPaneField<IPropertyFieldSelectedPermissionsPropsInternal> {
//Properties defined by IPropertyPaneField
public type = 1;//IPropertyPaneFieldType.Custom;
public type = 1; // IPropertyPaneFieldType.Custom
public targetProperty: string;
public properties: IPropertyFieldSelectedPermissionsPropsInternal;
//Custom properties
private label: string;
private onPropertyChange: (propertyPath: string, oldValue: any, newValue: any) => void;
private customProperties: any;
private onPropertyChange: (propertyPath: string, oldValue: ISelectedPermission[], newValue: ISelectedPermission[]) => void; // Updated type here
private customProperties: ISelectedPermission[];
public constructor(_targetProperty: string, _properties: IPropertyFieldSelectedPermissionsPropsInternal) {
this.render = this.render.bind(this);
this.properties = _properties;
this.label = _properties.label;
this.properties.onDispose = this.dispose;
this.properties.onRender = this.render;
this.onPropertyChange = _properties.onPropertyChange;
this.customProperties = _properties.SelectedPermissions ? _properties.SelectedPermissions : [];
}
private render(elem: HTMLElement): void {
// what other args does this get? does it get ctx and a callback?
const element: React.ReactElement<IPropertyFieldSelectedPermissionsHostProps> = React.createElement(PropertyFieldSelectedPermissionsHost, {
label: this.label,
onPropertyChange: this.onPropertyChange,
SelectedPermissions: this.customProperties,
});
ReactDom.render(element, elem);
}
private dispose(elem: HTMLElement): void {
}
}
export function PropertyFieldSelectedPermissions(targetProperty: string, properties: IPropertyFieldSelectedPermissionsProps): IPropertyPaneField<IPropertyFieldSelectedPermissionsPropsInternal> {
//Create an internal properties object from the given properties
var newProperties: IPropertyFieldSelectedPermissionsPropsInternal = {
export function PropertyFieldSelectedPermissions(
targetProperty: string,
properties: IPropertyFieldSelectedPermissionsProps
): IPropertyPaneField<IPropertyFieldSelectedPermissionsPropsInternal> {
const newProperties: IPropertyFieldSelectedPermissionsPropsInternal = {
label: properties.label,
targetProperty: targetProperty,
key: targetProperty,
@ -65,9 +64,6 @@ export function PropertyFieldSelectedPermissions(targetProperty: string, propert
onDispose: null,
onRender: null,
};
//Calles the PropertyFieldSelectedPermissions builder object
//This object will simulate a PropertyFieldCustom to manage his rendering process
return new PropertyFieldSelectedPermissionsBuilder(targetProperty, newProperties);
}

View File

@ -1,29 +1,26 @@
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button } from 'office-ui-fabric-react/lib/Button';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { Label } from '@fluentui/react/lib/Label';
import { Button } from '@fluentui/react/lib/Button';
import { Icon } from '@fluentui/react/lib/Icon';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode } from "@fluentui/react/lib/DetailsList";
import { IDropdownOption } from "@fluentui/react/lib/Dropdown";
import { IPropertyPaneDropdownOption } from "@microsoft/sp-property-pane";
import { SPPermission } from "@microsoft/sp-page-context";
import SelectedPermissionsPanel from "./SelectedPermissionsPanel";
export interface IPropertyFieldSelectedPermissionsHostProps {
label: string;
initialValue?: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
SelectedPermissions: Array<ISelectedPermission>;
initialValue?: ISelectedPermission[]; // Updated type here
onPropertyChange(propertyPath: string, oldValue: ISelectedPermission[], newValue: ISelectedPermission[]): void; // Updated type here
SelectedPermissions: ISelectedPermission[];
}
export interface IPropertyFieldSelectedPermissionsHostState {
openPanel?: boolean;
SelectedPermissions: Array<ISelectedPermission>;
SelectedPermissions: ISelectedPermission[];
}
export default class PropertyFieldSelectedPermissionsHost extends React.Component<IPropertyFieldSelectedPermissionsHostProps, IPropertyFieldSelectedPermissionsHostState> {
public panelColumns: IColumn[] = [
{
@ -33,10 +30,10 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
minWidth: 100,
maxWidth: 100,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
onRender: (item?: ISelectedPermission) => { // Updated type here
return (
<div>
{item.permission}
{item?.permission}
</div>
);
}
@ -48,10 +45,10 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
minWidth: 90,
maxWidth: 90,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
onRender: (item?: ISelectedPermission) => { // Updated type here
return (
<div>
{item.freindlyName}
{item?.freindlyName}
</div>
);
}
@ -63,13 +60,14 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
minWidth: 50,
maxWidth: 50,
isResizable: false,
onRender: (item?: ISelectedPermission, index?: number, column?: IColumn) => {
onRender: (item?: ISelectedPermission) => { // Updated type here
return (
<Icon iconName={item.iconName} style={{ color: item.color }} />
<Icon iconName={item?.iconName} style={{ color: item?.color }} />
);
}
}
];
constructor(props: IPropertyFieldSelectedPermissionsHostProps) {
super(props);
this.state = {
@ -77,10 +75,11 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
openPanel: false
};
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array();
const perms: IPropertyPaneDropdownOption[] = [];
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
if (typeof SPPermission[perm as keyof typeof SPPermission] === "object") {
perms.push({
text: perm,
key: perm
@ -89,16 +88,16 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
}
return perms;
}
private onOpenPanel(element?: any): void {
this.setState((current) => ({ ...current, openPanel: true }));
private onOpenPanel(): void {
this.setState({ openPanel: true });
}
private onClosePanel(element?: any): void {
//debugger;
this.setState((current) => ({ ...current, openPanel: false }));
private onClosePanel(): void {
this.setState({ openPanel: false });
}
public render(): JSX.Element {
//debugger;
//This Details list Renders the short list of permissions in the panel
return (
<div style={{ marginBottom: '8px' }}>
<Label>{this.props.label}</Label>
@ -108,26 +107,20 @@ export default class PropertyFieldSelectedPermissionsHost extends React.Componen
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.none}
/>
<Button
onClick={(e) => this.onOpenPanel()}>
<Button onClick={() => this.onOpenPanel()}>
Edit Permissions and Colors
</Button>
<SelectedPermissionsPanel
isOpen={this.state.openPanel}
onPropertyChange={(prop, oldval, newval) => {
this.setState((current) => ({ ...current, SelectedPermissions: [...newval] }));
onPropertyChange={(prop: string, oldval: ISelectedPermission[], newval: ISelectedPermission[]) => {
this.setState({ SelectedPermissions: [...newval] });
this.props.onPropertyChange("SelectedPermissions", this.props.SelectedPermissions, newval);
}}
closePanel={() => { this.onClosePanel(); }}
SelectedPermissions={this.props.SelectedPermissions}
/>
</div>
);
}
}

View File

@ -1,32 +1,29 @@
import { findIndex, filter, first } from "underscore";
import { findIndex, filter } from "lodash";
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button } from 'office-ui-fabric-react/lib/Button';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode, Selection } from "office-ui-fabric-react/lib/DetailsList";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { CommandBar } from "office-ui-fabric-react/lib/CommandBar";
import { Label } from '@fluentui/react/lib/Label';
import { Button, IconButton } from '@fluentui/react/lib/Button';
import { Panel, PanelType } from '@fluentui/react/lib/Panel';
import { Icon } from '@fluentui/react/lib/Icon';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode, Selection } from "@fluentui/react/lib/DetailsList";
import { Dropdown, IDropdownOption } from "@fluentui/react/lib/Dropdown";
import { CommandBar } from "@fluentui/react/lib/CommandBar";
import { SPPermission } from "@microsoft/sp-page-context";
import ColorIconSelectorDialog from "./ColorIconSelectorDialog";
import { disableBodyScroll } from "@uifabric/utilities";
export interface ISelectedPemissionPanelProps {
isOpen: boolean;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
closePanel(): void;
SelectedPermissions: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: ISelectedPermission[], newValue: ISelectedPermission[]): void;
closePanel: () => void;
SelectedPermissions: ISelectedPermission[];
}
export interface ISelectedPemissionPanelState {
SelectedPermissions: Array<ISelectedPermission>;
export interface ISelectedPemissionPanelState {
SelectedPermissions: ISelectedPermission[];
CurrentlySelectedPermission?: ISelectedPermission;
isColorIconSelecorDialogOpen: boolean;
}
export default class SelectedPermissionsPanel extends React.Component<ISelectedPemissionPanelProps, ISelectedPemissionPanelState> {
private selection: Selection;
private columns: IColumn[] = [
@ -37,19 +34,17 @@ export default class SelectedPermissionsPanel extends React.Component<ISelectedP
minWidth: 150,
maxWidth: 150,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
onRender: (item: ISelectedPermission) => {
return (
<Dropdown
options={this.getPermissionTypes()}
defaultSelectedKey={item.permission}
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, ix?: number) => {
var sps = this.state.SelectedPermissions;
item.permission = option.text;
this.setState((current) => ({ ...current, SelectedPermissions: [...this.state.SelectedPermissions] }));
}}>
</Dropdown>
onChange={(event, option) => {
const sps = [...this.state.SelectedPermissions];
item.permission = option?.text ?? '';
this.setState({ SelectedPermissions: [...sps] });
}}
/>
);
}
},
@ -59,34 +54,35 @@ export default class SelectedPermissionsPanel extends React.Component<ISelectedP
fieldName: 'freindlyName',
minWidth: 150,
maxWidth: 150,
isResizable: true
isResizable: true,
onRender: (item: ISelectedPermission) => {
return <div>{item.freindlyName}</div>;
}
},
{
key: 'color',
name: 'Display',
fieldName: 'color',
minWidth: 100,
maxWidth: 100,
minWidth: 150,
maxWidth: 200,
isResizable: true,
onRender: (item?: ISelectedPermission, index?: number, column?: IColumn) => {
onRender: (item: ISelectedPermission) => {
return (
<div>
<Icon iconName={item.iconName} style={{ color: item.color }} />
<Button onClick={(e) => {
this.setState((current) => ({
...current,
<Icon iconName={item.iconName} style={{ color: item.color, paddingRight: '10px' }} />
<Button
onClick={() => {
this.setState({
isColorIconSelecorDialogOpen: true,
CurrentlySelectedPermission: item
}));
}}>Edit Permission</Button>
});
}}
>
Edit Permission
</Button>
</div>
);
}
},
{
key: 'commands',
@ -95,184 +91,146 @@ export default class SelectedPermissionsPanel extends React.Component<ISelectedP
minWidth: 50,
maxWidth: 50,
isResizable: false,
onRender: (item?: any, index?: number, column?: IColumn) => {
onRender: (item: ISelectedPermission, index: number) => {
return (
<div>
<IconButton
iconProps={{ iconName: 'Up', }}
style={{ display: index === 0 ? "none" : "normal" }}
onClick={(e) => {
this.moveColumnUp(item);
}}>
</IconButton>
iconProps={{ iconName: 'Up' }}
disabled={index === 0}
onClick={() => this.moveColumnUp(item)}
/>
<IconButton
iconProps={{ iconName: 'Down', }}
style={{ display: index === this.state.SelectedPermissions.length - 1 ? "none" : "normal" }}
onClick={(e) => {
this.moveColumnDown(item);
}}>
</IconButton>
iconProps={{ iconName: 'Down' }}
disabled={index === this.state.SelectedPermissions.length - 1}
onClick={() => this.moveColumnDown(item)}
/>
<IconButton
iconProps={{ iconName: 'Delete', }}
onClick={(e) => {
this.removeColumn(item);
}}>
</IconButton>
iconProps={{ iconName: 'Delete' }}
onClick={() => this.removeColumn(item)}
/>
</div>
);
}
},
}
];
constructor(props: ISelectedPemissionPanelProps) {
super(props);
this.selection = new Selection({
onSelectionChanged: () => console.log("onSelectionChanged...")
});
this.selection = new Selection();
this.state = {
SelectedPermissions: this.props.SelectedPermissions,
isColorIconSelecorDialogOpen: false,
SelectedPermissions: props.SelectedPermissions,
isColorIconSelecorDialogOpen: false
};
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array<IDropdownOption>();
const perms: IDropdownOption[] = [];
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
if (typeof SPPermission[perm as keyof typeof SPPermission] === "object") {
perms.push({
text: perm,
key: perm,
disabled: findIndex(this.state.SelectedPermissions, (sp: ISelectedPermission) => { return sp.permission == perm; }) !== -1
disabled: findIndex(this.state.SelectedPermissions, sp => sp.permission === perm) !== -1
});
}
}
return perms;
}
// private addColumn(): void {
// let unusedPermission = first(filter(this.getPermissionTypes(), (pt) => { return !pt.disabled }));
// if (unusedPermission) {
// const col: ISelectedPermission = {
// "permission": unusedPermission.text,
// "freindlyName": unusedPermission.text,
// color: "FFFFFF",
// iconName: "Blocked"
// };
// var sp = this.state.SelectedPermissions;
// sp.push(col);
// this.setState((current) => ({ ...current, SelectedPermissions: [...sp] }));
// }
// }
private removeColumn(column: ISelectedPermission): void {
var sps = filter(this.state.SelectedPermissions, (o: ISelectedPermission) => { return o.permission !== column.permission; });
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
const sps = filter(this.state.SelectedPermissions, (o: ISelectedPermission) => o.permission !== column.permission);
this.setState({ SelectedPermissions: sps });
}
private removeAllColumns(): void {
this.setState((current) => ({ ...current, SelectedPermissions: [] }));
this.setState({ SelectedPermissions: [] });
}
private moveColumnUp(column: ISelectedPermission): void {
const sps = [...this.state.SelectedPermissions];
const index = findIndex(sps, sp => sp.permission === column.permission);
if (index > 0) {
[sps[index - 1], sps[index]] = [sps[index], sps[index - 1]];
this.setState({ SelectedPermissions: sps });
}
}
var sps: ISelectedPermission[] = this.state.SelectedPermissions;
const index = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == column.permission; });
if (index != -1) {
sps[index] = sps.splice(index - 1, 1, sps[index])[0];
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
private moveColumnDown(column: ISelectedPermission): void {
const sps = [...this.state.SelectedPermissions];
const index = findIndex(sps, sp => sp.permission === column.permission);
if (index < sps.length - 1) {
[sps[index], sps[index + 1]] = [sps[index + 1], sps[index]];
this.setState({ SelectedPermissions: sps });
}
}
var sps: ISelectedPermission[] = this.state.SelectedPermissions;
const index = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == column.permission; });
if (index != -1) {
sps[index] = sps.splice(index + 1, 1, sps[index])[0];
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
private saveChanges(): void {
if (this.props.onPropertyChange) {
this.props.onPropertyChange("SelectedPermissions", this.props.SelectedPermissions, this.state.SelectedPermissions);
this.onClosePanel();
}
}
private onOpenPanel(element?: any): void {
this.setState((current) => ({ ...current, openPanel: true }));
}
private onClosePanel(element?: any): void {
private onClosePanel(): void {
this.props.closePanel();
}
public render(): JSX.Element {
//Renders content
return (
<Panel
isOpen={this.props.isOpen} hasCloseButton={true}
isOpen={this.props.isOpen}
hasCloseButton={true}
onDismiss={() => this.onClosePanel()}
isLightDismiss={true} type={PanelType.largeFixed}
headerText="Select Permissions" >
isLightDismiss={true}
type={PanelType.largeFixed}
headerText="Select Permissions"
>
<Label>The grid will display the color of the first match, so list permissions from most restricted to least restricted (i.e. manageLists, then deleteListItems, then viewListItems)</Label>
<CommandBar items={[{
<CommandBar
items={[
{
key: "AddColumns",
name: "Add a Permission",
icon: "Add",
onClick: () => {
this.setState((current) => ({
...current,
this.setState({
isColorIconSelecorDialogOpen: true,
CurrentlySelectedPermission: { color: null, freindlyName: null, iconName: null, permission: null }
}));
CurrentlySelectedPermission: { color: '', freindlyName: '', iconName: '', permission: '' }
});
}
},
{
key: "ClearAllColums",
key: "ClearAllColumns",
name: "Remove All Permissions",
canCheck: true,
icon: "Delete",
onClick: () => {
this.removeAllColumns();
}
onClick: () => this.removeAllColumns()
},
{
key: "save",
name: "Save",
canCheck: true,
icon: "Save",
onClick: () => {
this.saveChanges();
onClick: () => this.saveChanges()
}
}
]} />
{this.state.isColorIconSelecorDialogOpen &&
]}
/>
{this.state.isColorIconSelecorDialogOpen && (
<ColorIconSelectorDialog
isOpen={this.state.isColorIconSelecorDialogOpen}
SelectedPermissions={this.state.SelectedPermissions}
title={`Edit Icon and color for ${this.state.CurrentlySelectedPermission.permission}`}
subText={`Edit Icon and color for ${this.state.CurrentlySelectedPermission.permission}`}
title={`Edit Icon and color for ${this.state.CurrentlySelectedPermission?.permission}`}
subText={`Edit Icon and color for ${this.state.CurrentlySelectedPermission?.permission}`}
currentPerm={this.state.CurrentlySelectedPermission}
closePanel={() => {
this.setState((current) => ({ ...current, isColorIconSelecorDialogOpen: false }));
}}
closePanel={() => this.setState({ isColorIconSelecorDialogOpen: false })}
onPermissionChange={(perm: ISelectedPermission) => {
//debugger;
var sps = this.state.SelectedPermissions;
const idx = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == perm.permission; });
const sps = [...this.state.SelectedPermissions];
const idx = findIndex(sps, sp => sp.permission === perm.permission);
if (idx === -1) {
sps.push(perm);
} else {
sps[idx] = perm;
}
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
this.setState({ SelectedPermissions: sps });
}}
/>
}
)}
<DetailsList
items={this.state.SelectedPermissions}
columns={this.columns}
@ -281,9 +239,6 @@ export default class SelectedPermissionsPanel extends React.Component<ISelectedP
selection={this.selection}
/>
</Panel>
);
}
}

View File

@ -1,11 +1,7 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
{ "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json",
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
"src/**/*.ts",
"src/**/*.tsx"
],
"compilerOptions": {
"moduleResolution": "node",
@ -26,13 +22,14 @@
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
"es2015.collection",
"es2015.promise"
],
"noImplicitAny": true
}
}

View File

@ -1,30 +0,0 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}