* Initial commit

* Update README.md

* fix casing of NameId, NameIdIssuer

* show  name or email

* fix headerHeight on popouts

* include Prinipal Type wuth site users

* show users or groups

* add images

* setup default config

* readme

* readme

* readme

* show only lists in user selection

* stylin!

* mo stylin'

* use folder icons

* added loading spinner

* comments on AD Groups

* remove graph call for AD groups for now
This commit is contained in:
Russell gove 2017-12-01 09:38:51 -05:00 committed by Vesa Juvonen
parent f8c3294680
commit 9b24bee1e7
41 changed files with 1775 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1 @@
* text=auto

32
samples/react-securitygrid/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,14 @@
# Folders
.vscode
coverage
node_modules
sharepoint
src
temp
# Files
*.csproj
.git*
.yo-rc.json
gulpfile.js
tsconfig.json

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.1",
"libraryName": "spsecurity-webpart-3",
"libraryId": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"environment": "spo"
}
}

View File

@ -0,0 +1,111 @@
# SPFX React Grid
## Summary
React-securitygrid is an SPFX webpart that uses React and Office-UI-Fabric to render a grid showing which users have access to which lists/libraries/folders/files on a Web as shown here:
![config panel](./src/images/MainDisplay.PNG)
Empty libraries are displayed withh a black folder icon, those with items are displayed with a white folder. The user can expand a list or library by clicking on the desired row. For deeply nested folders the Title column can be resized by drag and drop. The display shows a 'filled-in' circle if the user has the selected permission to the given list, library, file or folder. (NOTE:The grid does not currently take into account access give via membership in an active directory group). The user must have permissions to access lists and enumerate permissions in order to view the grid.
The user can change the permission being tested by cliking the Permission in the command bar and selecting a new Permission:
![permission panel](./src/images/selectPermissionsPopout.PNG)
The user can change which users are being shown in the grid by selecting the users button in the command bar and selecting a desired users:
![Select users](./src/images/SelectUsersPopout.PNG)
The user can change which lists are being shown in the grid by selecting the lists button in the command bar and selecting a desired lists:
![Select Lists](./src/images/Selectlistspopout.PNG)
The user can change alternate between displaying user names and emails selecting the Show Email/Show Name button in the command bar and selecting the desired option:
![Select Mode](./src/images/SelectDisplayModePopout.PNG)
The the first configuration panel of the webpart is shown below:
![config panel](./src/images/Configuration.PNG)
Permission Settings
The Permission Type dropdown sets the default permission to check.The 'Let user select Permission' checkbox determines whether the user can change this permission.
User Settings
The Show Email or Name Toggle determines whether the name or email is displayed by default.
The Show Security Groups checkbox determines whether SharePoint Security groups are included in the grid.
The Show Users checkbox determines whether Users are included in the grid.
The Let Users Select users checkbox determines whether Users can filter the selected users in the grid.
Display Settings
The Initial Title column width determines the initial width of the Title column(it can be resized).
The second configuarion pannel allows the owner to configure the List Settings
![List Confoguration panel](./src/images/ListConfiguration.PNG)
List Settings
The Show Hidden Lists checkbox determines whether Hidden lists are displayed.
The Show System Lists checkbox determines whether System Lists (Catalogs) are included in the grid.
The Show Users checkbox determines whether Users are included in the grid.
The Let Users Select lists checkbox determines whether Users can filter the selected lists in the grid.
Select Lists
The Include/Exclude Selected lists Toggle determines whether the lists selected are to be included or excluded.
The admin can select lists and libraries below to have them included/excluded from the grid
## Notes
This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/russgove/SPSecurity.
## Used SharePoint Framework Version
1.3.4
## Applies to
* [SharePoint Framework Developer Preview](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Prerequisites
> React, Office-UI-Fabric, sp-pnp-js, lodash
## Solution
Solution|Author(s)
--------|---------
react-securitygrid | Russell Gove
## Version history
Version|Date|Comments
-------|----|--------
1.0|December 31, 2016|Initial version
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
- `npm install`
- `gulp serve`

View File

@ -0,0 +1,19 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"sp-security-bundle": {
"components": [
{
"entrypoint": "./lib/webparts/spSecurity/SpSecurityWebPart.js",
"manifest": "./src/webparts/spSecurity/SpSecurityWebPart.manifest.json"
}
]
}
},
"localizedResources": {
"spSecurityStrings": "lib/webparts/spSecurity/loc/{locale}.js",
"PropertyControlStrings": "./node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
},
"externals": {}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spsecurity-webpart-3",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "spsecurity-webpart-3-client-side-solution",
"id": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "solution/spsecurity-webpart-3.sppkg"
}
}

