Skip to content

Commit 76ff329

Browse files
authored
Refactor: user interaction (#57)
2 parents 0f8d55f + f343bcc commit 76ff329

22 files changed

+665
-130
lines changed

.vscode/launch.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python Debugger: compiler-admin with Arguments",
9+
"type": "debugpy",
10+
"request": "launch",
11+
"program": "compiler_admin/main.py",
12+
"console": "integratedTerminal",
13+
"args": "${command:pickArgs}"
14+
}
15+
]
16+
}

compiler_admin/commands/user/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import click
22

3-
from compiler_admin.commands.user.alumni import alumni
3+
from compiler_admin.commands.user.backupcodes import backupcodes
44
from compiler_admin.commands.user.convert import convert
55
from compiler_admin.commands.user.create import create
6+
from compiler_admin.commands.user.deactivate import deactivate
67
from compiler_admin.commands.user.delete import delete
78
from compiler_admin.commands.user.offboard import offboard
89
from compiler_admin.commands.user.reset import reset
10+
from compiler_admin.commands.user.reactivate import reactivate
911
from compiler_admin.commands.user.restore import restore
1012
from compiler_admin.commands.user.signout import signout
1113

@@ -18,11 +20,13 @@ def user():
1820
pass
1921

2022

21-
user.add_command(alumni)
23+
user.add_command(backupcodes)
2224
user.add_command(convert)
2325
user.add_command(create)
26+
user.add_command(deactivate)
2427
user.add_command(delete)
2528
user.add_command(offboard)
29+
user.add_command(reactivate)
2630
user.add_command(reset)
2731
user.add_command(restore)
2832
user.add_command(signout)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import click
2+
3+
from compiler_admin import RESULT_FAILURE
4+
from compiler_admin.services.google import (
5+
get_backup_codes,
6+
user_account_name,
7+
user_exists,
8+
)
9+
10+
11+
@click.command()
12+
@click.argument("username")
13+
def backupcodes(username: str, **kwargs):
14+
"""
15+
Get backup codes for the user, creating a new set if needed.
16+
"""
17+
account = user_account_name(username)
18+
19+
if not user_exists(account):
20+
click.echo(f"User does not exist: {account}")
21+
raise SystemExit(RESULT_FAILURE)
22+
23+
backup_codes = get_backup_codes(account)
24+
click.echo(backup_codes)

compiler_admin/commands/user/convert.py

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import click
22

33
from compiler_admin import RESULT_FAILURE
4-
from compiler_admin.commands.user.alumni import alumni
54
from compiler_admin.services.google import (
65
GROUP_PARTNERS,
76
GROUP_STAFF,
8-
OU_ALUMNI,
97
OU_CONTRACTORS,
108
OU_PARTNERS,
119
OU_STAFF,
@@ -19,24 +17,11 @@
1917
)
2018

2119

