Skip to content

Commit ea2eee6

Browse files
committed
Initial commit of the new security trimmed control
1 parent 64c29b1 commit ea2eee6

File tree

8 files changed

+259
-0
lines changed

8 files changed

+259
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
## Usage
6+
### Checking permissions on the current site
7+
```
8+
<SecurityTrimmedControl context={this.props.context}
9+
level={PermissionLevel.currentWeb}
10+
permissions={[SPPermission.viewPages]}>
11+
{/* Specify the components to load when user has the required permissions */}
12+
</SecurityTrimmedControl>
13+
```
14+
15+
### Checking permissions on the current list
16+
```
17+
<SecurityTrimmedControl context={this.props.context}
18+
level={PermissionLevel.currentList}
19+
permissions={[SPPermission.addListItems]}>
20+
{/* Specify the components to load when user has the required permissions */}
21+
</SecurityTrimmedControl>
22+
```
23+
24+
### Checking permissions on remote site
25+
```
26+
<SecurityTrimmedControl context={this.props.context}
27+
level={PermissionLevel.remoteWeb}
28+
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
29+
permissions={[SPPermission.viewPages, SPPermission.addListItems]}>
30+
{/* Specify the components to load when user has the required permissions */}
31+
</SecurityTrimmedControl>
32+
```
33+
34+
### Checking permissions on remote list / library
35+
```
36+
<SecurityTrimmedControl context={this.props.context}
37+
level={PermissionLevel.remoteListOrLib}
38+
remoteSiteUrl="https://<tenant>.sharepoint.com/sites/<siteName>"
39+
relativeLibOrListUrl="/sites/<siteName>/<list-or-library-URL>"
40+
permissions={[SPPermission.addListItems]}>
41+
{/* Specify the components to load when user has the required permissions */}
42+
</SecurityTrimmedControl>
43+
```

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':

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+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './ISecurityTrimmedControlProps';
2+
export * from './ISecurityTrimmedControlState';
3+
export * from './SecurityTrimmedControl';
4+
export * from './PermissionLevel';

0 commit comments

Comments
 (0)