From c6a243c150eafa002e71c34e413c4b22ebb5f65d Mon Sep 17 00:00:00 2001 From: Mathijs Verbeeck Date: Sat, 25 Feb 2023 19:30:53 +0100 Subject: [PATCH] Add new command 'purview threatassessment get'. Closes #4427 --- .eslintrc.js | 2 + .../threatassessment/threatassessment-get.md | 191 ++++++++++++++++++ docs/mkdocs.yml | 2 + src/m365/purview/commands.ts | 3 +- .../threatassessment-get.spec.ts | 163 +++++++++++++++ .../threatassessment/threatassessment-get.ts | 87 ++++++++ 6 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 docs/docs/cmd/purview/threatassessment/threatassessment-get.md create mode 100644 src/m365/purview/commands/threatassessment/threatassessment-get.spec.ts create mode 100644 src/m365/purview/commands/threatassessment/threatassessment-get.ts diff --git a/.eslintrc.js b/.eslintrc.js index c0041043950..4360f953dec 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,6 +10,7 @@ const dictionary = [ 'application', 'apply', 'approve', + 'assessment', 'assets', 'audit', 'bin', @@ -84,6 +85,7 @@ const dictionary = [ 'storage', 'table', 'teams', + 'threat', 'token', 'type', 'user', diff --git a/docs/docs/cmd/purview/threatassessment/threatassessment-get.md b/docs/docs/cmd/purview/threatassessment/threatassessment-get.md new file mode 100644 index 00000000000..5061bc05e69 --- /dev/null +++ b/docs/docs/cmd/purview/threatassessment/threatassessment-get.md @@ -0,0 +1,191 @@ +# purview threatassessment get + +Get a threat assessment + +## Usage + +```sh +m365 purview threatassessment get [options] +``` + +## Options + +`-i, --id ` +: The Id of the threat assessment + +`--includeResults` +: Include the threat assessment results + +--8<-- "docs/cmd/_global.md" + +## Examples + +Get a threat assessment + +```sh +m365 purview threatassessment get --id c37d695e-d581-4ae9-82a0-9364eba4291e +``` + +Get a threat assessment including results + +```sh +m365 purview threatassessment get --id c37d695e-d581-4ae9-82a0-9364eba4291e --includeResults +``` + +## Response + +### Standard Response + +=== "JSON" + + ```json + { + "id": "8aaba0ac-ec4d-4e62-5774-08db16c68731", + "createdDateTime": "2023-02-25T00:23:33.0550644Z", + "contentType": "mail", + "expectedAssessment": "block", + "category": "spam", + "status": "pending", + "requestSource": "administrator", + "recipientEmail": "john@contoso.com", + "destinationRoutingReason": "notJunk", + "messageUri": "https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA=", + "createdBy": { + "user": { + "id": "fe36f75e-c103-410b-a18a-2bf6df06ac3a", + "displayName": "John Doe" + } + } + } + ``` + +=== "Text" + + ```text + category : spam + contentType : mail + createdBy : {"user":{"id":"fe36f75e-c103-410b-a18a-2bf6df06ac3a","displayName":"John Doe"}} + createdDateTime : 2023-02-25T00:23:33.0550644Z + destinationRoutingReason: notJunk + expectedAssessment : block + id : 8aaba0ac-ec4d-4e62-5774-08db16c68731 + messageUri : https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA= + recipientEmail : john@contoso.com + requestSource : administrator + status : pending + ``` + +=== "CSV" + + ```csv + id,createdDateTime,contentType,expectedAssessment,category,status,requestSource,recipientEmail,destinationRoutingReason,messageUri,createdBy + 8aaba0ac-ec4d-4e62-5774-08db16c68731,2023-02-25T00:23:33.0550644Z,mail,block,spam,pending,administrator,john@contoso.com,notJunk,https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA=,"{""user"":{""id"":""fe36f75e-c103-410b-a18a-2bf6df06ac3a"",""displayName"":""John Doe""}}" + ``` + +=== "Markdown" + + ```md + # purview threatassessment get --id "8aaba0ac-ec4d-4e62-5774-08db16c68731" + + Date: 25/02/2023 + + ## 8aaba0ac-ec4d-4e62-5774-08db16c68731 + + Property | Value + ---------|------- + id | 8aaba0ac-ec4d-4e62-5774-08db16c68731 + createdDateTime | 2023-02-25T00:23:33.0550644Z + contentType | mail + expectedAssessment | block + category | spam + status | pending + requestSource | administrator + recipientEmail | john@contoso.com + destinationRoutingReason | notJunk + messageUri | https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E\_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E\_hLMK5kAALHNaMuAAA= + createdBy | {"user":{"id":"fe36f75e-c103-410b-a18a-2bf6df06ac3a","displayName":"John Doe"}} + ``` + +### `includeResults` response + +When we make use of the option `includeResults` the response will differ. + +=== "JSON" + + ```json + { + "id": "8aaba0ac-ec4d-4e62-5774-08db16c68731", + "createdDateTime": "2023-02-25T00:23:33.0550644Z", + "contentType": "mail", + "expectedAssessment": "block", + "category": "spam", + "status": "pending", + "requestSource": "administrator", + "recipientEmail": "john@contoso.com", + "destinationRoutingReason": "notJunk", + "messageUri": "https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA=", + "createdBy": { + "user": { + "id": "fe36f75e-c103-410b-a18a-2bf6df06ac3a", + "displayName": "John Doe" + } + }, + "results": [ + { + "id": "a5455871-18d1-44d8-0866-08db16c68b85", + "createdDateTime": "2023-02-25T00:23:40.28Z", + "resultType": "checkPolicy", + "message": "No policy was hit." + } + ] + } + ``` + +=== "Text" + + ```text + category : spam + contentType : mail + createdBy : {"user":{"id":"fe36f75e-c103-410b-a18a-2bf6df06ac3a","displayName":"John Doe"}} + createdDateTime : 2023-02-25T00:23:33.0550644Z + destinationRoutingReason: notJunk + expectedAssessment : block + id : 8aaba0ac-ec4d-4e62-5774-08db16c68731 + messageUri : https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA= + recipientEmail : john@contoso.com + requestSource : administrator + results : [{"id":"a5455871-18d1-44d8-0866-08db16c68b85","createdDateTime":"2023-02-25T00:23:40.28Z","resultType":"checkPolicy","message":"No policy was hit."}] + status : pending + ``` + +=== "CSV" + + ```csv + id,createdDateTime,contentType,expectedAssessment,category,status,requestSource,recipientEmail,destinationRoutingReason,messageUri,createdBy,results + 8aaba0ac-ec4d-4e62-5774-08db16c68731,2023-02-25T00:23:33.0550644Z,mail,block,spam,pending,administrator,john@contoso.com,notJunk,https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA=,"{""user"":{""id"":""fe36f75e-c103-410b-a18a-2bf6df06ac3a"",""displayName"":""John Doe""}}","[{""id"":""a5455871-18d1-44d8-0866-08db16c68b85"",""createdDateTime"":""2023-02-25T00:23:40.28Z"",""resultType"":""checkPolicy"",""message"":""No policy was hit.""}]" + ``` + +=== "Markdown" + + ```md + # purview threatassessment get --id "8aaba0ac-ec4d-4e62-5774-08db16c68731" --includeResults "true" + + Date: 25/02/2023 + + ## 8aaba0ac-ec4d-4e62-5774-08db16c68731 + + Property | Value + ---------|------- + id | 8aaba0ac-ec4d-4e62-5774-08db16c68731 + createdDateTime | 2023-02-25T00:23:33.0550644Z + contentType | mail + expectedAssessment | block + category | spam + status | pending + requestSource | administrator + recipientEmail | john@contoso.com + destinationRoutingReason | notJunk + messageUri | https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E\_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E\_hLMK5kAALHNaMuAAA= + createdBy | {"user":{"id":"fe36f75e-c103-410b-a18a-2bf6df06ac3a","displayName":"John Doe"}} + results | [{"id":"a5455871-18d1-44d8-0866-08db16c68b85","createdDateTime":"2023-02-25T00:23:40.28Z","resultType":"checkPolicy","message":"No policy was hit."}] + ``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index bf3130cef17..38d361fce62 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -353,6 +353,8 @@ nav: - retentionlabel list: cmd/purview/retentionlabel/retentionlabel-list.md - retentionlabel remove: cmd/purview/retentionlabel/retentionlabel-remove.md - retentionlabel set: cmd/purview/retentionlabel/retentionlabel-set.md + - threatassessment: + - threatassessment get: cmd/purview/threatassessment/threatassessment-get.md - Search (search): - externalconnection: - externalconnection add: cmd/search/externalconnection/externalconnection-add.md diff --git a/src/m365/purview/commands.ts b/src/m365/purview/commands.ts index 9cd940598ff..404825c2df8 100644 --- a/src/m365/purview/commands.ts +++ b/src/m365/purview/commands.ts @@ -15,5 +15,6 @@ export default { RETENTIONLABEL_GET: `${prefix} retentionlabel get`, RETENTIONLABEL_LIST: `${prefix} retentionlabel list`, RETENTIONLABEL_REMOVE: `${prefix} retentionlabel remove`, - RETENTIONLABEL_SET: `${prefix} retentionlabel set` + RETENTIONLABEL_SET: `${prefix} retentionlabel set`, + THREATASSESSMENT_GET: `${prefix} threatassessment get` }; \ No newline at end of file diff --git a/src/m365/purview/commands/threatassessment/threatassessment-get.spec.ts b/src/m365/purview/commands/threatassessment/threatassessment-get.spec.ts new file mode 100644 index 00000000000..6e5af14fd88 --- /dev/null +++ b/src/m365/purview/commands/threatassessment/threatassessment-get.spec.ts @@ -0,0 +1,163 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { telemetry } from '../../../../telemetry'; +import auth from '../../../../Auth'; +import { Logger } from '../../../../cli/Logger'; +import Command, { CommandError } from '../../../../Command'; +import request from '../../../../request'; +import { pid } from '../../../../utils/pid'; +import { session } from '../../../../utils/session'; +import { sinonUtil } from '../../../../utils/sinonUtil'; +import commands from '../../commands'; +import { CommandInfo } from '../../../../cli/CommandInfo'; +import { Cli } from '../../../../cli/Cli'; +const command: Command = require('./threatassessment-get'); + +describe(commands.THREATASSESSMENT_GET, () => { + const threatAssessmentId = 'c37d695e-d581-4ae9-82a0-9364eba4291e'; + const threatAssessmentGetResponse = { + 'id': '8aaba0ac-ec4d-4e62-5774-08db16c68731', + 'createdDateTime': '2023-02-25T00:23:33.0550644Z', + 'contentType': 'mail', + 'expectedAssessment': 'block', + 'category': 'spam', + 'status': 'pending', + 'requestSource': 'administrator', + 'recipientEmail': 'john@contoso.com', + 'destinationRoutingReason': 'notJunk', + 'messageUri': 'https://graph.microsoft.com/v1.0/users/john@contoso.com/messages/AAMkADgzN2Q1NThiLTI0NjYtNGIxYS05MDdjLTg1OWQxNzgwZGM2ZgBGAAAAAAC6jQfUzacTSIHqMw2yacnUBwBiOC8xvYmdT6G2E_hLMK5kAAAAAAEMAABiOC8xvYmdT6G2E_hLMK5kAALHNaMuAAA=', + 'createdBy': { + 'user': { + 'id': 'fe36f75e-c103-410b-a18a-2bf6df06ac3a', + 'displayName': 'John Doe' + } + } + }; + + const threatAssessmentGetResponseIncludingResults = { + ...threatAssessmentGetResponse, + 'results': [ + { + 'id': 'a5455871-18d1-44d8-0866-08db16c68b85', + 'createdDateTime': '2023-02-25T00:23:40.28Z', + 'resultType': 'checkPolicy', + 'message': 'No policy was hit.' + } + ] + }; + + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + + before(() => { + sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); + sinon.stub(pid, 'getProcessName').callsFake(() => ''); + sinon.stub(session, 'getId').callsFake(() => ''); + auth.service.connected = true; + commandInfo = Cli.getCommandInfo(command); + }); + + beforeEach(() => { + log = []; + logger = { + log: (msg: string) => { + log.push(msg); + }, + logRaw: (msg: string) => { + log.push(msg); + }, + logToStderr: (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + (command as any).items = []; + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinonUtil.restore([ + auth.restoreAuth, + telemetry.trackEvent, + pid.getProcessName, + session.getId + ]); + auth.service.connected = false; + auth.service.accessTokens = {}; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.THREATASSESSMENT_GET); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if id is not a valid GUID', async () => { + const actual = await command.validate({ options: { id: 'invalid' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('passes validation if a correct id is entered and includeResults is specified', async () => { + const actual = await command.validate({ options: { id: threatAssessmentId, includeResults: true } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('retrieves threat assessment by specified id', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/informationProtection/threatAssessmentRequests/${threatAssessmentId}`) { + return threatAssessmentGetResponse; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { id: threatAssessmentId, verbose: true } }); + assert(loggerLogSpy.calledWith(threatAssessmentGetResponse)); + }); + + it('retrieves threat assessment by specified id including results', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/informationProtection/threatAssessmentRequests/${threatAssessmentId}?$expand=results`) { + return threatAssessmentGetResponseIncludingResults; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { id: threatAssessmentId, includeResults: true, verbose: true } }); + assert(loggerLogSpy.calledWith(threatAssessmentGetResponseIncludingResults)); + }); + + it('handles error when threat assessment by specified id is not found', async () => { + const error = { + 'error': { + 'code': 'ResourceNotFound', + 'message': 'The requested resource does not exist.', + 'innerError': { + 'date': '2023-02-25T16:13:25', + 'request-id': 'a9e23bc8-0845-4eef-8ba1-e031b098c955', + 'client-request-id': 'a9e23bc8-0845-4eef-8ba1-e031b098c955' + } + } + }; + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/informationProtection/threatAssessmentRequests/${threatAssessmentId}`) { + throw error; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { id: threatAssessmentId } }), new CommandError(error.error.message)); + }); +}); \ No newline at end of file diff --git a/src/m365/purview/commands/threatassessment/threatassessment-get.ts b/src/m365/purview/commands/threatassessment/threatassessment-get.ts new file mode 100644 index 00000000000..3bed24223b9 --- /dev/null +++ b/src/m365/purview/commands/threatassessment/threatassessment-get.ts @@ -0,0 +1,87 @@ +import { Logger } from '../../../../cli/Logger'; +import GlobalOptions from '../../../../GlobalOptions'; +import request, { CliRequestOptions } from '../../../../request'; +import { validation } from '../../../../utils/validation'; +import GraphCommand from '../../../base/GraphCommand'; +import commands from '../../commands'; + +interface CommandArgs { + options: Options; +} + +interface Options extends GlobalOptions { + id: string; + includeResults?: boolean; +} + +class PurviewThreatAssessmentGetCommand extends GraphCommand { + public get name(): string { + return commands.THREATASSESSMENT_GET; + } + + public get description(): string { + return 'Get a threat assessment'; + } + + constructor() { + super(); + + this.#initTelemetry(); + this.#initOptions(); + this.#initValidators(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + includeResults: !!args.options.includeResults + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { + option: '-i, --id ' + }, + { + option: '--includeResults' + } + ); + } + + #initValidators(): void { + this.validators.push( + async (args: CommandArgs) => { + if (!validation.isValidGuid(args.options.id)) { + return `${args.options.id} is not a valid GUID.`; + } + return true; + } + ); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + logger.logToStderr(`Retrieving threat assessment with id ${args.options.id}`); + } + + const requestOptions: CliRequestOptions = { + url: `${this.resource}/v1.0/informationProtection/threatAssessmentRequests/${args.options.id}${args.options.includeResults ? '?$expand=results' : ''}`, + headers: { + accept: 'application/json;odata.metadata=none' + }, + responseType: 'json' + }; + + const res: any = await request.get(requestOptions); + logger.log(res); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +module.exports = new PurviewThreatAssessmentGetCommand(); \ No newline at end of file