View File

@ -0,0 +1,9 @@
{
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,49 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"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-case": true,
"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": false,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false,
"no-debugger":false,
"no-unused-variable": false,
"max-line-lenth": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

View File

@ -0,0 +1,41 @@
{
"name": "spsecurity-webpart-3",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"@microsoft/sp-build-web": "^1.3.4",
"@microsoft/sp-client-base": "~1.0.0",
"@microsoft/sp-core-library": "^1.3.4",
"@microsoft/sp-module-interfaces": "^1.3.4",
"@microsoft/sp-webpart-base": "^1.3.4",
"@microsoft/sp-webpart-workbench": "^1.3.4",
"@pnp/spfx-property-controls": "1.0.0",
"@types/react": "15.0.38",
"@types/react-addons-shallow-compare": "0.14.17",
"@types/react-addons-test-utils": "0.14.15",
"@types/react-addons-update": "0.14.14",
"@types/react-dom": "0.14.18",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"lodash": "^4.17.4",
"office-ui-fabric-react": "^4.21.2",
"react": "15.4.2",
"react-dom": "15.4.2",
"sp-pnp-js": "^3.0.1"
},
"devDependencies": {
"@microsoft/sp-build-web": "^1.3.4",
"@microsoft/sp-module-interfaces": "^1.3.4",
"@microsoft/sp-webpart-workbench": "^1.3.4",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"gulp": "~3.9.1"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,49 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"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-case": true,
"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": false,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false,
"no-debugger":false,
"no-unused-variable": false,
"max-line-lenth": false
}
}
}

View File

