Skip to content

Commit 4371665

Browse files
Adds command purview threatassessment add
1 parent d4d820f commit 4371665

File tree

5 files changed

+481
-0
lines changed

5 files changed

+481
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# purview threatassessment add
6+
7+
Create a threat assessment
8+
9+
## Usage
10+
11+
```sh
12+
m365 purview threatassessment add [options]
13+
```
14+
15+
## Options
16+
17+
```md definition-list
18+
`-t, --type <type>`
19+
: The type of threat assessment to retrieve. Supports `file` and `url`.
20+
21+
`-e, --expectedAssessment <expectedAssessment>`
22+
: The expected assessment from submitter. Possible values are: `block` and `unblock`.
23+
24+
`-c, --category <category>`
25+
: The threat category. Possible values are: `spam`, `phishing`, `malware`.
26+
27+
`-p, --path [path]`
28+
: Local path to the file to upload. Can only be used for threat assessment with type `file`.
29+
30+
`-u, --url [url]`
31+
: The URL string. Can only be used for threat assessment with type `url`.
32+
```
33+
34+
<Global />
35+
36+
## Remarks
37+
38+
!!! attention
39+
This command currently only supports delegated permissions.
40+
41+
## Examples
42+
43+
Create a file threat assessment
44+
45+
```sh
46+
m365 purview threatassessment add --type file --expectedAssessment block --category malware --fileName 'test.txt' --path 'C:\Path\To\File.txt'
47+
```
48+
49+
Create a url threat assessment
50+
51+
```sh
52+
m365 purview threatassessment add --type url --expectedAssessment block --category phishing --url 'http://contoso.com'
53+
```
54+
55+
## Response
56+
57+
<Tabs>
58+
<TabItem value="JSON">
59+
60+
```json
61+
{
62+
"id": "b0e69f1c-adda-4df2-2906-08dc2caa6b3f",
63+
"createdDateTime": "2024-02-13T15:42:43.5131654Z",
64+
"contentType": "file",
65+
"expectedAssessment": "block",
66+
"category": "malware",
67+
"status": "pending",
68+
"requestSource": "administrator",
69+
"fileName": "test.txt",
70+
"contentData": "dGVzdC50eHQ=",
71+
"createdBy": {
72+
"user": {
73+
"id": "fe36f75e-c103-410b-a18a-2bf6df06ac3a",
74+
"displayName": "John Doe"
75+
}
76+
}
77+
}
78+
```
79+
80+
</TabItem>
81+
<TabItem value="Text">
82+
83+
```text
84+
category : malware
85+
contentData : dGVzdC50eHQ=
86+
contentType : file
87+
createdBy : {"user":{"id":"fe36f75e-c103-410b-a18a-2bf6df06ac3a","displayName":"John Doe"}}
88+
createdDateTime : 2024-02-13T16:15:00.4125206Z
89+
expectedAssessment: block
90+
fileName : test.txt
91+
id : be1223c4-4e92-4a4c-8c16-08dc2caeedba
92+
requestSource : administrator
93+
status : pending
94+
```
95+
96+
</TabItem>
97+
<TabItem value="CSV">
98+
99+
```csv
100+
id,createdDateTime,contentType,expectedAssessment,category,status,requestSource,fileName,contentData
101+
d31eb5f5-ecd5-4a8e-fb2a-08dc2caf00a8,2024-02-13T16:15:32.1741098Z,file,block,malware,pending,administrator,test.txt,dGVzdC50eHQ=
102+
```
103+
104+
</TabItem>
105+
<TabItem value="Markdown">
106+
107+
```md
108+
# purview threatassessment add --type "file" --expectedAssessment "block" --category "malware" --path "C:\temp\test.txt"
109+
110+
Date: 13/02/2024
111+
112+
## e3524baf-6ea6-4fab-68af-08dc2caf0ae3
113+
114+
Property | Value
115+
---------|-------
116+
id | e3524baf-6ea6-4fab-68af-08dc2caf0ae3
117+
createdDateTime | 2024-02-13T16:15:49.3342383Z
118+
contentType | file
119+
expectedAssessment | block
120+
category | malware
121+
status | pending
122+
requestSource | administrator
123+
fileName | test.txt
124+
contentData | dGVzdC50eHQ=
125+
```
126+
127+
</TabItem>
128+
</Tabs>

