Dev (#365)
* 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
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
* text=auto
|
|
@ -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
|
|
@ -0,0 +1,14 @@
|
|||
# Folders
|
||||
.vscode
|
||||
coverage
|
||||
node_modules
|
||||
sharepoint
|
||||
src
|
||||
temp
|
||||
|
||||
# Files
|
||||
*.csproj
|
||||
.git*
|
||||
.yo-rc.json
|
||||
gulpfile.js
|
||||
tsconfig.json
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.1.1",
|
||||
"libraryName": "spsecurity-webpart-3",
|
||||
"libraryId": "788271fb-ee9b-40df-8381-eb3dc70d1982",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -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`
|
||||
|
||||
|
||||
|
||||
|
|
@ -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": {}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spsecurity-webpart-3",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 32 KiB |
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}),
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.spSecurity {
|
||||
|
||||
}
|
||||
.themecolor{
|
||||
color: "[theme:themePrimary, default:#ff0000]"
|
||||
}
|
||||
.nonbrandeddocumentcolor{
|
||||
color: "#00FF00"
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 > {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 > {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 > {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> {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>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ISpSecurityStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'spSecurityStrings' {
|
||||
const strings: ISpSecurityStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('SpSecurityWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|