Skip to content

Commit 92df7c1

Browse files
nanddeepnJwaegebaert
authored andcommitted
Adds 'spo folder roleinheritance reset' command. Closes #3603
1 parent bc3200c commit 92df7c1

5 files changed

Lines changed: 328 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# spo folder roleinheritance reset
2+
3+
Restores the role inheritance of a folder.
4+
5+
## Usage
6+
7+
```sh
8+
m365 spo folder roleinheritance reset [options]
9+
```
10+
11+
## Options
12+
13+
`-u, --webUrl <webUrl>`
14+
: URL of the site where the folder is located.
15+
16+
`-f, --folderUrl <folderUrl>`
17+
: The site-relative URL of the folder.
18+
19+
`--confirm`
20+
: Don't prompt for confirmation to reset role inheritance of the folder.
21+
22+
--8<-- "docs/cmd/_global.md"
23+
24+
## Examples
25+
26+
Reset inheritance of folder with site-relative url _Shared Documents/TestFolder_ located in site _https://contoso.sharepoint.com/sites/project-x_.
27+
28+
```sh
29+
m365 spo folder roleinheritance reset --webUrl "https://contoso.sharepoint.com/sites/project-x" --folderUrl "Shared Documents/TestFolder"
30+
```
31+
32+
Reset inheritance of folder with site-relative url _Shared Documents/TestFolder_ located in site _https://contoso.sharepoint.com/sites/project-x_. It will **not** prompt for confirmation before resetting.
33+
34+
```sh
35+
m365 spo folder roleinheritance reset --webUrl "https://contoso.sharepoint.com/sites/project-x" --folderUrl "Shared Documents/TestFolder" --confirm
36+
```