@ -0,0 +1,448 @@
import pnp from "sp-pnp-js";
import { find, indexOf, includes } from "lodash";
import { SPPermission } from "@microsoft/sp-page-context";
import { GraphHttpClient, HttpClientResponse, IGraphHttpClientOptions } from "@microsoft/sp-http";
export interface ISPSecurableObject {
id: number;
roleAssignments: SPRoleAssignment[];
}
export class SPBasePermissions {
public low: number;
public high: number;
public constructor(high: any, low: any) {
this.high = parseInt(high, 10);
this.low = parseInt(low, 10);
}
}
export enum securableType {
List
}
export class SPSiteGroup {
public id: number;
public title: string;
public isHiddenInUI: boolean;
public isShareByEmailGuestUse: boolean;
public isSiteAdmin: boolean;
public userIds: number[];
}
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
}
export class SPRoleDefinition {
public id: number;
public basePermissions: SPBasePermissions;
public description: string;
public hidden: boolean;
public name: string;
public constructor(id: number, basePermissions: SPBasePermissions, description: string, hidden: boolean, name: string) {
this.id = id;
this.basePermissions = basePermissions;
this.description = description;
this.hidden = hidden;
this.name = name;
}
}
export class SPSecurityInfo {
public siteUsers: SPSiteUser[];
public siteGroups: SPSiteGroup[];
public roleDefinitions: SPRoleDefinition[];
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>();
}
}
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 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 SPExternalUser {
public nameId: string;
public nameIdIssuer: string;
}
export class SPRoleAssignment {
public roleDefinitionIds: number[] = [];
public users: number[] = [];
public groups: number[] = [];
public userId: SPExternalUser;
}
export class Helpers {
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups) {
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups);
for (const permission of permissions) {
if (
((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low))
&&
((permission.high & requestedpermission.value.High) === (requestedpermission.value.High))
) {
return true;
}
}
return false;
}
public static getBasePermissionsForRoleDefinitiuonIds(selectedRoleDefinitionIds: number[],
roleDefinitions: SPRoleDefinition[]): Array<SPBasePermissions> {
let basePermissions = [];
for (const selectedRoleDefinitionId of selectedRoleDefinitionIds) {
for (const roleDefinition of roleDefinitions) {
if (roleDefinition.id === selectedRoleDefinitionId) {
basePermissions.push(roleDefinition.basePermissions);
}
}
}
// 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[]) {
const roleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups);
let roleDefinitionIds: number[] = [];
for (const roleAssignment of roleAssignments) {
for (const roleDefinitionID of roleAssignment.roleDefinitionIds) {
roleDefinitionIds.push(roleDefinitionID);
}
}
// for (var rax = 0; rax < roleAssignments.length; rax++) {
// for (var rdx = 0; rdx < roleAssignments[rax].roleDefinitionIds.length; rdx++) {
// roleDefinitionIds.push(roleAssignments[rax].roleDefinitionIds[rdx]);
// }
// }
var userPermissions = Helpers.getBasePermissionsForRoleDefinitiuonIds(roleDefinitionIds, roles);
return userPermissions;
}
public static findGroup(groupId: number, groups: SPSiteGroup[]): SPSiteGroup {
return find(groups, (g) => { return g.id === groupId; });
}
public static userIsInGroup(userId: number, groupId: number, groups: SPSiteGroup[]): boolean {
let group: SPSiteGroup = this.findGroup(groupId, groups);
return includes(group.userIds, userId);
}
public static GetRoleAssignmentsForUser(securableObject: ISPSecurableObject, user: SPSiteUser,
groups: SPSiteGroup[]): SPRoleAssignment[] {
let selectedRoleAssignments: SPRoleAssignment[] = [];
for (const roleAssignment of securableObject.roleAssignments) {
for (const assignedUser of roleAssignment.users) {
if (assignedUser === user.id) {
selectedRoleAssignments.push(roleAssignment);
}
}
for (const groupId of roleAssignment.groups) {
// if the user is in the group add the assignment
if (this.userIsInGroup(user.id, groupId, groups)) {
selectedRoleAssignments.push(roleAssignment);
}
}
if (roleAssignment.userId
&& user.userId
&& roleAssignment.userId.nameId
&& roleAssignment.userId.nameIdIssuer
&& roleAssignment.userId.nameId === user.userId.nameId
&& roleAssignment.userId.nameIdIssuer === user.userId.nameIdIssuer) {
selectedRoleAssignments.push(roleAssignment);
}
}
return selectedRoleAssignments;
}
}
export default class SPSecurityService {
public siteUrl: string;
public constructor(siteUrl: string) {
this.siteUrl = siteUrl;
}
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>"
};
return pnp.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();
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
itemToAdd.title = listItem.Folder.Name;
itemToAdd.serverRelativeUrl = listItem.Folder.ServerRelativeUrl;
itemToAdd.itemCount = listItem.Folder.ItemCount;
} else {
if (listItem.File) {// its a file
itemToAdd.title = listItem.File.Name;
itemToAdd.serverRelativeUrl = listItem.File.ServerRelativeUrl;
} else { // its a listitem
itemToAdd.title = listItem.Title;
}
}
for (let roleAssignmentObject of listItem.RoleAssignments) {
let roleAssignment: SPRoleAssignment = {
roleDefinitionIds: [],
users: [],
groups: [],
userId: null // external user
};
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);
}
return itemsToAdd;
});
}
public async getMembersOfAdGroup(graphHttpClient: GraphHttpClient, groupName: string): Promise<any> {
return graphHttpClient.get("v1.0/groups?$filter=displayName eq '" + groupName + "'&$expand=members",
GraphHttpClient.configurations.v1).then((response) => {
response.json().then((data) => {
debugger;
});
}).catch((err) => {
});
}
/// Loads data for intial display
public loadData(showHiddenLists: boolean, showCatalogs: boolean, graphHttpClient: GraphHttpClient, forceReload: boolean): Promise<SPSecurityInfo> {
let securityInfo: SPSecurityInfo = new SPSecurityInfo();
let batch: any = pnp.sp.createBatch();
pnp.sp.web.siteUsers
.inBatch(batch).get().then((response) => {
securityInfo.siteUsers = response.map((u) => {
let user: SPSiteUser = new SPSiteUser();
user.isSelected = true;
user.id = u.Id;
user.name = u.Title;
user.principalType=u.PrincipalType;
user.upn = u.LoginName.split('|')[2];
if (u.UserId) {
user.userId = new SPExternalUser();
user.userId.nameId = u.UserId.NameId;
user.userId.nameIdIssuer = u.UserId.NameIdIssuer;
}
return user;
});
return securityInfo.siteUsers;
});
pnp.sp.web.siteGroups.expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get().then((response) => {
let AdGroupPromises: Array<Promise<any>> = [];
// if group contains an ad group(PrincipalType=4) expand it
securityInfo.siteGroups = response.map((grp) => {
let siteGroup: SPSiteGroup = new SPSiteGroup();
siteGroup.userIds = [];
siteGroup.id = grp.Id;
siteGroup.title = grp.Title;
for (let user of grp.Users) {
if (user.PrincipalType === 4) {
// To make this work with AD groups, I need to stop using the integer UserId of the user as the
// key to the Users list and use UPN/Email instead. Users in an AD group may not have a accessed the site
// yet , and so will not be in the userinfo list.
// I can get the users from the AD group using the graph HTTPClient. and add them to the Users array
// in my state. Would also need to add a list of AD groups and their members to my state.
// then in DoesUserHavePermission method, Just inlude permissions of any ADQ Groups the user is in.
//
// Also should check for users that have been invited but not yet accessed the site.
// graphHttpClient.get("v1.0/groups?$filter=displayName eq '" + user.Title + "'&$expand=members", GraphHttpClient.configurations.v1).then((response2) => {
// response2.json().then((data) => {
// //debugger;
// });
// }).catch((err) => {
// });
} else {
siteGroup.userIds.push(user.Id);
}
}
return siteGroup;
});
return Promise.all(AdGroupPromises).then(() => {
return securityInfo.siteGroups;
});
});
pnp.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;
});
let filters: string[] = [];
if (!showHiddenLists) {
filters.push("Hidden eq false");
}
if (!showCatalogs) {
filters.push("IsCatalog eq false");
}
let filter: string = filters.join(" and ");
pnp.sp.web.lists
.expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member",
"RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId")
.filter(filter)
.inBatch(batch).get().then((response) => {
securityInfo.lists = response.map((listObject) => {
let mylist: SPList = new SPList();
mylist.isSelected = true;// Shoudl be shown in the UI, user can deslect it in the ui
mylist.title = listObject.Title;
mylist.id = listObject.Id;
mylist.hidden = listObject.Hidden;
mylist.serverRelativeUrl = listObject.RootFolder.ServerRelativeUrl;
mylist.type = securableType.List;// to differeentiate foldes from lists
mylist.itemCount = listObject.ItemCount;
mylist.isExpanded = false;
mylist.hasBeenRetrieved = false;
mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject) => {
let roleAssignment: SPRoleAssignment = new SPRoleAssignment();
if (roleAssignmentObject.Member.UserId) {
roleAssignment.userId = new SPExternalUser();
roleAssignment.userId.nameId = roleAssignmentObject.Member.UserId.NameId;
roleAssignment.userId.nameIdIssuer = roleAssignmentObject.Member.UserId.NameIdIssuer;
}
if (roleAssignmentObject.Member.Users) {
roleAssignment.users = roleAssignmentObject.Member.Users.map((user) => {
return user.Id;
});
}
if (roleAssignmentObject.Member.Groips) {
roleAssignment.groups = roleAssignmentObject.Member.Groups.map((group) => {
return group.Id;
});
}
mylist.roleAssignments = roleAssignmentObject.RoleDefinitionBindings.map((roleDefinitionBinding) => {
roleAssignment.roleDefinitionIds.push(roleDefinitionBinding.Id as number);
});
return roleAssignment;
});
return mylist;
});
});
return batch.execute().then(() => {
return securityInfo;
});
}
}

