Skip to content

Commit 108ba6b

Browse files
authored
Merge pull request #75 from SharePoint/securitytrimmedcontrol
Merge of the SecurityTrimmedControl
2 parents 64c29b1 + 26d9e03 commit 108ba6b

File tree

12 files changed

+307
-1
lines changed

12 files changed

+307
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Releases
22

3+
## 1.4.0
4+
5+
**New Controls**
6+
7+
- `SecurityTrimmedControl` control got added [#74](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/74)
8+
39
## 1.3.0
410

511
**New Controls**

docs/documentation/docs/about/release-notes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Releases
22

3+
## 1.4.0
4+
5+
**New Controls**
6+
7+
- `SecurityTrimmedControl` control got added [#74](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/74)
8+
39
## 1.3.0
410

511
**New Controls**
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# SecurityTrimmedControl
2+
3+
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.
4+
5+
## How to use this control in your solutions
6+
7+
- 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.
8+
- Import the following modules to your component:
9+
10+
```TypeScript
11+
import { SecurityTrimmedControl } from "@pnp/spfx-controls-react/lib/SecurityTrimmedControl";
12+
```
13+
14+
- You can use the `SecurityTrimmedControl` as follows in your solutions:
15+
16+
**Checking permissions on the current site**
17+
18+
```jsx
19+
<SecurityTrimmedControl context={this.props.context}
20+
level={PermissionLevel.currentWeb}
21+
permissions={[SPPermission.viewPages]}>
22+
{/* Specify the components to load when user has the required permissions */}
23+
</SecurityTrimmedControl>
24+
```
25+
26+
**Checking permissions on the current list**
27+
28+
```jsx
29+
<SecurityTrimmedControl context={this.props.context}
30+
level={PermissionLevel.currentList}
31+
permissions={[SPPermission.addListItems]}>
32+
{/* Specify the components to load when user has the required permissions */}
33+
</SecurityTrimmedControl>
34+
```
35+
36+
**Checking permissions on remote site**
37+
38+
```jsx
39+
<SecurityTrimmedControl context={this.props.context}
40+
level={PermissionLevel.remoteWeb}
41+
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
42+
permissions={[SPPermission.viewPages, SPPermission.addListItems]}>
43+
{/* Specify the components to load when user has the required permissions */}
44+
</SecurityTrimmedControl>
45+
```
46+
47+
**Checking permissions on remote list / library**
48+
49+
```jsx
50+
<SecurityTrimmedControl context={this.props.context}
51+
level={PermissionLevel.remoteListOrLib}
52+
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
53+
relativeLibOrListUrl="/sites/<siteName>/<list-or-library-URL>"
54+
permissions={[SPPermission.addListItems]}>
55+
{/* Specify the components to load when user has the required permissions */}
56+
</SecurityTrimmedControl>
57+
```
58+
59+
## Implementation
60+
61+
The `SecurityTrimmedControl` can be configured with the following properties:
62+
63+
| Property | Type | Required | Description |
64+
| ---- | ---- | ---- | ---- |
65+
| context | WebPartContext or ApplicationCustomizerContext or FieldCustomizerContext or ListViewCommandSetContext | yes | Context of the web part, application customizer, field customizer, or list view command set. |
66+
| permissions | SPPermission[] | yes | The permissions to check for the user. |
67+
| level | PermissionLevel | yes | Specify where to check the user permissions: current site or list / remote site or list. |
68+
| remoteSiteUrl | string | no | The URL of the remote site. Required when you want to check permissions on remote site or list. |
69+
| relativeLibOrListUrl | string | no | The relative URL of the list or library. Required when you want to check permissions on remote list. |
70+
71+
72+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/SecurityTrimmedControl)

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pages:
99
- Placeholder: 'controls/Placeholder.md'
1010
- SiteBreadcrumb: 'controls/SiteBreadcrumb.md'
1111
- WebPartTitle: 'controls/WebPartTitle.md'
12+
- SecurityTrimmedControl: 'controls/SecurityTrimmedControl.md'
1213
- TaxonomyPicker: 'controls/TaxonomyPicker.md'
1314
- IFrameDialog: 'controls/IFrameDialog.md'
1415
- 'Field Controls':

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@pnp/spfx-controls-react",
33
"description": "Reusable React controls for SharePoint Framework solutions",
4-
"version": "1.3.0",
4+
"version": "1.4.0",
55
"engines": {
66
"node": ">=0.10.0"
77
},

src/SecurityTrimmedControl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/securityTrimmedControl';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
2+
import { FieldCustomizerContext, ListViewCommandSetContext } from '@microsoft/sp-listview-extensibility';
3+
import { WebPartContext } from '@microsoft/sp-webpart-base';
4+
import { SPPermission } from '@microsoft/sp-page-context';
5+
import { PermissionLevel } from '.';
6+
7+
export interface ISecurityTrimmedControlProps {
8+
/**
9+
* Context of the web part, application customizer, field customizer, or list view command set.
10+
*/
11+
context: WebPartContext | ApplicationCustomizerContext | FieldCustomizerContext | ListViewCommandSetContext;
12+
/**
13+
* The permissions to check for the user.
14+
*/
15+
permissions: SPPermission[];
16+
/**
17+
* Specify where to check the user permissions: current site or list / remote site or list.
18+
*/
19+
level: PermissionLevel;
20+
/**
21+
* The URL of the remote site. Required when you want to check permissions on remote site or list.
22+
*/
23+
remoteSiteUrl?: string;
24+
/**
25+
* The relative URL of the list or library. Required when you want to check permissions on remote list.
26+
*/
27+
relativeLibOrListUrl?: string;
28+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface ISecurityTrimmedControlState {
2+
allowRender: boolean;
3+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Permission level enum
3+
*/
4+
export enum PermissionLevel {
5+
/**
6+
* Checks permissions on the current web
7+
*/
8+
currentWeb = 1,
9+
/**
10+
* Checks permissions in the current loaded list
11+
*/
12+
currentList,
13+
/**
14+
* Checks permissions on the specified site URL
15+
*/
16+
remoteWeb,
17+
/**
18+
* Checks permissions on the specified list/library URL in combination with the site URL
19+
*/
20+
remoteListOrLib
21+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import * as React from 'react';
2+
import { ISecurityTrimmedControlProps, ISecurityTrimmedControlState, PermissionLevel } from '.';
3+
import { SPHttpClient } from '@microsoft/sp-http';
4+
import { SPPermission } from '@microsoft/sp-page-context';
5+
6+
export class SecurityTrimmedControl extends React.Component<ISecurityTrimmedControlProps, ISecurityTrimmedControlState> {
7+
constructor(props: ISecurityTrimmedControlProps) {
8+
super(props);
9+
10+
this.state = {
11+
allowRender: false
12+
};
13+
}
14+
15+
/**
16+
* componentDidMount lifecycle method
17+
*/
18+
public componentDidMount(): void {
19+
this.checkPermissions();
20+
}
21+
22+
/**
23+
* componentDidUpdate lifecycle method
24+
*/
25+
public componentDidUpdate(prevProps: ISecurityTrimmedControlProps, prevState: ISecurityTrimmedControlState): void {
26+
// Check permissions only if necessary
27+
if (prevProps.level !== this.props.level ||
28+
prevProps.permissions !== this.props.permissions ||
29+
prevProps.relativeLibOrListUrl !== this.props.relativeLibOrListUrl ||
30+
prevProps.remoteSiteUrl !== this.props.remoteSiteUrl) {
31+
this.checkPermissions();
32+
}
33+
}
34+
35+
/**
36+
* Check if the user has the permissions to render the element
37+
*/
38+
private checkPermissions() {
39+
const { context, level } = this.props;
40+
// Check if the permission level needs to be checked on the current site
41+
if (level === PermissionLevel.currentWeb || level === PermissionLevel.currentList) {
42+
// Get the permission scope
43+
const { permissions } = level === PermissionLevel.currentWeb ? context.pageContext.web : context.pageContext.list;
44+
// Check the user its permissions
45+
if (permissions.hasAllPermissions(...this.props.permissions)) {
46+
this.setState({
47+
allowRender: true
48+
});
49+
} else {
50+
this.setState({
51+
allowRender: false
52+
});
53+
}
54+
} else if (level === PermissionLevel.remoteWeb) {
55+
// Check permissions on remote site
56+
this.checkRemoteSitePermissions();
57+
} else if (level === PermissionLevel.remoteListOrLib) {
58+
// Check permissions on remote list/library
59+
this.checkRemoteListOrLibPermissions();
60+
}
61+
}
62+
63+
/**
64+
* Check the user its permissions on the remote site
65+
*/
66+
private async checkRemoteSitePermissions() {
67+
const { context, remoteSiteUrl, permissions } = this.props;
68+
if (remoteSiteUrl && permissions) {
69+
for (const permission of permissions) {
70+
const apiUrl = `${remoteSiteUrl}/_api/web/DoesUserHavePermissions(@v)?@v=${JSON.stringify(permission.value)}`;
71+
const result = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1).then(data => data.json());
72+
// Check if a result was retrieved
73+
if (result) {
74+
// Check if an error was retrieved
75+
if (result.error) {
76+
// Do not allow rendering when there was an error
77+
this.setState({
78+
allowRender: false
79+
});
80+
console.error(`Error retrieved while checking user's remote site permissions.`);
81+
return;
82+
}
83+
// Check the result value
84+
if (typeof result.value !== "undefined" && result.value === false) {
85+
this.setState({
86+
allowRender: false
87+
});
88+
return;
89+
}
90+
} else {
91+
this.setState({
92+
allowRender: false
93+
});
94+
console.error(`No result value was retrieved when checking the user's remote site permissions.`);
95+
return;
96+
}
97+
}
98+
99+
// Render the controls when the permissions were OK for the user
100+
this.setState({
101+
allowRender: true
102+
});
103+
}
104+
}
105+
106+
/**
107+
* Check the user its permissions on the remote list or library
108+
*/
109+
private async checkRemoteListOrLibPermissions() {
110+
const { context, remoteSiteUrl, relativeLibOrListUrl, permissions } = this.props;
111+
// Check if all properties are provided
112+
if (remoteSiteUrl && relativeLibOrListUrl && permissions) {
113+
const apiUrl = `${remoteSiteUrl}/_api/web/GetList(@listUrl)/EffectiveBasePermissions?@listUrl='${encodeURIComponent(relativeLibOrListUrl)}'`;
114+
const result = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1).then(data => data.json());
115+
// Check if a result was retrieved
116+
if (result) {
117+
// Check if an error was retrieved
118+
if (result.error) {
119+
// Do not allow rendering when there was an error
120+
this.setState({
121+
allowRender: false
122+
});
123+
console.error(`Error retrieved while checking user's remote list or library permissions.`);
124+
return;
125+
}
126+
127+
// Check the result high and low value are returned
128+
if (typeof result.High !== "undefined" && typeof result.Low !== "undefined") {
129+
// Create the permission mask
130+
const permission = new SPPermission(result);
131+
const hasPermissions = permission.hasAllPermissions(...permissions);
132+
133+
this.setState({
134+
allowRender: hasPermissions
135+
});
136+
return;
137+
}
138+
} else {
139+
this.setState({
140+
allowRender: false
141+
});
142+
console.error(`No result value was retrieved when checking the user's remote list or library permissions.`);
143+
return;
144+
}
145+
}
146+
}
147+
148+
/**
149+
* Default React render method
150+
*/
151+
public render(): React.ReactElement<ISecurityTrimmedControlProps> {
152+
return (
153+
this.state.allowRender ? (
154+
<div>{this.props.children}</div>
155+
) : null
156+
);
157+
}
158+
}

0 commit comments

Comments
 (0)