From b179e327d3d9451ec02e3153609b5c17ecd85144 Mon Sep 17 00:00:00 2001 From: Mathijs Verbeeck Date: Tue, 7 Apr 2026 10:48:17 +0200 Subject: [PATCH] Adds command spo list sensitivitylabel remove --- .devproxy/api-specs/sharepoint.yaml | 84 +++++++ .../spo/list/list-sensitivitylabel-remove.mdx | 77 ++++++ docs/src/config/sidebars.ts | 5 + src/m365/spo/commands.ts | 1 + .../list/list-sensitivitylabel-remove.spec.ts | 225 ++++++++++++++++++ .../list/list-sensitivitylabel-remove.ts | 101 ++++++++ 6 files changed, 493 insertions(+) create mode 100644 docs/docs/cmd/spo/list/list-sensitivitylabel-remove.mdx create mode 100644 src/m365/spo/commands/list/list-sensitivitylabel-remove.spec.ts create mode 100644 src/m365/spo/commands/list/list-sensitivitylabel-remove.ts diff --git a/.devproxy/api-specs/sharepoint.yaml b/.devproxy/api-specs/sharepoint.yaml index 45cde8ac3f7..362fa8c381e 100644 --- a/.devproxy/api-specs/sharepoint.yaml +++ b/.devproxy/api-specs/sharepoint.yaml @@ -341,6 +341,90 @@ paths: responses: 200: description: OK + /_api/web/lists(guid'{listId}'): + patch: + parameters: + - name: listId + in: path + required: true + description: list GUID + schema: + type: string + example: "b4cfa0d9-b3d7-49ae-a0f0-f14ffdd005f7" + requestBody: + required: true + content: + application/json: + example: + DefaultSensitivityLabelForLibrary: "" + security: + - delegated: + - AllSites.Write + - AllSites.Manage + - AllSites.FullControl + - application: + - Sites.ReadWrite.All + - Sites.Manage.All + - Sites.FullControl.All + responses: + 204: + description: No Content + /_api/web/lists/getByTitle('{listTitle}'): + patch: + parameters: + - name: listTitle + in: path + required: true + description: list title + schema: + type: string + example: "Shared Documents" + requestBody: + required: true + content: + application/json: + example: + DefaultSensitivityLabelForLibrary: "" + security: + - delegated: + - AllSites.Write + - AllSites.Manage + - AllSites.FullControl + - application: + - Sites.ReadWrite.All + - Sites.Manage.All + - Sites.FullControl.All + responses: + 204: + description: No Content + /_api/web/GetList('{listServerRelativeUrl}'): + patch: + parameters: + - name: listServerRelativeUrl + in: path + required: true + description: Server-relative URL of the list + schema: + type: string + example: "/Shared Documents" + requestBody: + required: true + content: + application/json: + example: + DefaultSensitivityLabelForLibrary: "" + security: + - delegated: + - AllSites.Write + - AllSites.Manage + - AllSites.FullControl + - application: + - Sites.ReadWrite.All + - Sites.Manage.All + - Sites.FullControl.All + responses: + 204: + description: No Content /_api/web/webs: get: security: diff --git a/docs/docs/cmd/spo/list/list-sensitivitylabel-remove.mdx b/docs/docs/cmd/spo/list/list-sensitivitylabel-remove.mdx new file mode 100644 index 00000000000..59df8a2344d --- /dev/null +++ b/docs/docs/cmd/spo/list/list-sensitivitylabel-remove.mdx @@ -0,0 +1,77 @@ +import Global from '../../_global.mdx'; +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; + +# spo list sensitivitylabel remove + +Clears a default sensitivity label from a document library + +## Usage + +```sh +m365 spo list sensitivitylabel remove [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: The URL of the site where the list is located. + +`-t, --listTitle [listTitle]` +: The title of the library on which to remove the label. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`-l, --listId [listId]` +: The ID of the library on which to remove the label. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`--listUrl [listUrl]` +: Server- or web-relative URL of the library on which to remove the label. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`-f, --force` +: Don't prompt for confirmation. +``` + + + +## Permissions + + + + + | Resource | Permissions | + |------------|----------------| + | SharePoint | AllSites.Write | + + + + + | Resource | Permissions | + |------------|--------------------| + | SharePoint | Sites.ReadWrite.All | + + + + +## Examples + +Removes a sensitivity label from a document library based on the list title. + +```sh +m365 spo list sensitivitylabel remove --webUrl 'https://contoso.sharepoint.com' --listTitle 'Shared Documents' +``` + +Removes a sensitivity label from a document library based on the list url. + +```sh +m365 spo list sensitivitylabel remove --webUrl 'https://contoso.sharepoint.com' --listUrl '/Shared Documents' +``` + +Removes a sensitivity label from a document library based on the list id without prompting for confirmation. + +```sh +m365 spo list sensitivitylabel remove --webUrl 'https://contoso.sharepoint.com' --listId 'b4cfa0d9-b3d7-49ae-a0f0-f14ffdd005f7' --force +``` + +## Response + +The command won't return a response on success. diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index e0f837d676a..f358230ca0b 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -3199,6 +3199,11 @@ const sidebars: SidebarsConfig = { label: 'list sensitivitylabel ensure', id: 'cmd/spo/list/list-sensitivitylabel-ensure' }, + { + type: 'doc', + label: 'list sensitivitylabel remove', + id: 'cmd/spo/list/list-sensitivitylabel-remove' + }, { type: 'doc', label: 'list sitescript get', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index 238ce27cb9e..d0f5780e7ca 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -159,6 +159,7 @@ export default { LIST_ROLEINHERITANCE_BREAK: `${prefix} list roleinheritance break`, LIST_ROLEINHERITANCE_RESET: `${prefix} list roleinheritance reset`, LIST_SENSITIVITYLABEL_ENSURE: `${prefix} list sensitivitylabel ensure`, + LIST_SENSITIVITYLABEL_REMOVE: `${prefix} list sensitivitylabel remove`, LIST_SET: `${prefix} list set`, LIST_SITESCRIPT_GET: `${prefix} list sitescript get`, LIST_VIEW_ADD: `${prefix} list view add`, diff --git a/src/m365/spo/commands/list/list-sensitivitylabel-remove.spec.ts b/src/m365/spo/commands/list/list-sensitivitylabel-remove.spec.ts new file mode 100644 index 00000000000..4f1a41e23ff --- /dev/null +++ b/src/m365/spo/commands/list/list-sensitivitylabel-remove.spec.ts @@ -0,0 +1,225 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import commands from '../../commands.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import { cli } from '../../../../cli/cli.js'; +import command, { options } from './list-sensitivitylabel-remove.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; + +describe(commands.LIST_SENSITIVITYLABEL_REMOVE, () => { + const webUrl = 'https://contoso.sharepoint.com'; + const listTitle = 'Shared Documents'; + const listId = 'b4cfa0d9-b3d7-49ae-a0f0-f14ffdd005f7'; + const listUrl = '/Shared Documents'; + + let log: string[]; + let logger: Logger; + let promptIssued: boolean; + let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').resolves(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + sinon.stub(cli, 'promptForConfirmation').callsFake(async () => { + promptIssued = true; + return false; + }); + promptIssued = false; + }); + + afterEach(() => { + sinonUtil.restore([ + request.patch, + cli.promptForConfirmation + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.LIST_SENSITIVITYLABEL_REMOVE); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('passes validation when listTitle is specified', () => { + const actual = commandOptionsSchema.safeParse({ webUrl: webUrl, listTitle: listTitle }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when listId is a valid GUID', () => { + const actual = commandOptionsSchema.safeParse({ webUrl: webUrl, listId: listId }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when listUrl is specified', () => { + const actual = commandOptionsSchema.safeParse({ webUrl: webUrl, listUrl: listUrl }); + assert.strictEqual(actual.success, true); + }); + + it('fails validation if webUrl is not a valid SharePoint site URL', () => { + const actual = commandOptionsSchema.safeParse({ webUrl: 'foo', listTitle: listTitle }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if listId is not a valid GUID', () => { + const actual = commandOptionsSchema.safeParse({ webUrl: webUrl, listId: 'invalid' }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if listId, listUrl and listTitle options are not passed', () => { + const schema = commandInfo.command.getRefinedSchema(commandOptionsSchema); + const actual = schema!.safeParse({ webUrl: webUrl }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if multiple list options are specified', () => { + const schema = commandInfo.command.getRefinedSchema(commandOptionsSchema); + const actual = schema!.safeParse({ webUrl: webUrl, listTitle: listTitle, listId: listId }); + assert.notStrictEqual(actual.success, true); + }); + + it('prompts before removing the sensitivity label when force option not passed', async () => { + await command.action(logger, { options: { webUrl: webUrl, listTitle: listTitle } }); + + assert(promptIssued); + }); + + it('prompts before removing the sensitivity label when using listUrl and force option not passed', async () => { + await command.action(logger, { options: { webUrl: webUrl, listUrl: listUrl } }); + + assert(promptIssued); + }); + + it('aborts removing sensitivity label when prompt not confirmed', async () => { + const patchSpy = sinon.stub(request, 'patch').resolves(); + + await command.action(logger, { options: { webUrl: webUrl, listTitle: listTitle } }); + assert(patchSpy.notCalled); + }); + + it('removes sensitivity label from document library using listTitle without prompting for confirmation', async () => { + const patchStub = sinon.stub(request, 'patch').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/_api/web/lists/getByTitle('Shared%20Documents')`) { + return; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: webUrl, listTitle: listTitle, force: true, verbose: true } }); + + const lastCall = patchStub.lastCall.args[0]; + assert.strictEqual(lastCall.data.DefaultSensitivityLabelForLibrary, ''); + }); + + it('removes sensitivity label from document library using listId without prompting for confirmation', async () => { + const patchStub = sinon.stub(request, 'patch').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/_api/web/lists(guid'b4cfa0d9-b3d7-49ae-a0f0-f14ffdd005f7')`) { + return; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: webUrl, listId: listId, force: true, verbose: true } }); + + const lastCall = patchStub.lastCall.args[0]; + assert.strictEqual(lastCall.data.DefaultSensitivityLabelForLibrary, ''); + }); + + it('removes sensitivity label from document library using listUrl without prompting for confirmation', async () => { + const patchStub = sinon.stub(request, 'patch').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/_api/web/GetList('%2FShared%20Documents')`) { + return; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: webUrl, listUrl: listUrl, force: true, verbose: true } }); + + const lastCall = patchStub.lastCall.args[0]; + assert.strictEqual(lastCall.data.DefaultSensitivityLabelForLibrary, ''); + }); + + it('removes sensitivity label when prompt is confirmed', async () => { + sinonUtil.restore(cli.promptForConfirmation); + sinon.stub(cli, 'promptForConfirmation').resolves(true); + + const patchStub = sinon.stub(request, 'patch').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/_api/web/lists/getByTitle('Shared%20Documents')`) { + return; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: webUrl, listTitle: listTitle } }); + + const lastCall = patchStub.lastCall.args[0]; + assert.strictEqual(lastCall.data.DefaultSensitivityLabelForLibrary, ''); + }); + + it('correctly handles API error', async () => { + const error = { + error: { + 'odata.error': { + code: '-1, Microsoft.SharePoint.Client.InvalidOperationException', + message: { + value: '404 - File not found' + } + } + } + }; + + sinon.stub(request, 'patch').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/_api/web/lists/getByTitle('Shared%20Documents')`) { + throw error; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { + options: { + webUrl: webUrl, + listTitle: listTitle, + force: true + } + }), new CommandError(error.error['odata.error'].message.value)); + }); +}); diff --git a/src/m365/spo/commands/list/list-sensitivitylabel-remove.ts b/src/m365/spo/commands/list/list-sensitivitylabel-remove.ts new file mode 100644 index 00000000000..fa5579156a5 --- /dev/null +++ b/src/m365/spo/commands/list/list-sensitivitylabel-remove.ts @@ -0,0 +1,101 @@ +import { z } from 'zod'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { validation } from '../../../../utils/validation.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import commands from '../../commands.js'; +import { cli } from '../../../../cli/cli.js'; + +export const options = z.strictObject({ + ...globalOptionsZod.shape, + webUrl: z.string() + .refine(url => validation.isValidSharePointUrl(url) === true, { + error: 'Specify a valid SharePoint site URL.' + }) + .alias('u'), + listTitle: z.string().optional().alias('t'), + listId: z.uuid().optional().alias('l'), + listUrl: z.string().optional(), + force: z.boolean().optional().alias('f') +}); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class SpoListSensitivityLabelRemoveCommand extends SpoCommand { + public get name(): string { + return commands.LIST_SENSITIVITYLABEL_REMOVE; + } + + public get description(): string { + return 'Clears a default sensitivity label from a document library'; + } + + public get schema(): z.ZodTypeAny { + return options; + } + + public getRefinedSchema(schema: typeof options): z.ZodObject | undefined { + return schema + .refine(options => [options.listId, options.listTitle, options.listUrl].filter(o => o !== undefined).length === 1, { + error: 'Use one of the following options: listId, listTitle, or listUrl.' + }); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const removeSensitivityLabel = async (): Promise => { + try { + if (this.verbose) { + await logger.logToStderr(`Removing the sensitivity label from list '${args.options.listId || args.options.listTitle || args.options.listUrl}' in site at ${args.options.webUrl}...`); + } + + let requestUrl: string = `${args.options.webUrl}/_api/web`; + + if (args.options.listId) { + requestUrl += `/lists(guid'${formatting.encodeQueryParameter(args.options.listId)}')`; + } + else if (args.options.listTitle) { + requestUrl += `/lists/getByTitle('${formatting.encodeQueryParameter(args.options.listTitle)}')`; + } + else if (args.options.listUrl) { + const listServerRelativeUrl: string = urlUtil.getServerRelativePath(args.options.webUrl, args.options.listUrl); + requestUrl += `/GetList('${formatting.encodeQueryParameter(listServerRelativeUrl)}')`; + } + + const requestOptions: CliRequestOptions = { + url: requestUrl, + headers: { + accept: 'application/json;odata=nometadata', + 'content-type': 'application/json;odata=nometadata', + 'if-match': '*' + }, + data: { 'DefaultSensitivityLabelForLibrary': '' }, + responseType: 'json' + }; + + await request.patch(requestOptions); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + }; + + if (args.options.force) { + await removeSensitivityLabel(); + } + else { + const result = await cli.promptForConfirmation({ message: `Are you sure you want to remove the sensitivity label from list '${args.options.listId || args.options.listTitle || args.options.listUrl}'?` }); + + if (result) { + await removeSensitivityLabel(); + } + } + } +} + +export default new SpoListSensitivityLabelRemoveCommand();