View File

@ -0,0 +1,20 @@
import { SPSiteUser } from "../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
export interface ISpSecurityWebPartProps {
users: SPSiteUser[];
permission: string;
showHiddenLists: boolean;
showCatalogs:boolean;
letUserSelectPermission:boolean;
letUserSelectUsers:boolean;
letUserSelectLists:boolean;
includeAdminSelectedLists:boolean; // true to inlude them, false to excluder
adminSelectedLists:string[];
listTitleColumnWidth:number;
showEmail:boolean; //0 show name, 1 show email
showSecurityGroups:boolean; // show PrincipalType=4
showUsers:boolean; // show PrincipalType=1
}

View File

@ -0,0 +1,40 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"alias": "SpSecurityWebPart",
"componentType": "WebPart",
"version": "*", // The "*" signifies that the version should be taken from the package.json
"manifestVersion": 2,
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*/
"safeWithCustomScriptDisabled": false,
"preconfiguredEntries": [
{
"groupId": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"group": {
"default": "Under Development"
},
"title": {
"default": "SPSecurity"
},
"description": {
"default": "Security Grid Display"
},
"officeFabricIconFontName": "LifesaverLock",
"properties": {
"permission": "viewListItems",
"showHiddenLists": false,
"showCatalogs": false,
"letUserSelectPermission": true,
"letUserSelectUsers": true,
"letUserSelectLists": true,
"includeAdminSelectedLists": false,
"listTitleColumnWidth": 120,
"showEmail": false,
"showUsers": true
}
}
]
}

