Skip to content

Commit 2498ee4

Browse files
authored
adding trusted activities commands (#325)
* adding trusted activities commands * fixing integration test * added trust service to build.yml * doc fixes * styling * remove trusted-activities show cmd * remove trusted-activities show integration test * styling * adjust changelog * by row error handling * check resource-id type in bulk cmds * style * whitespace * removing redundant try-catch block
1 parent a3e7f78 commit 2498ee4

File tree

11 files changed

+641
-11
lines changed

11 files changed

+641
-11
lines changed

.github/workflows/build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
127.0.0.1 preservation-data-service
5151
127.0.0.1 connected-server
5252
127.0.0.1 cases
53+
127.0.0.1 trusted-activities-service
5354
EOF
5455
- name: Install ncat
5556
run: sudo apt-get install ncat

CHANGELOG.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,22 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1515
- New option `--include-legal-hold-membership` on command `code42 users list` that includes the legal hold matter name and ID for any user on legal hold.
1616

1717
- New commands for deactivating/reactivating Code42 user accounts:
18-
- `code42 users deactivate`
19-
- `code42 users reactivate`
20-
- `code42 users bulk deactivate`
21-
- `code42 users bulk reactivate`
18+
- `code42 users deactivate`
19+
- `code42 users reactivate`
20+
- `code42 users bulk deactivate`
21+
- `code42 users bulk reactivate`
2222

2323
- `code42 profile use` now prompts you to select a profile when not given a profile name argument.
2424

25+
- New `trusted-activities` commands for managing trusted activities and resources:
26+
- `code42 trusted-activities create` to create a trusted activity.
27+
- `code42 trusted-activities update` to update a trusted activity.
28+
- `code42 trusted-activities remove` to remove a trusted activity.
29+
- `code42 trusted-activities list` to print the details of all trusted activities.
30+
- `code42 trusted-activities bulk create` to bulk create trusted activities from a CSV file.
31+
- `code42 trusted-activities bulk update` to bulk update trusted activities from a CSV file.
32+
- `code42 trusted-activities bulk remove` to bulk remove trusted activities from a CSV file.
33+
2534
### Fixed
2635

2736
- Bug where `audit-logs search` with `--use-checkpoint` option was causing output formatting problems.

docs/commands.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# Commands
22

3-
* [Profile](commands/profile.rst)
4-
* [Security Data](commands/securitydata.rst)
5-
* [Audit Logs](commands/auditlogs.rst)
6-
* [Alerts](commands/alerts.rst)
73
* [Alert Rules](commands/alertrules.rst)
4+
* [Alerts](commands/alerts.rst)
5+
* [Audit Logs](commands/auditlogs.rst)
6+
* [Cases](commands/cases.rst)
87
* [Departing Employee](commands/departingemployee.rst)
98
* [Devices](commands/devices.rst)
109
* [High Risk Employee](commands/highriskemployee.rst)
1110
* [Legal Hold](commands/legalhold.rst)
12-
* [Cases](commands/cases.rst)
11+
* [Profile](commands/profile.rst)
12+
* [Security Data](commands/securitydata.rst)
13+
* [Trusted Activities](commands/trustedactivities.rst)
1314
* [Users](commands/users.rst)

docs/commands/trustedactivities.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.. click:: code42cli.cmds.trustedactivities:trusted_activities
2+
:prog: trusted-activities
3+
:nested: full

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"keyrings.alt==3.2.0",
4141
"ipython>=7.16.1",
4242
"pandas>=1.1.3",
43-
"py42>=1.18.1",
43+
"py42>=1.19.0",
4444
],
4545
extras_require={
4646
"dev": [

src/code42cli/click_ext/groups.py

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
from py42.exceptions import Py42InvalidUsernameError
1616
from py42.exceptions import Py42LegalHoldNotFoundOrPermissionDeniedError
1717
from py42.exceptions import Py42OrgNotFoundError
18+
from py42.exceptions import Py42TrustedActivityConflictError
19+
from py42.exceptions import Py42TrustedActivityIdNotFound
20+
from py42.exceptions import Py42TrustedActivityInvalidCharacterError
1821
from py42.exceptions import Py42UpdateClosedCaseError
1922
from py42.exceptions import Py42UserAlreadyAddedError
2023
from py42.exceptions import Py42UsernameMustBeEmailError
@@ -79,6 +82,9 @@ def invoke(self, ctx):
7982
Py42InvalidUsernameError,
8083
Py42ActiveLegalHoldError,
8184
Py42OrgNotFoundError,
85+
Py42TrustedActivityConflictError,
86+
Py42TrustedActivityInvalidCharacterError,
87+
Py42TrustedActivityIdNotFound,
8288
) as err:
8389
self.logger.log_error(err)
8490
raise Code42CLIError(str(err))
+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import click
2+
from py42.clients.trustedactivities import TrustedActivityType
3+
4+
from code42cli.bulk import generate_template_cmd_factory
5+
from code42cli.bulk import run_bulk_process
6+
from code42cli.click_ext.groups import OrderedGroup
7+
from code42cli.errors import Code42CLIError
8+
from code42cli.file_readers import read_csv_arg
9+
from code42cli.options import format_option
10+
from code42cli.options import sdk_options
11+
from code42cli.output_formats import OutputFormatter
12+
13+
resource_id_arg = click.argument("resource-id", type=int)
14+
type_option = click.option(
15+
"--type",
16+
help=f"Type of trusted activity. Valid types include {', '.join(TrustedActivityType.choices())}.",
17+
type=click.Choice(TrustedActivityType.choices()),
18+
)
19+
value_option = click.option(
20+
"--value",
21+
help="The value of the trusted activity, such as the domain or Slack workspace name.",
22+
)
23+
description_option = click.option(
24+
"--description", help="The description of the trusted activity."
25+
)
26+
27+
28+
def _get_trust_header():
29+
return {
30+
"resourceId": "Resource Id",
31+
"type": "Type",
32+
"value": "Value",
33+
"description": "Description",
34+
"updatedAt": "Last Update Time",
35+
"updatedByUsername": "Last Updated By (Username)",
36+
"updatedByUserUid": "Last updated By (UserUID)",
37+
}
38+
39+
40+
@click.group(cls=OrderedGroup)
41+
@sdk_options(hidden=True)
42+
def trusted_activities(state):
43+
"""Manage trusted activities and resources."""
44+
pass
45+
46+
47+
@trusted_activities.command()
48+
@click.argument("type", type=click.Choice(TrustedActivityType.choices()))
49+
@click.argument("value")
50+
@description_option
51+
@sdk_options()
52+
def create(state, type, value, description):
53+
"""Create a trusted activity.
54+
55+
VALUE is the name of the domain or Slack workspace.
56+
"""
57+
state.sdk.trustedactivities.create(
58+
type, value, description=description,
59+
)
60+
61+
62+
@trusted_activities.command()
63+
@resource_id_arg
64+
@value_option
65+
@description_option
66+
@sdk_options()
67+
def update(state, resource_id, value, description):
68+
"""Update a trusted activity. Requires the activity's resource ID."""
69+
state.sdk.trustedactivities.update(
70+
resource_id, value=value, description=description,
71+
)
72+
73+
74+
@trusted_activities.command()
75+
@resource_id_arg
76+
@sdk_options()
77+
def remove(state, resource_id):
78+
"""Remove a trusted activity. Requires the activity's resource ID."""
79+
state.sdk.trustedactivities.delete(resource_id)
80+
81+
82+
@trusted_activities.command("list")
83+
@click.option("--type", type=click.Choice(TrustedActivityType.choices()))
84+
@format_option
85+
@sdk_options()
86+
def _list(state, type, format):
87+
"""List all trusted activities."""
88+
pages = state.sdk.trustedactivities.get_all(type=type)
89+
formatter = OutputFormatter(format, _get_trust_header())
90+
trusted_resources = [
91+
resource for page in pages for resource in page["trustResources"]
92+
]
93+
if trusted_resources:
94+
formatter.echo_formatted_list(trusted_resources)
95+
else:
96+
click.echo("No trusted activities found.")
97+
98+
99+
@trusted_activities.group(cls=OrderedGroup)
100+
@sdk_options(hidden=True)
101+
def bulk(state):
102+
"""Tools for executing bulk trusted activity actions."""
103+
pass
104+
105+
106+
TRUST_CREATE_HEADERS = [
107+
"type",
108+
"value",
109+
"description",
110+
]
111+
TRUST_UPDATE_HEADERS = [
112+
"resource_id",
113+
"value",
114+
"description",
115+
]
116+
TRUST_REMOVE_HEADERS = [
117+
"resource_id",
118+
]
119+
120+
trusted_activities_generate_template = generate_template_cmd_factory(
121+
group_name="trusted_activities",
122+
commands_dict={
123+
"create": TRUST_CREATE_HEADERS,
124+
"update": TRUST_UPDATE_HEADERS,
125+
"remove": TRUST_REMOVE_HEADERS,
126+
},
127+
help_message="Generate the CSV template needed for bulk trusted-activities commands",
128+
)
129+
bulk.add_command(trusted_activities_generate_template)
130+
131+
132+
@bulk.command(
133+
name="create",
134+
help="Bulk create trusted activities using a CSV file with "
135+
f"format: {','.join(TRUST_UPDATE_HEADERS)}.",
136+
)
137+
@read_csv_arg(headers=TRUST_CREATE_HEADERS)
138+
@sdk_options()
139+
def bulk_create(state, csv_rows):
140+
"""Bulk create trusted activities."""
141+
sdk = state.sdk
142+
143+
def handle_row(type, value, description):
144+
if type not in TrustedActivityType.choices():
145+
message = f"Invalid type {type}, valid types include {', '.join(TrustedActivityType.choices())}."
146+
raise Code42CLIError(message)
147+
if type is None:
148+
message = "'type' is a required field to create a trusted activity."
149+
raise Code42CLIError(message)
150+
if value is None:
151+
message = "'value' is a required field to create a trusted activity."
152+
raise Code42CLIError(message)
153+
sdk.trustedactivities.create(type, value, description)
154+
155+
run_bulk_process(
156+
handle_row, csv_rows, progress_label="Creating trusting activities:",
157+
)
158+
159+
160+
@bulk.command(
161+
name="update",
162+
help="Bulk update trusted activities using a CSV file with "
163+
f"format: {','.join(TRUST_UPDATE_HEADERS)}.",
164+
)
165+
@read_csv_arg(headers=TRUST_UPDATE_HEADERS)
166+
@sdk_options()
167+
def bulk_update(state, csv_rows):
168+
"""Bulk update trusted activities."""
169+
sdk = state.sdk
170+
171+
def handle_row(resource_id, value, description):
172+
if resource_id is None:
173+
message = "'resource_id' is a required field to update a trusted activity."
174+
raise Code42CLIError(message)
175+
_check_resource_id_type(resource_id)
176+
sdk.trustedactivities.update(resource_id, value, description)
177+
178+
run_bulk_process(
179+
handle_row, csv_rows, progress_label="Updating trusted activities:"
180+
)
181+
182+
183+
@bulk.command(
184+
name="remove",
185+
help="Bulk remove trusted activities using a CSV file with "
186+
f"format: {','.join(TRUST_REMOVE_HEADERS)}.",
187+
)
188+
@read_csv_arg(headers=TRUST_REMOVE_HEADERS)
189+
@sdk_options()
190+
def bulk_remove(state, csv_rows):
191+
"""Bulk remove trusted activities."""
192+
sdk = state.sdk
193+
194+
def handle_row(resource_id):
195+
if resource_id is None:
196+
message = "'resource_id' is a required field to remove a trusted activity."
197+
raise Code42CLIError(message)
198+
_check_resource_id_type(resource_id)
199+
sdk.trustedactivities.delete(resource_id)
200+
201+
run_bulk_process(
202+
handle_row, csv_rows, progress_label="Removing trusted activities:",
203+
)
204+
205+
206+
def _check_resource_id_type(resource_id):
207+
def raise_error(resource_id):
208+
message = f"Invalid resource ID {resource_id}. Must be an integer."
209+
raise Code42CLIError(message)
210+
211+
try:
212+
if not float(resource_id).is_integer():
213+
raise_error(resource_id)
214+
except ValueError:
215+
raise_error(resource_id)

src/code42cli/main.py

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from code42cli.cmds.profile import profile
2323
from code42cli.cmds.securitydata import security_data
2424
from code42cli.cmds.shell import shell
25+
from code42cli.cmds.trustedactivities import trusted_activities
2526
from code42cli.cmds.users import users
2627
from code42cli.options import sdk_options
2728

@@ -91,3 +92,4 @@ def cli(state, python, script_dir):
9192
cli.add_command(security_data)
9293
cli.add_command(shell)
9394
cli.add_command(users)
95+
cli.add_command(trusted_activities)

tests/cmds/test_cases.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def test_cases_create_when_description_length_limit_exceeds_raises_exception_pri
417417
assert "Description limit exceeded, max 250 characters allowed." in result.output
418418

419419

420-
def test_cases_udpate_when_description_length_limit_exceeds_raises_exception_prints_error_message(
420+
def test_cases_update_when_description_length_limit_exceeds_raises_exception_prints_error_message(
421421
runner, cli_state, case_description_limit_exceeded_error
422422
):
423423
cli_state.sdk.cases.update.side_effect = case_description_limit_exceeded_error

0 commit comments

Comments
 (0)