22-
ACCOUNT_TYPE_OU = {"alumni": OU_ALUMNI, "contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF}
20+
ACCOUNT_TYPE_OU = {"contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF}
2321

2422

2523
@click.command()
2624
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
27-
@click.option(
28-
"-n", "--notify", help="An email address to send the new password notification. Only valid for alumni conversion."
29-
)
30-
@click.option(
31-
"-e",
32-
"--recovery-email",
33-
help="An email address to use as the new recovery email. Only valid for alumni conversion.",
34-
)
35-
@click.option(
36-
"-p",
37-
"--recovery-phone",
38-
help="A phone number to use as the new recovery phone number. Only valid for alumni conversion.",
39-
)
4025
@click.argument("username")
4126
@click.argument("account_type", type=click.Choice(ACCOUNT_TYPE_OU.keys(), case_sensitive=False))
4227
@click.pass_context
@@ -52,11 +37,7 @@ def convert(ctx: click.Context, username: str, account_type: str, **kwargs):
5237

5338
click.echo(f"User exists, converting to: {account_type} for {account}")
5439

55-
if account_type == "alumni":
56-
# call the alumni command
57-
ctx.forward(alumni)
58-
59-
elif account_type == "contractor":
40+
if account_type == "contractor":
6041
if user_is_partner(account):
6142
remove_user_from_group(account, GROUP_PARTNERS)
6243
remove_user_from_group(account, GROUP_STAFF)

compiler_admin/commands/user/alumni.py renamed to compiler_admin/commands/user/deactivate.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
move_user_ou,
99
user_account_name,
1010
user_exists,
11+
user_is_deactivated,
1112
)
1213

1314

@@ -17,34 +18,40 @@
1718
@click.option(
1819
"-e",
1920
"--recovery-email",
21+
default="",
2022
help="An email address to use as the new recovery email. Without a value, clears the recovery email.",
2123
)
2224
@click.option(
2325
"-p",
2426
"--recovery-phone",
27+
default="",
2528
help="A phone number to use as the new recovery phone number. Without a value, clears the recovery phone number.",
2629
)
2730
@click.argument("username")
2831
@click.pass_context
29-
def alumni(
32+
def deactivate(
3033
ctx: click.Context, username: str, force: bool = False, recovery_email: str = "", recovery_phone: str = "", **kwargs
3134
):
3235
"""
33-
Convert a user to a Compiler alumni.
36+
Deactivate (but do not delete) a user.
3437
"""
3538
account = user_account_name(username)
3639

3740
if not user_exists(account):
3841
click.echo(f"User does not exist: {account}")
3942
raise SystemExit(RESULT_FAILURE)
4043

44+
if user_is_deactivated(account):
45+
click.echo("User is already deactivated")
46+
raise SystemExit(RESULT_FAILURE)
47+
4148
if not force:
42-
cont = input(f"Convert account to alumni for {account}? (Y/n): ")
49+
cont = input(f"Deactivate account {account}? (Y/n): ")
4350
if not cont.lower().startswith("y"):
44-
click.echo("Aborting conversion.")
51+
click.echo("Aborting deactivation")
4552
raise SystemExit(RESULT_SUCCESS)
4653

47-
click.echo(f"User exists, converting to alumni: {account}")
54+
click.echo(f"User exists, deactivating: {account}")
4855

4956
click.echo("Removing from groups")
5057
CallGAMCommand(("user", account, "delete", "groups"))
@@ -71,3 +78,5 @@ def alumni(
7178
click.echo("Turning off 2FA")
7279
command = ("user", account, "turnoff2sv")
7380
CallGAMCommand(command)
81+
82+
click.echo(f"User is deactivated: {account}")

compiler_admin/commands/user/delete.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@click.command()
88
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
99
@click.argument("username")
10-
def delete(username: str, force: bool = False):
10+
def delete(username: str, force: bool = False, **kwargs):
1111
"""
1212
Delete a user account.
1313
"""

compiler_admin/commands/user/offboard.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import click
44

55
from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS
6-
from compiler_admin.commands.user.alumni import alumni
6+
from compiler_admin.commands.user.deactivate import deactivate
77
from compiler_admin.commands.user.delete import delete
88
from compiler_admin.services.google import (
99
USER_ARCHIVE,
@@ -16,19 +16,16 @@
1616

1717
@click.command()
1818
@click.option("-a", "--alias", help="Another account to assign username as an alias.")
19+
@click.option("-d", "--delete", "_delete", is_flag=True, help="Also delete the account.")
1920
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
2021
@click.option("-n", "--notify", help="An email address to send the new password notification.")
2122
@click.argument("username")
2223
@click.pass_context
23-
def offboard(ctx: click.Context, username: str, alias: str = "", force: bool = False, **kwargs):
24-
"""Fully offboard a user from Compiler.
25-
26-
Args:
27-
username (str): The user account to offboard.
24+
def offboard(ctx: click.Context, username: str, alias: str = "", _delete: bool = False, force: bool = False, **kwargs):
25+
"""
26+
Fully offboard a user from Compiler.
2827
29-
alias (str): [Optional] account to assign username as an alias
30-
Returns:
31-
A value indicating if the operation succeeded or failed.
28+
Deactivate, back up email, transfer Calendar/Drive, and optionally delete.
3229
"""
3330
account = user_account_name(username)
3431

@@ -49,8 +46,8 @@ def offboard(ctx: click.Context, username: str, alias: str = "", force: bool = F
4946

5047
click.echo(f"User exists, offboarding: {account}")
5148

52-
# call the alumni command
53-
ctx.forward(alumni)
49+
# call the deactivate command
50+
ctx.forward(deactivate)
5451

5552
click.echo("Backing up email")
5653
CallGYBCommand(("--service-account", "--email", account, "--action", "backup"))
@@ -65,11 +62,14 @@ def offboard(ctx: click.Context, username: str, alias: str = "", force: bool = F
6562
CallGAMCommand(("show", "transfers", "olduser", username), stdout=stdout.name, stderr="stdout")
6663
status = " ".join(stdout.readlines())
6764
stdout.seek(0)
65+
click.echo("Transfer complete")
6866

67+
click.echo("Deprovisioning POP/IMAP")
6968
CallGAMCommand(("user", account, "deprovision", "popimap"))
7069

7170
# call the delete command
72-
ctx.forward(delete)
71+
if _delete:
72+
ctx.forward(delete)
7373

7474
if alias_account:
7575
click.echo(f"Adding an alias to account: {alias_account}")
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import click
2+
3+
from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS
4+
from compiler_admin.commands.user.backupcodes import backupcodes
5+
from compiler_admin.commands.user.reset import reset
6+
from compiler_admin.services.google import (
7+
GROUP_STAFF,
8+
GROUP_TEAM,
9+
OU_CONTRACTORS,
10+
OU_STAFF,
11+
CallGAMCommand,
12+
add_user_to_group,
13+
move_user_ou,
14+
user_account_name,
15+
user_exists,
16+
user_is_deactivated,
17+
)
18+
19+
20+
@click.command()
21+
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
22+
@click.option("-n", "--notify", help="An email address to send the new password notification.")
23+
@click.option(
24+
"-e",
25+
"--recovery-email",
26+
default="",
27+
help="An email address to use as the new recovery email.",
28+
)
29+
@click.option(
30+
"-p",
31+
"--recovery-phone",
32+
default="",
33+
help="A phone number to use as the new recovery phone number.",
34+
)
35+
@click.option("-s", "--staff", is_flag=True, help="Reactivate the user as a staff member. The default is contractor.")
36+
@click.argument("username")
37+
@click.pass_context
38+
def reactivate(
39+
ctx: click.Context,
40+
username: str,
41+
force: bool = False,
42+
recovery_email: str = "",
43+
recovery_phone: str = "",
44+
staff: bool = False,
45+
**kwargs,
46+
):
47+
"""
48+
Reactivate a previously deactivated user.
49+
"""
50+
account = user_account_name(username)
51+
52+
if not user_exists(account):
53+
click.echo(f"User does not exist: {account}")
54+
raise SystemExit(RESULT_FAILURE)
55+
56+
if not user_is_deactivated(account):
57+
click.echo("User is not deactivated, cannot reactivate")
58+
raise SystemExit(RESULT_FAILURE)
59+
60+
if not force:
61+
cont = input(f"Reactivate account {account}? (Y/n): ")
62+
if not cont.lower().startswith("y"):
63+
click.echo("Aborting reactivation")
64+
raise SystemExit(RESULT_SUCCESS)
65+
66+
click.echo(f"User exists, reactivating: {account}")
67+
68+
click.echo(f"Adding to group: {GROUP_TEAM}")
69+
add_user_to_group(account, GROUP_TEAM)
70+
71+
if staff:
72+
click.echo(f"Moving to OU: {OU_STAFF}")
73+
move_user_ou(account, OU_STAFF)
74+
click.echo(f"Adding to group: {GROUP_STAFF}")
75+
add_user_to_group(account, GROUP_STAFF)
76+
else:
77+
click.echo(f"Moving to OU: {OU_CONTRACTORS}")
78+
move_user_ou(account, OU_CONTRACTORS)
79+
80+
# reset password, sign out
81+
ctx.forward(reset)
82+
83+
click.echo("Update user profile info")
84+
profile = dict(recoveryemail=recovery_email, recoveryphone=recovery_phone)
85+
profile = {k: v for k, v in profile.items() if v}
86+
for prop, val in profile.items():
87+
command = ("update", "user", account, prop, val)
88+
CallGAMCommand(command)
89+
90+
# get the user's backup codes
91+
ctx.forward(backupcodes)
92+
93+
click.echo(f"User is reactivated: {account}")

0 commit comments

Comments
 (0)