View File

@ -0,0 +1,183 @@
import * as React from "react";
import * as ReactDom from "react-dom";
import { Version } from "@microsoft/sp-core-library";
import { SPPermission } from "@microsoft/sp-page-context";
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown, IPropertyPaneDropdownOption,
PropertyPaneCheckbox,
PropertyPaneToggle
} from "@microsoft/sp-webpart-base";
import pnp from "sp-pnp-js";
import * as strings from "spSecurityStrings";
import SpSecurity from "./components/SpSecurity";
import { ISpSecurityProps } from "./components/ISpSecurityProps";
import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps";
import { PropertyPaneSlider } from "@microsoft/sp-webpart-base/lib/propertyPane/propertyPaneFields/propertyPaneSlider/PropertyPaneSlider";
import PropertyPane from "@microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane";
export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> {
public onInit(): Promise<void> {
return super.onInit().then(_ => {
pnp.setup({
spfxContext: this.context,
defaultCachingStore: "session", // or "local"
defaultCachingTimeoutSeconds: 30,
globalCacheDisable: true // or true to disable caching in case of debugging/testing
});
});
}
public render(): void {
const props: ISpSecurityProps = {
permission: this.properties.permission,
showHiddenLists: this.properties.showHiddenLists,
showCatalogs: this.properties.showCatalogs,
showEmail: this.properties.showEmail,
showSecurityGroups: this.properties.showSecurityGroups,
showUsers: this.properties.showUsers,
letUserSelectPermission: this.properties.letUserSelectPermission,
letUserSelectUsers: this.properties.letUserSelectUsers,
letUserSelectLists: this.properties.letUserSelectLists,
includeAdminSelectedLists: this.properties.includeAdminSelectedLists,
adminSelectedLists: this.properties.adminSelectedLists,
listTitleColumnWidth: this.properties.listTitleColumnWidth,
users: this.properties.users,
getPermissionTypes: this.getPermissionTypes,
graphHttpClient: this.context.graphHttpClient
};
const element: React.ReactElement<ISpSecurityProps> = React.createElement(
SpSecurity, props
);
ReactDom.render(element, this.domElement);
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
public getPermissionTypes(): IPropertyPaneDropdownOption[] {
let perms = new Array();
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
perms.push({
text: perm,
key: perm
});
}
}
return perms;
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: "Configuration"
},
groups: [
{
groupName: "Permission Settings",
groupFields: [
PropertyPaneDropdown("permission", {
label: "Permission Type",
options: this.getPermissionTypes()
}),
PropertyPaneCheckbox("letUserSelectPermission", {
text: "Let user select Permission"
}),
]
},
{
groupName: "User Settings",
groupFields: [
PropertyPaneToggle("showEmail", {
label: "Show Email or Name",
onText: "Show Email",
offText: "Show Name",
}),
PropertyPaneCheckbox("showSecurityGroups", {
text: "Show Security Groups"
}),
PropertyPaneCheckbox("showUsers", {
text: "Show Users"
}),
PropertyPaneCheckbox("letUserSelectUsers", {
text: "Let user select Users"
})
]
},
{
groupName: "Display Settings",
groupFields: [
PropertyPaneSlider("listTitleColumnWidth", {
label: "Initial title column width",
min: 1,
max: 1000
}),
]
}
]
},
{
header: {
description: "Configure Lists"
},
groups: [
{
groupName: "List Settings",
groupFields: [
PropertyPaneCheckbox("showHiddenLists", {
text: "Show Hidden Lists"
}),
PropertyPaneCheckbox("showCatalogs", {
text: "Show System Lists"
}),
PropertyPaneCheckbox("letUserSelectLists", {
text: "Let user select Lists"
}),
]
},
{
groupName: "Select Lists",
groupFields: [
PropertyPaneToggle("includeAdminSelectedLists", {
label: "Inclued/exclude selected lists",
onText: "Include selected lists",
offText: "Exclude selected lists",
}),
PropertyFieldListPicker("adminSelectedLists", {
label: 'Select lists to include/exclude',
selectedList: this.properties.adminSelectedLists,
includeHidden: this.properties.showHiddenLists,
orderBy: PropertyFieldListPickerOrderBy.Title,
disabled: false,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: null,
deferredValidationTime: 0,
key: 'listPickerFieldId',
multiSelect: true
}),
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,23 @@
import { SPSiteUser } from "../../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
import {IPropertyPaneDropdownOption} from "@microsoft/sp-webpart-base";
import { GraphHttpClient } from "@microsoft/sp-http";
export interface ISpSecurityProps {
users: SPSiteUser[];
permission: string;
showHiddenLists: boolean;
showCatalogs:boolean;
getPermissionTypes:()=> IPropertyPaneDropdownOption[];
graphHttpClient: GraphHttpClient;
letUserSelectPermission:boolean;
letUserSelectUsers:boolean;
letUserSelectLists:boolean;
includeAdminSelectedLists:boolean; // true to inlude them, false to excluder
adminSelectedLists:string[];
listTitleColumnWidth:number;
showEmail:boolean; //0 show name, 1 show email
showSecurityGroups:boolean; // show PrincipalType=4
showUsers:boolean; // show PrincipalType=1
}

View File

@ -0,0 +1,10 @@
import { SPSecurityInfo } from "../../SPSecurityService";
export interface ISpSecurityState {
securityInfo: SPSecurityInfo;
permission: string;
showUserPanel:boolean;
showListPanel:boolean;
showEmail:boolean; //0 show name, 1 show email
securityInfoLoaded:boolean;
}

View File

@ -0,0 +1,9 @@
.spSecurity {
}
.themecolor{
color: "[theme:themePrimary, default:#ff0000]"
}
.nonbrandeddocumentcolor{
color: "#00FF00"
}

View File

@ -0,0 +1,24 @@
@import 'node_modules/office-ui-fabric-react/dist/sass/Fabric.scss';
//*Set the Header are to 140px so it can hold the rotated headers
.SPFXSecurityGrid .ms-DetailsHeader { //* when adjusting line hieight also need to adjust second parameter to transform:translate below
height: 140px;
white-space: nowrap;
}
//*Set the wrappers around the headers to display at 45degree angles
.ms-DetailsHeader-cell.rotatedColumnHeader{
transform: translate(25px,40px ) //*Change second parameter when adjusting line height
rotate(315deg);
padding-bottom: 90px;
width: 36px !important;
}
.ms-DetailsHeader-cell{
vertical-align: bottom;
}
.ms-DetailsHeader-cell span {
overflow: visible;
}
.ms-List-cell:nth-child(even) .ms-DetailsRow {
background: #EEE;
}

View File

@ -0,0 +1,578 @@
import * as React from "react";
import styles from "./SpSecurity.module.scss";
import { ISpSecurityProps } from "./ISpSecurityProps";
import { ISpSecurityState } from "./ISpSecurityState";
import SPSecurityService from "../../SPSecurityService";
import { SPListItem, SPList, SPSiteUser, Helpers } from "../../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
import { indexOf, findIndex, find, filter, } from "lodash";
import { DetailsList, IColumn, SelectionMode, IDetailsRowProps, Selection } from 'office-ui-fabric-react/lib/DetailsList';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { CommandBar } from "office-ui-fabric-react/lib/CommandBar";
import { Spinner } from "office-ui-fabric-react/lib/Spinner";
import { IContextualMenuItem, ContextualMenuItemType } from "office-ui-fabric-react/lib/ContextualMenu";
import { Panel, PanelType } from "office-ui-fabric-react/lib/Panel";
import { right } from "glamor";
/* tslint:disable */
require('./spSecurity.css'); // loads the SpSecurity,css with unmodified names
export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSecurityState> {
private svc: SPSecurityService = new SPSecurityService("ss");
private userSelection = new Selection();
private listSelection = new Selection();
private validBrandIcons = " accdb csv docx dotx mpp mpt odp ods odt one onepkg onetoc potx ppsx pptx pub vsdx vssx vstx xls xlsx xltx xsn ";
constructor(props: any) {
super(props);
this.state = {
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [] },
permission: this.props.permission,
showUserPanel: false,
showListPanel: false,
showEmail: this.props.showEmail,
securityInfoLoaded: false
};
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 componentWillMount(): void {
this.svc.loadData(this.props.showHiddenLists, this.props.showCatalogs, this.props.graphHttpClient, false).then((response) => {
const state: ISpSecurityState = {
securityInfo: response,
permission: this.props.permission,
showUserPanel: false,
showListPanel: false,
showEmail: this.props.showEmail,
securityInfoLoaded: true
};
// inlclude\exclude lists selected in property pane
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((err) => {
debugger;
});
}
public expandList(item: any): any {
if (item instanceof SPListItem && !item.serverRelativeUrl) { // its a listitem. nothing to do
return;
}
if (item.isFetched) {
item.isExpanded = true;
this.setState(this.state);
}
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);
item.isExpanded = true;
item.isFetched = true;
this.setState(this.state);
}).catch((err) => {
debugger;
});
}
}
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;
});
return parent.isExpanded;
}
public renderItemTitle(item?: any, index?: number, column?: IColumn): any {
let extension = item.title.split('.').pop();
let classname = "ms-u-smOffset" + (item.level);
if (this.validBrandIcons.indexOf(" " + extension + " ") !== -1) {
classname += " ms-Icon ms-BrandIcon--" + extension + " ms-BrandIcon--icon16 ";
}
else {
classname += " ms-Icon ms-Icon--TextDocument " + styles.themecolor;
}
return (
<div>
<div className={classname} />
<span >&nbsp;{item.title}</span>
</div>);
}
public renderListTitle(item?: any, index?: number, column?: IColumn): any {
let classname = " ms-Icon ";
if (item.itemCount > 0) {
classname += " ms-Icon ms-Icon--FabricFormLibrary " + styles.themecolor;
} else {
classname += " ms-Icon ms-Icon--FabricFolder ";
}
return (
<div onClick={(e) => {
this.expandCollapseList(item);
}}>
<div className={classname} />
<span >&nbsp;{item.title}</span>
</div>);
}
public renderFolderTitle(item?: any, index?: number, column?: IColumn): any {
let classname = "ms-u-smOffset" + (item.level);
if (item.itemCount > 0) {
classname += " ms-Icon ms-Icon--FabricFormLibrary " + styles.themecolor;
} else {
classname += " ms-Icon ms-Icon--FabricFolder ";
}
return (
<div onClick={(e) => {
this.expandCollapseList(item);
}}>
<div className={classname} />
<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 getIcon(item?: any, index?: number, column?: IColumn): string {
// debugger;
// let classname: string = "";
// if (item instanceof SPList || item.type==="Folder") {
// if (item.itemCount === 0) {
// return "FabricFolderFill";
// } else {
// return "FabricFolder";
// }
// } else{
// return "ms-Icon ms-Icon--ExcelDocument"
// }
// }
// public renderTitle(item?: any, index?: number, column?: IColumn): any {
// let classname: string = "";
// if (item instanceof SPListItem) {
// classname = "ms-u-smOffset" + (item.level);
// }
// return (
// <div className={classname}>
// <div style={{ float: "left" }}>
// <Icon iconName={this.getIcon(item, index, column)} onClick={(e) => {
// this.expandCollapseList(item);
// }} />
// </div>
// <div>&nbsp;{item.title}</div>
// <div style={{ clear: "both" }} />
// </div>
// );
// }
public renderUserItem(item?: any, index?: number, column?: IColumn): any {
let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => {
return su.id.toString() === column.key;
});
if (Helpers.doesUserHavePermission(item, user, SPPermission[this.state.permission],
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups)) {
return (
<Icon iconName="CircleFill" onClick={(e) => {
this.expandCollapseList(item);
}} />
);
} else {
return (
<Icon iconName="LocationCircle" onClick={(e) => {
this.expandCollapseList(item);
}} />
);
}
}
public renderUserSelected(item?: SPSiteUser, index?: number, column?: IColumn): any {
return (
<Checkbox checked={item.isSelected} />
)
}
public addUserColumns(columns: IColumn[], users: SPSiteUser[]): IColumn[] {
for (let user of users) {
if (user.isSelected) {
if (
(user.principalType === 1 && this.props.showUsers)
||
(user.principalType === 4 && this.props.showSecurityGroups)
)
columns.push({
key: user.id.toString(),
name: this.state.showEmail ? user.upn : user.name,
fieldName: "",
minWidth: 20,
maxWidth: 20,
onRender: this.renderUserItem,
headerClassName: "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);
}
public render(): React.ReactElement<ISpSecurityProps> {
if (!this.state.securityInfoLoaded) {
return (
<div >
<Spinner label={'Fetching security information, please wait...'} />
</div>
);
}
debugger;
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({
icon: "BoxAdditionSolid",
key: "Add All Lists",
name: "Add All Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.lists) {
if (item instanceof SPList) {
item.isSelected = true;
}
}
this.setState(this.state);
}
});
listPanelCommands.push({
icon: "BoxSubtractionSolid",
key: "Remove All Lists",
name: "Remove All Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.lists) {
if (item instanceof SPList) {
item.isSelected = false;
}
}
this.setState(this.state);
}
});
let commands: IContextualMenuItem[] = [];
if (this.props.letUserSelectPermission) {
commands.push({
title: "Permission",
name: "Permission:",
key:
"permissionlabel"
})
commands.push({
icon: "AzureKeyVault",
key: "SecurityLevel",
title: "Permission",
label: "sss",
name: this.state.permission ? this.state.permission : "Select Permission",
itemType: ContextualMenuItemType.Normal,
items: this.getPermissionLevels()
});
}
if (this.props.letUserSelectUsers) {
commands.push({
icon: "People",
key: "Users",
name: "Users",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
this.setState((current) => ({ ...current, showUserPanel: !current.showUserPanel }));
}
});
}
if (this.props.letUserSelectLists) {
commands.push({
icon: "PageListSolid",
key: "Lists",
name: "Lists",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
this.setState((current) => ({ ...current, showListPanel: !current.showListPanel }));
}
});
}
commands.push({
key: "DisplayMode",
title: "DisplayMode",
name: this.state.showEmail ? "Show Email" : "Show Name",
itemType: ContextualMenuItemType.Normal,
items: [{
key: "ShowName",
name: "Show Name",
onClick: (event, item) => {
debugger;
this.setState((current) => ({ ...current, showEmail: false }));
}
},
{
key: "ShowEmail",
name: "Show Email",
onClick: (event, item) => {
debugger;
this.setState((current) => ({ ...current, showEmail: true }));
}
}]
});
let columns: Array<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);
let displayItems: (SPList | SPListItem)[] = filter(this.state.securityInfo.lists, (item) => {
return (
(item instanceof SPList && item.isSelected)
||
((item instanceof SPListItem) && (this.parentIsExpanded(item)))
)
})
return (
<div >
<CommandBar
items={commands}
/>
<DetailsList
items={displayItems}
columns={displayColumns}
selectionMode={SelectionMode.none}
className="SPFXSecurityGrid"
/>
<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}
selectionMode={SelectionMode.none}
items={filter(this.state.securityInfo.lists, (l) => {
return l instanceof SPList;
})}
columns={[
{
key: "isSelected", name: "Select", fieldName: "isSelected", minWidth: 30,
onRender: (item) => <Checkbox
checked={item.isSelected}
onChange={(element, value) => { this.selectList(item.id, value); }}
/>
},
{ key: "id", name: "Title", fieldName: "title", minWidth: 500 },
]}
/>
</Panel>
</div>
)
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ISpSecurityStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'spSecurityStrings' {
const strings: ISpSecurityStrings;
export = strings;
}

View File

@ -0,0 +1,9 @@
/// <reference types="mocha" />
import { assert } from 'chai';
describe('SpSecurityWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"types": [
"es6-promise",
"es6-collections",
"webpack-env"
]
}
}

View File

@ -0,0 +1,11 @@
// Type definitions for Microsoft ODSP projects
// Project: ODSP
/* Global definition for UNIT_TEST builds
Code that is wrapped inside an if(UNIT_TEST) {...}
block will not be included in the final bundle when the
--ship flag is specified */
declare const UNIT_TEST: boolean;
/* Global defintion for SPO builds */
declare const DATACENTER: boolean;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />