Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge of the SecurityTrimmedControl #75

Merged
merged 2 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Releases

## 1.4.0

**New Controls**

- `SecurityTrimmedControl` control got added [#74](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/74)

## 1.3.0

**New Controls**
Expand Down
6 changes: 6 additions & 0 deletions docs/documentation/docs/about/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Releases

## 1.4.0

**New Controls**

- `SecurityTrimmedControl` control got added [#74](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/74)

## 1.3.0

**New Controls**
Expand Down
72 changes: 72 additions & 0 deletions docs/documentation/docs/controls/SecurityTrimmedControl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# SecurityTrimmedControl

This control is intended to be used when you want to show or hide components based on the user its permissions. The control can be used to check the user’s permissions on the current site / list were the solution is loaded, or on a remote site / list.

## How to use this control in your solutions

- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../#getting-started) page for more information about installing the dependency.
- Import the following modules to your component:

```TypeScript
import { SecurityTrimmedControl } from "@pnp/spfx-controls-react/lib/SecurityTrimmedControl";
```

- You can use the `SecurityTrimmedControl` as follows in your solutions:

**Checking permissions on the current site**

```jsx
<SecurityTrimmedControl context={this.props.context}
level={PermissionLevel.currentWeb}
permissions={[SPPermission.viewPages]}>
{/* Specify the components to load when user has the required permissions */}
</SecurityTrimmedControl>
```

**Checking permissions on the current list**

```jsx
<SecurityTrimmedControl context={this.props.context}
level={PermissionLevel.currentList}
permissions={[SPPermission.addListItems]}>
{/* Specify the components to load when user has the required permissions */}
</SecurityTrimmedControl>
```

**Checking permissions on remote site**

```jsx
<SecurityTrimmedControl context={this.props.context}
level={PermissionLevel.remoteWeb}
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
permissions={[SPPermission.viewPages, SPPermission.addListItems]}>
{/* Specify the components to load when user has the required permissions */}
</SecurityTrimmedControl>
```

**Checking permissions on remote list / library**

```jsx
<SecurityTrimmedControl context={this.props.context}
level={PermissionLevel.remoteListOrLib}
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
relativeLibOrListUrl="/sites/<siteName>/<list-or-library-URL>"
permissions={[SPPermission.addListItems]}>
{/* Specify the components to load when user has the required permissions */}
</SecurityTrimmedControl>
```

## Implementation

The `SecurityTrimmedControl` can be configured with the following properties:

| Property | Type | Required | Description |
| ---- | ---- | ---- | ---- |
| context | WebPartContext or ApplicationCustomizerContext or FieldCustomizerContext or ListViewCommandSetContext | yes | Context of the web part, application customizer, field customizer, or list view command set. |
| permissions | SPPermission[] | yes | The permissions to check for the user. |
| level | PermissionLevel | yes | Specify where to check the user permissions: current site or list / remote site or list. |
| remoteSiteUrl | string | no | The URL of the remote site. Required when you want to check permissions on remote site or list. |
| relativeLibOrListUrl | string | no | The relative URL of the list or library. Required when you want to check permissions on remote list. |


![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/SecurityTrimmedControl)
1 change: 1 addition & 0 deletions docs/documentation/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pages:
- Placeholder: 'controls/Placeholder.md'
- SiteBreadcrumb: 'controls/SiteBreadcrumb.md'
- WebPartTitle: 'controls/WebPartTitle.md'
- SecurityTrimmedControl: 'controls/SecurityTrimmedControl.md'
- TaxonomyPicker: 'controls/TaxonomyPicker.md'
- IFrameDialog: 'controls/IFrameDialog.md'
- 'Field Controls':
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@pnp/spfx-controls-react",
"description": "Reusable React controls for SharePoint Framework solutions",
"version": "1.3.0",
"version": "1.4.0",
"engines": {
"node": ">=0.10.0"
},
Expand Down
1 change: 1 addition & 0 deletions src/SecurityTrimmedControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './controls/securityTrimmedControl';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
import { FieldCustomizerContext, ListViewCommandSetContext } from '@microsoft/sp-listview-extensibility';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { SPPermission } from '@microsoft/sp-page-context';
import { PermissionLevel } from '.';

export interface ISecurityTrimmedControlProps {
/**
* Context of the web part, application customizer, field customizer, or list view command set.
*/
context: WebPartContext | ApplicationCustomizerContext | FieldCustomizerContext | ListViewCommandSetContext;
/**
* The permissions to check for the user.
*/
permissions: SPPermission[];
/**
* Specify where to check the user permissions: current site or list / remote site or list.
*/
level: PermissionLevel;
/**
* The URL of the remote site. Required when you want to check permissions on remote site or list.
*/
remoteSiteUrl?: string;
/**
* The relative URL of the list or library. Required when you want to check permissions on remote list.
*/
relativeLibOrListUrl?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ISecurityTrimmedControlState {
allowRender: boolean;
}
21 changes: 21 additions & 0 deletions src/controls/securityTrimmedControl/PermissionLevel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Permission level enum
*/
export enum PermissionLevel {
/**
* Checks permissions on the current web
*/
currentWeb = 1,
/**
* Checks permissions in the current loaded list
*/
currentList,
/**
* Checks permissions on the specified site URL
*/
remoteWeb,
/**
* Checks permissions on the specified list/library URL in combination with the site URL
*/
remoteListOrLib
}
158 changes: 158 additions & 0 deletions src/controls/securityTrimmedControl/SecurityTrimmedControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import * as React from 'react';
import { ISecurityTrimmedControlProps, ISecurityTrimmedControlState, PermissionLevel } from '.';
import { SPHttpClient } from '@microsoft/sp-http';
import { SPPermission } from '@microsoft/sp-page-context';

export class SecurityTrimmedControl extends React.Component<ISecurityTrimmedControlProps, ISecurityTrimmedControlState> {
constructor(props: ISecurityTrimmedControlProps) {
super(props);

this.state = {
allowRender: false
};
}

/**
* componentDidMount lifecycle method
*/
public componentDidMount(): void {
this.checkPermissions();
}

/**
* componentDidUpdate lifecycle method
*/
public componentDidUpdate(prevProps: ISecurityTrimmedControlProps, prevState: ISecurityTrimmedControlState): void {
// Check permissions only if necessary
if (prevProps.level !== this.props.level ||
prevProps.permissions !== this.props.permissions ||
prevProps.relativeLibOrListUrl !== this.props.relativeLibOrListUrl ||
prevProps.remoteSiteUrl !== this.props.remoteSiteUrl) {
this.checkPermissions();
}
}

/**
* Check if the user has the permissions to render the element
*/
private checkPermissions() {
const { context, level } = this.props;
// Check if the permission level needs to be checked on the current site
if (level === PermissionLevel.currentWeb || level === PermissionLevel.currentList) {
// Get the permission scope
const { permissions } = level === PermissionLevel.currentWeb ? context.pageContext.web : context.pageContext.list;
// Check the user its permissions
if (permissions.hasAllPermissions(...this.props.permissions)) {
this.setState({
allowRender: true
});
} else {
this.setState({
allowRender: false
});
}
} else if (level === PermissionLevel.remoteWeb) {
// Check permissions on remote site
this.checkRemoteSitePermissions();
} else if (level === PermissionLevel.remoteListOrLib) {
// Check permissions on remote list/library
this.checkRemoteListOrLibPermissions();
}
}

/**
* Check the user its permissions on the remote site
*/
private async checkRemoteSitePermissions() {
const { context, remoteSiteUrl, permissions } = this.props;
if (remoteSiteUrl && permissions) {
for (const permission of permissions) {
const apiUrl = `${remoteSiteUrl}/_api/web/DoesUserHavePermissions(@v)?@v=${JSON.stringify(permission.value)}`;
const result = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1).then(data => data.json());
// Check if a result was retrieved
if (result) {
// Check if an error was retrieved
if (result.error) {
// Do not allow rendering when there was an error
this.setState({
allowRender: false
});
console.error(`Error retrieved while checking user's remote site permissions.`);
return;
}
// Check the result value
if (typeof result.value !== "undefined" && result.value === false) {
this.setState({
allowRender: false
});
return;
}
} else {
this.setState({
allowRender: false
});
console.error(`No result value was retrieved when checking the user's remote site permissions.`);
return;
}
}

// Render the controls when the permissions were OK for the user
this.setState({
allowRender: true
});
}
}

/**
* Check the user its permissions on the remote list or library
*/
private async checkRemoteListOrLibPermissions() {
const { context, remoteSiteUrl, relativeLibOrListUrl, permissions } = this.props;
// Check if all properties are provided
if (remoteSiteUrl && relativeLibOrListUrl && permissions) {
const apiUrl = `${remoteSiteUrl}/_api/web/GetList(@listUrl)/EffectiveBasePermissions?@listUrl='${encodeURIComponent(relativeLibOrListUrl)}'`;
const result = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1).then(data => data.json());
// Check if a result was retrieved
if (result) {
// Check if an error was retrieved
if (result.error) {
// Do not allow rendering when there was an error
this.setState({
allowRender: false
});
console.error(`Error retrieved while checking user's remote list or library permissions.`);
return;
}

// Check the result high and low value are returned
if (typeof result.High !== "undefined" && typeof result.Low !== "undefined") {
// Create the permission mask
const permission = new SPPermission(result);
const hasPermissions = permission.hasAllPermissions(...permissions);

this.setState({
allowRender: hasPermissions
});
return;
}
} else {
this.setState({
allowRender: false
});
console.error(`No result value was retrieved when checking the user's remote list or library permissions.`);
return;
}
}
}

/**
* Default React render method
*/
public render(): React.ReactElement<ISecurityTrimmedControlProps> {
return (
this.state.allowRender ? (
<div>{this.props.children}</div>
) : null
);
}
}
4 changes: 4 additions & 0 deletions src/controls/securityTrimmedControl/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './ISecurityTrimmedControlProps';
export * from './ISecurityTrimmedControlState';
export * from './SecurityTrimmedControl';
export * from './PermissionLevel';
6 changes: 6 additions & 0 deletions src/webparts/controlsTest/components/ControlsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { TaxonomyPicker, IPickerTerms } from '../../../TaxonomyPicker';
import { ListPicker } from '../../../ListPicker';
import { IFrameDialog } from '../../../IFrameDialog';
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { SecurityTrimmedControl, PermissionLevel } from '../../../SecurityTrimmedControl';
import { SPPermission } from '@microsoft/sp-page-context';

/**
* Component that can be used to test out the React controls from this project
Expand Down Expand Up @@ -177,6 +179,10 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
<div className="ms-Grid-col ms-lg10 ms-xl8 ms-xlPush2 ms-lgPush1">
<span className="ms-font-xl">Controls testing</span>

<SecurityTrimmedControl context={this.props.context} level={PermissionLevel.currentWeb} permissions={[SPPermission.viewListItems]}>
<p>You have permissions to view list items.</p>
</SecurityTrimmedControl>

<p className="ms-font-l">
File type icon control
</p>
Expand Down