Skip to content

Feature/api clients #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta

### Added

- Support for Code42 API clients.
- You can create a new profile with API client authentication using `code42 profile create-api-client`
- Or, update your existing profile to use API clients with `code42 update --api-client-id <id> --secret <secret>`
- When using API client authentication, changes to the following `legal-hold` commands:
- `code42 legal-hold list` - Change in response shape.
- `code42 legal-hold show` - Change in response shape.
- `code42 legal-hold search-events` - **Not available.**
- New commands to view details for user risk profiles:
- `code42 users list-risk-profiles`
- `code42 users show-risk-profile`
Expand Down
17 changes: 16 additions & 1 deletion docs/userguides/profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
Use the [code42 profile](../commands/profile.md) set of commands to establish the Code42 environment you're working
within and your user information.

First, create your profile:
## User token authentication

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

## API client authentication

Once you've generated an API Client in your Code42 console, use the following command to create your profile with API client authentication:
```bash
code42 profile create-api-client --name MY_API_CLIENT_PROFILE --server example.authority.com --api-client-id "key-42" --secret "code42%api%client%secret"
```

```{eval-rst}
.. note:: Remember to wrap your API client secret with single quotes to avoid issues with bash expansion and special characters.
```

## View profiles

You can add multiple profiles with different names and the change the default profile with the `use` command:

```bash
Expand Down
1 change: 1 addition & 0 deletions docs/userguides/v2apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ V1 file event APIs were marked deprecated in May 2022 and will be no longer be s
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.

Use `code42 profile show` to check the status of this setting on your profile:

```bash
% code42 profile update --use-v2-file-events True

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"keyrings.alt==3.2.0",
"ipython==7.16.3",
"pandas>=1.1.3",
"py42>=1.24.0",
"py42>=1.26.0",
],
extras_require={
"dev": [
Expand Down
98 changes: 64 additions & 34 deletions src/code42cli/cmds/legal_hold.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import click
from click import echo
from click import style

from code42cli.bulk import generate_template_cmd_factory
from code42cli.bulk import run_bulk_process
Expand Down Expand Up @@ -89,7 +90,7 @@ def add_user(state, matter_id, username):
@sdk_options()
def remove_user(state, matter_id, username):
"""Release a custodian from a legal hold matter."""
_remove_user_from_legal_hold(state.sdk, matter_id, username)
_remove_user_from_legal_hold(state, state.sdk, matter_id, username)


@legal_hold.command("list")
Expand All @@ -98,7 +99,7 @@ def remove_user(state, matter_id, username):
def _list(state, format=None):
"""Fetch existing legal hold matters."""
formatter = OutputFormatter(format, _MATTER_KEYS_MAP)
matters = _get_all_active_matters(state.sdk)
matters = _get_all_active_matters(state)
if matters:
formatter.echo_formatted_list(matters)

Expand All @@ -120,14 +121,21 @@ def _list(state, format=None):
def show(state, matter_id, include_inactive=False, include_policy=False):
"""Display details of a given legal hold matter."""
matter = _check_matter_is_accessible(state.sdk, matter_id)
matter["creator_username"] = matter["creator"]["username"]

if state.profile.api_client_auth == "True":
try:
matter["creator_username"] = matter["creator"]["user"]["email"]
except KeyError:
pass
else:
matter["creator_username"] = matter["creator"]["username"]
matter = json.loads(matter.text)

# if `active` is None then all matters (whether active or inactive) are returned. True returns
# only those that are active.
active = None if include_inactive else True
memberships = _get_legal_hold_memberships_for_matter(
state.sdk, matter_id, active=active
state, state.sdk, matter_id, active=active
)
active_usernames = [
member["user"]["username"] for member in memberships if member["active"]
Expand Down Expand Up @@ -161,6 +169,15 @@ def show(state, matter_id, include_inactive=False, include_policy=False):
@sdk_options()
def search_events(state, matter_id, event_type, begin, end, format):
"""Tools for getting legal hold event data."""
if state.profile.api_client_auth == "True":
echo(
style(
"WARNING: This method is unavailable with API Client Authentication.",
fg="red",
),
err=True,
)

formatter = OutputFormatter(format, _EVENT_KEYS_MAP)
events = _get_all_events(state.sdk, matter_id, begin, end)
if event_type:
Expand Down Expand Up @@ -214,7 +231,7 @@ def remove(state, csv_rows):
sdk = state.sdk

def handle_row(matter_id, username):
_remove_user_from_legal_hold(sdk, matter_id, username)
_remove_user_from_legal_hold(state, sdk, matter_id, username)

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


def _remove_user_from_legal_hold(sdk, matter_id, username):
def _remove_user_from_legal_hold(state, sdk, matter_id, username):
_check_matter_is_accessible(sdk, matter_id)
membership_id = _get_legal_hold_membership_id_for_user_and_matter(
sdk, username, matter_id

user_id = get_user_id(sdk, username)
memberships = _get_legal_hold_memberships_for_matter(
state, sdk, matter_id, active=True
)
membership_id = None
for member in memberships:
if member["user"]["userUid"] == user_id:
membership_id = member["legalHoldMembershipUid"]
if not membership_id:
raise UserNotInLegalHoldError(username, matter_id)

sdk.legalhold.remove_from_matter(membership_id)


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


def _get_legal_hold_membership_id_for_user_and_matter(sdk, username, matter_id):
user_id = get_user_id(sdk, username)
memberships = _get_legal_hold_memberships_for_matter(sdk, matter_id, active=True)
for member in memberships:
if member["user"]["userUid"] == user_id:
return member["legalHoldMembershipUid"]
raise UserNotInLegalHoldError(username, matter_id)


def _get_legal_hold_memberships_for_matter(sdk, matter_id, active=True):
def _get_legal_hold_memberships_for_matter(state, sdk, matter_id, active=True):
memberships_generator = sdk.legalhold.get_all_matter_custodians(
legal_hold_uid=matter_id, active=active
matter_id, active=active
)
memberships = [
member
for page in memberships_generator
for member in page["legalHoldMemberships"]
]
if state.profile.api_client_auth == "True":
memberships = [member for page in memberships_generator for member in page]
else:
memberships = [
member
for page in memberships_generator
for member in page["legalHoldMemberships"]
]
return memberships


def _get_all_active_matters(sdk):
matters_generator = sdk.legalhold.get_all_matters()
matters = [
matter
for page in matters_generator
for matter in page["legalHolds"]
if matter["active"]
]
for matter in matters:
matter["creator_username"] = matter["creator"]["username"]
def _get_all_active_matters(state):
matters_generator = state.sdk.legalhold.get_all_matters()
if state.profile.api_client_auth == "True":
matters = [
matter for page in matters_generator for matter in page if matter["active"]
]
for matter in matters:
try:
matter["creator_username"] = matter["creator"]["user"]["email"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail if the matter was created via an API client, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the conditions for the field being populated are (but that would make sense). This creator user email field isn't even included on the response documentation and isn't on every matter, but it does exist and is populated on some of them.

except KeyError:
pass
else:
matters = [
matter
for page in matters_generator
for matter in page["legalHolds"]
if matter["active"]
]
for matter in matters:
matter["creator_username"] = matter["creator"]["username"]
return matters


Expand Down
Loading