docs/src/config/sidebars.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,11 @@ const sidebars: SidebarsConfig = {
17611761
},
17621762
{
17631763
threatassessment: [
1764+
{
1765+
type: 'doc',
1766+
label: 'threatassessment add',
1767+
id: 'cmd/purview/threatassessment/threatassessment-add'
1768+
},
17641769
{
17651770
type: 'doc',
17661771
label: 'threatassessment get',

src/m365/purview/commands.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ export default {
1919
SENSITIVITYLABEL_GET: `${prefix} sensitivitylabel get`,
2020
SENSITIVITYLABEL_LIST: `${prefix} sensitivitylabel list`,
2121
SENSITIVITYLABEL_POLICYSETTINGS_LIST: `${prefix} sensitivitylabel policysettings list`,
22+
THREATASSESSMENT_ADD: `${prefix} threatassessment add`,
2223
THREATASSESSMENT_GET: `${prefix} threatassessment get`
2324
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import { telemetry } from '../../../../telemetry.js';
4+
import auth from '../../../../Auth.js';
5+
import { cli } from '../../../../cli/cli.js';
6+
import { CommandInfo } from '../../../../cli/CommandInfo.js';
7+
import { Logger } from '../../../../cli/Logger.js';
8+
import { CommandError } from '../../../../Command.js';
9+
import request from '../../../../request.js';
10+
import { pid } from '../../../../utils/pid.js';
11+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
12+
import { session } from '../../../../utils/session.js';
13+
import { accessToken } from '../../../../utils/accessToken.js';
14+
import fs from 'fs';
15+
import commands from '../../commands.js';
16+
import command from './threatassessment-add.js';
17+
18+
describe(commands.THREATASSESSMENT_ADD, () => {
19+
20+
let log: string[];
21+
let logger: Logger;
22+
let loggerLogSpy: sinon.SinonSpy;
23+
let commandInfo: CommandInfo;
24+
25+
before(() => {
26+
sinon.stub(auth, 'restoreAuth').resolves();
27+
sinon.stub(telemetry, 'trackEvent').returns();
28+
sinon.stub(pid, 'getProcessName').returns('');
29+
sinon.stub(session, 'getId').returns('');
30+
auth.service.connected = true;
31+
auth.service.accessTokens[(command as any).resource] = {
32+
accessToken: 'abc',
33+
expiresOn: new Date()
34+
};
35+
commandInfo = cli.getCommandInfo(command);
36+
});
37+
38+
beforeEach(() => {
39+
log = [];
40+
logger = {
41+
log: async (msg: string) => {
42+
log.push(msg);
43+
},
44+
logRaw: async (msg: string) => {
45+
log.push(msg);
46+
},
47+
logToStderr: async (msg: string) => {
48+
log.push(msg);
49+
}
50+
};
51+
loggerLogSpy = sinon.spy(logger, 'log');
52+
(command as any).items = [];
53+
sinon.stub(accessToken, 'isAppOnlyAccessToken').returns(false);
54+
sinon.stub(fs, 'existsSync').returns(true);
55+
sinon.stub(fs, 'readFileSync').returns('VGhpcyBpcyBhIHRlc3QgZmlsZQ==');
56+
});
57+
58+
afterEach(() => {
59+
sinonUtil.restore([
60+
accessToken.isAppOnlyAccessToken,
61+
fs.existsSync,
62+
fs.readFileSync,
63+
request.post
64+
]);
65+
});
66+
67+
after(() => {
68+
sinon.restore();
69+
auth.service.connected = false;
70+
auth.service.accessTokens = {};
71+
});
72+
73+
it('has correct name', () => {
74+
assert.strictEqual(command.name.startsWith(commands.THREATASSESSMENT_ADD), true);
75+
});
76+
77+
it('has a description', () => {
78+
assert.notStrictEqual(command.description, null);
79+
});
80+
81+
it('creates a file assessment request', async () => {
82+
const fileThreatAssessmentRequest = {
83+
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#informationProtection/threatAssessmentRequests/$entity',
84+
'@odata.type': '#microsoft.graph.fileAssessmentRequest',
85+
'id': '18406a56-7209-4720-a250-08d772fccdaa',
86+
'createdDateTime': '2019-11-27T05:44:00.4051536Z',
87+
'contentType': 'file',
88+
'expectedAssessment': 'block',
89+
'category': 'malware',
90+
'status': 'completed',
91+
'requestSource': 'administrator',
92+
'fileName': 'illegalfile.txt',
93+
'contentData': '',
94+
'createdBy': {
95+
'user': {
96+
'id': 'c52ce8db-3e4b-4181-93c4-7d6b6bffaf60',
97+
'displayName': 'John Doe'
98+
}
99+
}
100+
};
101+
102+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
103+
if (opts.url === `https://graph.microsoft.com/v1.0/informationProtection/threatAssessmentRequests`) {
104+
return fileThreatAssessmentRequest;
105+
}
106+
107+
throw 'Invalid request';
108+
});
109+
110+
await command.action(logger, { options: { type: 'file', expectedAssessment: 'block', category: 'malware', path: 'C:\\Temp\\DummyFile.txt', verbose: true } });
111+
assert.strictEqual(postStub.lastCall.args[0].data['@odata.type'], '#microsoft.graph.fileAssessmentRequest');
112+
assert(loggerLogSpy.calledWith(fileThreatAssessmentRequest));
113+
});
114+
115+
it('creates an url assessment request', async () => {
116+
const urlThreatAssessmentRequest = {
117+
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#informationProtection/threatAssessmentRequests/$entity',
118+
'@odata.type': '#microsoft.graph.urlAssessmentRequest',
119+
'id': '8d87d2b2-ca4d-422c-f8df-08d774a5c9ac',
120+
'createdDateTime': '2019-11-29T08:26:09.8196703Z',
121+
'contentType': 'url',
122+
'expectedAssessment': 'block',
123+
'category': 'phishing',
124+
'status': 'pending',
125+
'requestSource': 'administrator',
126+
'url': 'http://test.com',
127+
'createdBy': {
128+
'user': {
129+
'id': 'c52ce8db-3e4b-4181-93c4-7d6b6bffaf60',
130+
'displayName': 'John Doe'
131+
}
132+
}
133+
};
134+
135+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
136+
if (opts.url === `https://graph.microsoft.com/v1.0/informationProtection/threatAssessmentRequests`) {
137+
return urlThreatAssessmentRequest;
138+
}
139+
140+
throw 'Invalid request';
141+
});
142+
143+
await command.action(logger, { options: { type: 'url', expectedAssessment: 'block', category: 'phishing', url: 'https://phisingurl.be', verbose: true } });
144+
assert.strictEqual(postStub.lastCall.args[0].data['@odata.type'], '#microsoft.graph.urlAssessmentRequest');
145+
assert(loggerLogSpy.calledWith(urlThreatAssessmentRequest));
146+
});
147+
148+
it('passes validation if all options are passed propertly', async () => {
149+
const actual = await command.validate({ options: { type: 'url', expectedAssessment: 'block', category: 'spam', url: 'https://pnp.github.io/cli-microsoft365/' } }, commandInfo);
150+
assert.strictEqual(actual, true);
151+
});
152+
153+
it('fails validation if type is not a valid type', async () => {
154+
const actual = await command.validate({ options: { type: 'invalid', expectedAssessment: 'block', category: 'spam', url: 'https://pnp.github.io/cli-microsoft365/' } }, commandInfo);
155+
assert.notStrictEqual(actual, true);
156+
});
157+
158+
it('fails validation if expectedAssessment is not a valid expectedAssessment', async () => {
159+
const actual = await command.validate({ options: { type: 'url', expectedAssessment: 'invalid', category: 'spam', url: 'https://pnp.github.io/cli-microsoft365/' } }, commandInfo);
160+
assert.notStrictEqual(actual, true);
161+
});
162+
163+
it('fails validation if category is not a valid category', async () => {
164+
const actual = await command.validate({ options: { type: 'url', expectedAssessment: 'block', category: 'invalid', url: 'https://pnp.github.io/cli-microsoft365/' } }, commandInfo);
165+
assert.notStrictEqual(actual, true);
166+
});
167+
168+
it('fails validation if path is specified and file does not exist', async () => {
169+
sinonUtil.restore(fs.existsSync);
170+
sinon.stub(fs, 'existsSync').returns(false);
171+
const actual = await command.validate({ options: { type: 'file', expectedAssessment: 'block', category: 'malware', path: 'C:\\Path\\That\\Does\\Not\\Exist' } }, commandInfo);
172+
assert.notStrictEqual(actual, true);
173+
});
174+
175+
it('throws an error when we execute the command using application permissions', async () => {
176+
sinonUtil.restore(accessToken.isAppOnlyAccessToken);
177+
sinon.stub(accessToken, 'isAppOnlyAccessToken').returns(true);
178+
await assert.rejects(command.action(logger, { options: { type: 'url', expectedAssessment: 'block', category: 'spam', url: 'https://pnp.github.io/cli-microsoft365/' } }),
179+
new CommandError('This command currently does not support app only permissions.'));
180+
});
181+
});

0 commit comments

Comments
 (0)