docs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ nav:
362362
- folder move: 'cmd/spo/folder/folder-move.md'
363363
- folder remove: 'cmd/spo/folder/folder-remove.md'
364364
- folder rename: 'cmd/spo/folder/folder-rename.md'
365+
- folder roleinheritance reset: 'cmd/spo/folder/folder-roleinheritance-reset.md'
365366
- group:
366367
- group add: 'cmd/spo/group/group-add.md'
367368
- group get: 'cmd/spo/group/group-get.md'

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export default {
6464
FOLDER_MOVE: `${prefix} folder move`,
6565
FOLDER_REMOVE: `${prefix} folder remove`,
6666
FOLDER_RENAME: `${prefix} folder rename`,
67+
FOLDER_ROLEINHERITANCE_RESET: `${prefix} folder roleinheritance reset`,
6768
GET: `${prefix} get`,
6869
GROUP_ADD: `${prefix} group add`,
6970
GROUP_GET: `${prefix} group get`,
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import * as assert from 'assert';
2+
import * as sinon from 'sinon';
3+
import appInsights from '../../../../appInsights';
4+
import auth from '../../../../Auth';
5+
import { Cli } from '../../../../cli/Cli';
6+
import { CommandInfo } from '../../../../cli/CommandInfo';
7+
import { Logger } from '../../../../cli/Logger';
8+
import Command, { CommandError } from '../../../../Command';
9+
import request from '../../../../request';
10+
import { sinonUtil } from '../../../../utils/sinonUtil';
11+
import commands from '../../commands';
12+
const command: Command = require('./folder-roleinheritance-reset');
13+
14+
describe(commands.FOLDER_ROLEINHERITANCE_RESET, () => {
15+
const webUrl = 'https://contoso.sharepoint.com/sites/project-x';
16+
const folderUrl = 'Shared Documents/TestFolder';
17+
18+
let log: any[];
19+
let logger: Logger;
20+
let commandInfo: CommandInfo;
21+
let promptOptions: any;
22+
23+
before(() => {
24+
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
25+
sinon.stub(appInsights, 'trackEvent').callsFake(() => { });
26+
auth.service.connected = true;
27+
commandInfo = Cli.getCommandInfo(command);
28+
});
29+
30+
beforeEach(() => {
31+
log = [];
32+
logger = {
33+
log: (msg: string) => {
34+
log.push(msg);
35+
},
36+
logRaw: (msg: string) => {
37+
log.push(msg);
38+
},
39+
logToStderr: (msg: string) => {
40+
log.push(msg);
41+
}
42+
};
43+
sinon.stub(Cli, 'prompt').callsFake(async (options: any) => {
44+
promptOptions = options;
45+
return { continue: false };
46+
});
47+
promptOptions = undefined;
48+
});
49+
50+
afterEach(() => {
51+
sinonUtil.restore([
52+
Cli.prompt,
53+
request.post
54+
]);
55+
});
56+
57+
after(() => {
58+
sinonUtil.restore([
59+
auth.restoreAuth,
60+
appInsights.trackEvent
61+
]);
62+
auth.service.connected = false;
63+
});
64+
65+
it('has correct name', () => {
66+
assert.strictEqual(command.name.startsWith(commands.FOLDER_ROLEINHERITANCE_RESET), true);
67+
});
68+
69+
it('has a description', () => {
70+
assert.notStrictEqual(command.description, null);
71+
});
72+
73+
it('supports specifying URL', () => {
74+
const options = command.options;
75+
let containsTypeOption = false;
76+
options.forEach(o => {
77+
if (o.option.indexOf('<webUrl>') > -1) {
78+
containsTypeOption = true;
79+
}
80+
});
81+
assert(containsTypeOption);
82+
});
83+
84+
it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => {
85+
const actual = await command.validate({ options: { webUrl: 'foo', folderUrl: folderUrl, confirm: true } }, commandInfo);
86+
assert.notStrictEqual(actual, true);
87+
});
88+
89+
it('passes validation if webUrl and folderUrl are valid', async () => {
90+
const actual = await command.validate({ options: { webUrl: webUrl, folderUrl: folderUrl, confirm: true } }, commandInfo);
91+
assert.strictEqual(actual, true);
92+
});
93+
94+
it('prompts before resetting role inheritance for the folder when confirm option not passed', async () => {
95+
await command.action(logger, {
96+
options: {
97+
webUrl: webUrl,
98+
folderUrl: folderUrl
99+
}
100+
});
101+
102+
let promptIssued = false;
103+
104+
if (promptOptions && promptOptions.type === 'confirm') {
105+
promptIssued = true;
106+
}
107+
108+
assert(promptIssued);
109+
});
110+
111+
it('aborts resetting role inheritance for the folder when confirm option is not passed and prompt not confirmed', async () => {
112+
const postSpy = sinon.spy(request, 'post');
113+
114+
await command.action(logger, {
115+
options: {
116+
webUrl: webUrl,
117+
folderUrl: folderUrl
118+
}
119+
});
120+
121+
assert(postSpy.notCalled);
122+
});
123+
124+
it('reset role inheritance on folder by site-relative URL (debug)', async () => {
125+
sinon.stub(request, 'post').callsFake(async (opts) => {
126+
if (opts.url === `${webUrl}/_api/web/GetFolderByServerRelativeUrl('${folderUrl}')/ListItemAllFields/resetroleinheritance`) {
127+
return;
128+
}
129+
130+
throw 'Invalid request';
131+
});
132+
133+
await command.action(logger, {
134+
options: {
135+
debug: true,
136+
webUrl: webUrl,
137+
folderUrl: folderUrl,
138+
confirm: true
139+
}
140+
});
141+
});
142+
143+
it('reset role inheritance on folder by site-relative URL when prompt confirmed', async () => {
144+
sinon.stub(request, 'post').callsFake(async (opts) => {
145+
if (opts.url === `${webUrl}/_api/web/GetFolderByServerRelativeUrl('${folderUrl}')/ListItemAllFields/resetroleinheritance`) {
146+
return;
147+
}
148+
149+
throw 'Invalid request';
150+
});
151+
152+
sinonUtil.restore(Cli.prompt);
153+
sinon.stub(Cli, 'prompt').callsFake(async () => (
154+
{ continue: true }
155+
));
156+
157+
await command.action(logger, {
158+
options: {
159+
webUrl: webUrl,
160+
folderUrl: folderUrl
161+
}
162+
});
163+
});
164+
165+
it('correctly handles error when resetting folder role inheritance', async () => {
166+
const errorMessage = 'request rejected';
167+
sinon.stub(request, 'post').callsFake(async () => { throw errorMessage; });
168+
169+
await assert.rejects(command.action(logger, {
170+
options: {
171+
debug: true,
172+
webUrl: webUrl,
173+
folderUrl: folderUrl,
174+
confirm: true
175+
}
176+
}), new CommandError(errorMessage));
177+
});
178+
179+
it('supports debug mode', () => {
180+
const options = command.options;
181+
let containsDebugOption = false;
182+
options.forEach(o => {
183+
if (o.option === '--debug') {
184+
containsDebugOption = true;
185+
}
186+
});
187+
assert(containsDebugOption);
188+
});
189+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Cli } from '../../../../cli/Cli';
2+
import { Logger } from '../../../../cli/Logger';
3+
import { AxiosRequestConfig } from 'axios';
4+
import GlobalOptions from '../../../../GlobalOptions';
5+
import request from '../../../../request';
6+
import { validation } from '../../../../utils/validation';
7+
import SpoCommand from '../../../base/SpoCommand';
8+
import commands from '../../commands';
9+
10+
interface CommandArgs {
11+
options: Options;
12+
}
13+
14+
interface Options extends GlobalOptions {
15+
webUrl: string;
16+
folderUrl: string;
17+
confirm?: boolean;
18+
}
19+
20+
class SpoFolderRoleInheritanceResetCommand extends SpoCommand {
21+
public get name(): string {
22+
return commands.FOLDER_ROLEINHERITANCE_RESET;
23+
}
24+
25+
public get description(): string {
26+
return 'Restores the role inheritance of a folder';
27+
}
28+
29+
constructor() {
30+
super();
31+
32+
this.#initTelemetry();
33+
this.#initOptions();
34+
this.#initValidators();
35+
}
36+
37+
#initTelemetry(): void {
38+
this.telemetry.push((args: CommandArgs) => {
39+
Object.assign(this.telemetryProperties, {
40+
confirm: !!args.options.confirm
41+
});
42+
});
43+
}
44+
45+
#initOptions(): void {
46+
this.options.unshift(
47+
{
48+
option: '-u, --webUrl <webUrl>'
49+
},
50+
{
51+
option: '-f, --folderUrl <folderUrl>'
52+
},
53+
{
54+
option: '--confirm'
55+
}
56+
);
57+
}
58+
59+
#initValidators(): void {
60+
this.validators.push(
61+
async (args: CommandArgs) => validation.isValidSharePointUrl(args.options.webUrl)
62+
);
63+
}
64+
65+
public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
66+
const resetFolderRoleInheritance: () => Promise<void> = async (): Promise<void> => {
67+
try {
68+
const requestOptions: AxiosRequestConfig = {
69+
url: `${args.options.webUrl}/_api/web/GetFolderByServerRelativeUrl('${args.options.folderUrl}')/ListItemAllFields/resetroleinheritance`,
70+
headers: {
71+
accept: 'application/json;odata.metadata=none'
72+
},
73+
responseType: 'json'
74+
};
75+
76+
await request.post(requestOptions);
77+
}
78+
catch (err: any) {
79+
this.handleRejectedODataJsonPromise(err);
80+
}
81+
};
82+
83+
if (args.options.confirm) {
84+
await resetFolderRoleInheritance();
85+
}
86+
else {
87+
const result = await Cli.prompt<{ continue: boolean }>({
88+
type: 'confirm',
89+
name: 'continue',
90+
default: false,
91+
message: `Are you sure you want to reset the role inheritance of folder ${args.options.folderUrl} located in site ${args.options.webUrl}?`
92+
});
93+
94+
if (result.continue) {
95+
await resetFolderRoleInheritance();
96+
}
97+
}
98+
}
99+
}
100+
101+
module.exports = new SpoFolderRoleInheritanceResetCommand();

0 commit comments

Comments
 (0)