Skip to content

Commit 1ec784d

Browse files
authored
Feature/api clients (#389)
* api client support * add api client auth section to profile user guide * fix unit tests * suggest wrapping secrets in single quotes * reword to username/password authentication * init sdk in bulk remove command * minor change tp update cmd param check * update unit tests
1 parent cc2b586 commit 1ec784d

15 files changed

+552
-122
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1212

1313
### Added
1414

15+
- Support for Code42 API clients.
16+
- You can create a new profile with API client authentication using `code42 profile create-api-client`
17+
- Or, update your existing profile to use API clients with `code42 update --api-client-id <id> --secret <secret>`
18+
- When using API client authentication, changes to the following `legal-hold` commands:
19+
- `code42 legal-hold list` - Change in response shape.
20+
- `code42 legal-hold show` - Change in response shape.
21+
- `code42 legal-hold search-events` - **Not available.**
1522
- New commands to view details for user risk profiles:
1623
- `code42 users list-risk-profiles`
1724
- `code42 users show-risk-profile`

docs/userguides/profile.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
Use the [code42 profile](../commands/profile.md) set of commands to establish the Code42 environment you're working
44
within and your user information.
55

6-
First, create your profile:
6+
## User token authentication
7+
8+
Use the following command to create your profile with user token authentication:
79
```bash
810
code42 profile create --name MY_FIRST_PROFILE --server example.authority.com --username [email protected]
911
```
@@ -15,6 +17,19 @@ Your password is not shown when you do `code42 profile show`. However, `code42 p
1517
password exists for your profile. If you do not set a password, you will be securely prompted to enter a password each
1618
time you run a command.
1719

20+
## API client authentication
21+
22+
Once you've generated an API Client in your Code42 console, use the following command to create your profile with API client authentication:
23+
```bash
24+
code42 profile create-api-client --name MY_API_CLIENT_PROFILE --server example.authority.com --api-client-id "key-42" --secret "code42%api%client%secret"
25+
```
26+
27+
```{eval-rst}
28+
.. note:: Remember to wrap your API client secret with single quotes to avoid issues with bash expansion and special characters.
29+
```
30+
31+
## View profiles
32+
1833
You can add multiple profiles with different names and the change the default profile with the `use` command:
1934

2035
```bash

docs/userguides/v2apis.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ V1 file event APIs were marked deprecated in May 2022 and will be no longer be s
1111
Use the `--use-v2-file-events True` option with the `code42 profile create` or `code42 profile update` commands to enable your code42 CLI profile to use the latest V2 file event data model.
1212

1313
Use `code42 profile show` to check the status of this setting on your profile:
14+
1415
```bash
1516
% code42 profile update --use-v2-file-events True
1617

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"keyrings.alt==3.2.0",
4040
"ipython==7.16.3",
4141
"pandas>=1.1.3",
42-
"py42>=1.24.0",
42+
"py42>=1.26.0",
4343
],
4444
extras_require={
4545
"dev": [

src/code42cli/cmds/legal_hold.py

+64-34
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import click
66
from click import echo
7+
from click import style
78

89
from code42cli.bulk import generate_template_cmd_factory
910
from code42cli.bulk import run_bulk_process
@@ -89,7 +90,7 @@ def add_user(state, matter_id, username):
8990
@sdk_options()
9091
def remove_user(state, matter_id, username):
9192
"""Release a custodian from a legal hold matter."""
92-
_remove_user_from_legal_hold(state.sdk, matter_id, username)
93+
_remove_user_from_legal_hold(state, state.sdk, matter_id, username)
9394

9495

9596
@legal_hold.command("list")
@@ -98,7 +99,7 @@ def remove_user(state, matter_id, username):
9899
def _list(state, format=None):
99100
"""Fetch existing legal hold matters."""
100101
formatter = OutputFormatter(format, _MATTER_KEYS_MAP)
101-
matters = _get_all_active_matters(state.sdk)
102+
matters = _get_all_active_matters(state)
102103
if matters:
103104
formatter.echo_formatted_list(matters)
104105

@@ -120,14 +121,21 @@ def _list(state, format=None):
120121
def show(state, matter_id, include_inactive=False, include_policy=False):
121122
"""Display details of a given legal hold matter."""
122123
matter = _check_matter_is_accessible(state.sdk, matter_id)
123-
matter["creator_username"] = matter["creator"]["username"]
124+
125+
if state.profile.api_client_auth == "True":
126+
try:
127+
matter["creator_username"] = matter["creator"]["user"]["email"]
128+
except KeyError:
129+
pass
130+
else:
131+
matter["creator_username"] = matter["creator"]["username"]
124132
matter = json.loads(matter.text)
125133

126134
# if `active` is None then all matters (whether active or inactive) are returned. True returns
127135
# only those that are active.
128136
active = None if include_inactive else True
129137
memberships = _get_legal_hold_memberships_for_matter(
130-
state.sdk, matter_id, active=active
138+
state, state.sdk, matter_id, active=active
131139
)
132140
active_usernames = [
133141
member["user"]["username"] for member in memberships if member["active"]
@@ -161,6 +169,15 @@ def show(state, matter_id, include_inactive=False, include_policy=False):
161169
@sdk_options()
162170
def search_events(state, matter_id, event_type, begin, end, format):
163171
"""Tools for getting legal hold event data."""
172+
if state.profile.api_client_auth == "True":
173+
echo(
174+
style(
175+
"WARNING: This method is unavailable with API Client Authentication.",
176+
fg="red",
177+
),
178+
err=True,
179+
)
180+
164181
formatter = OutputFormatter(format, _EVENT_KEYS_MAP)
165182
events = _get_all_events(state.sdk, matter_id, begin, end)
166183
if event_type:
@@ -214,7 +231,7 @@ def remove(state, csv_rows):
214231
sdk = state.sdk
215232

216233
def handle_row(matter_id, username):
217-
_remove_user_from_legal_hold(sdk, matter_id, username)
234+
_remove_user_from_legal_hold(state, sdk, matter_id, username)
218235

219236
run_bulk_process(
220237
handle_row, csv_rows, progress_label="Removing users from legal hold:"
@@ -227,11 +244,20 @@ def _add_user_to_legal_hold(sdk, matter_id, username):
227244
sdk.legalhold.add_to_matter(user_id, matter_id)
228245

229246

230-
def _remove_user_from_legal_hold(sdk, matter_id, username):
247+
def _remove_user_from_legal_hold(state, sdk, matter_id, username):
231248
_check_matter_is_accessible(sdk, matter_id)
232-
membership_id = _get_legal_hold_membership_id_for_user_and_matter(
233-
sdk, username, matter_id
249+
250+
user_id = get_user_id(sdk, username)
251+
memberships = _get_legal_hold_memberships_for_matter(
252+
state, sdk, matter_id, active=True
234253
)
254+
membership_id = None
255+
for member in memberships:
256+
if member["user"]["userUid"] == user_id:
257+
membership_id = member["legalHoldMembershipUid"]
258+
if not membership_id:
259+
raise UserNotInLegalHoldError(username, matter_id)
260+
235261
sdk.legalhold.remove_from_matter(membership_id)
236262

237263

@@ -241,37 +267,41 @@ def _get_and_print_preservation_policy(sdk, policy_uid):
241267
echo(pformat(json.loads(preservation_policy.text)))
242268

243269

244-
def _get_legal_hold_membership_id_for_user_and_matter(sdk, username, matter_id):
245-
user_id = get_user_id(sdk, username)
246-
memberships = _get_legal_hold_memberships_for_matter(sdk, matter_id, active=True)
247-
for member in memberships:
248-
if member["user"]["userUid"] == user_id:
249-
return member["legalHoldMembershipUid"]
250-
raise UserNotInLegalHoldError(username, matter_id)
251-
252-
253-
def _get_legal_hold_memberships_for_matter(sdk, matter_id, active=True):
270+
def _get_legal_hold_memberships_for_matter(state, sdk, matter_id, active=True):
254271
memberships_generator = sdk.legalhold.get_all_matter_custodians(
255-
legal_hold_uid=matter_id, active=active
272+
matter_id, active=active
256273
)
257-
memberships = [
258-
member
259-
for page in memberships_generator
260-
for member in page["legalHoldMemberships"]
261-
]
274+
if state.profile.api_client_auth == "True":
275+
memberships = [member for page in memberships_generator for member in page]
276+
else:
277+
memberships = [
278+
member
279+
for page in memberships_generator
280+
for member in page["legalHoldMemberships"]
281+
]
262282
return memberships
263283

264284

265-
def _get_all_active_matters(sdk):
266-
matters_generator = sdk.legalhold.get_all_matters()
267-
matters = [
268-
matter
269-
for page in matters_generator
270-
for matter in page["legalHolds"]
271-
if matter["active"]
272-
]
273-
for matter in matters:
274-
matter["creator_username"] = matter["creator"]["username"]
285+
def _get_all_active_matters(state):
286+
matters_generator = state.sdk.legalhold.get_all_matters()
287+
if state.profile.api_client_auth == "True":
288+
matters = [
289+
matter for page in matters_generator for matter in page if matter["active"]
290+
]
291+
for matter in matters:
292+
try:
293+
matter["creator_username"] = matter["creator"]["user"]["email"]
294+
except KeyError:
295+
pass
296+
else:
297+
matters = [
298+
matter
299+
for page in matters_generator
300+
for matter in page["legalHolds"]
301+
if matter["active"]
302+
]
303+
for matter in matters:
304+
matter["creator_username"] = matter["creator"]["username"]
275305
return matters
276306

277307

0 commit comments

Comments
 (0)