Skip to content
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
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: compiler-admin with Arguments",
"type": "debugpy",
"request": "launch",
"program": "compiler_admin/main.py",
"console": "integratedTerminal",
"args": "${command:pickArgs}"
}
]
}
8 changes: 6 additions & 2 deletions compiler_admin/commands/user/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import click

from compiler_admin.commands.user.alumni import alumni
from compiler_admin.commands.user.backupcodes import backupcodes
from compiler_admin.commands.user.convert import convert
from compiler_admin.commands.user.create import create
from compiler_admin.commands.user.deactivate import deactivate
from compiler_admin.commands.user.delete import delete
from compiler_admin.commands.user.offboard import offboard
from compiler_admin.commands.user.reset import reset
from compiler_admin.commands.user.reactivate import reactivate
from compiler_admin.commands.user.restore import restore
from compiler_admin.commands.user.signout import signout

Expand All @@ -18,11 +20,13 @@ def user():
pass


user.add_command(alumni)
user.add_command(backupcodes)
user.add_command(convert)
user.add_command(create)
user.add_command(deactivate)
user.add_command(delete)
user.add_command(offboard)
user.add_command(reactivate)
user.add_command(reset)
user.add_command(restore)
user.add_command(signout)
24 changes: 24 additions & 0 deletions compiler_admin/commands/user/backupcodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import click

from compiler_admin import RESULT_FAILURE
from compiler_admin.services.google import (
get_backup_codes,
user_account_name,
user_exists,
)


@click.command()
@click.argument("username")
def backupcodes(username: str, **kwargs):
"""
Get backup codes for the user, creating a new set if needed.
"""
account = user_account_name(username)

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

backup_codes = get_backup_codes(account)
click.echo(backup_codes)
23 changes: 2 additions & 21 deletions compiler_admin/commands/user/convert.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import click

from compiler_admin import RESULT_FAILURE
from compiler_admin.commands.user.alumni import alumni
from compiler_admin.services.google import (
GROUP_PARTNERS,
GROUP_STAFF,
OU_ALUMNI,
OU_CONTRACTORS,
OU_PARTNERS,
OU_STAFF,
Expand All @@ -19,24 +17,11 @@
)


ACCOUNT_TYPE_OU = {"alumni": OU_ALUMNI, "contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF}
ACCOUNT_TYPE_OU = {"contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF}


@click.command()
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
@click.option(
"-n", "--notify", help="An email address to send the new password notification. Only valid for alumni conversion."
)
@click.option(
"-e",
"--recovery-email",
help="An email address to use as the new recovery email. Only valid for alumni conversion.",
)
@click.option(
"-p",
"--recovery-phone",
help="A phone number to use as the new recovery phone number. Only valid for alumni conversion.",
)
@click.argument("username")
@click.argument("account_type", type=click.Choice(ACCOUNT_TYPE_OU.keys(), case_sensitive=False))
@click.pass_context
Expand All @@ -52,11 +37,7 @@ def convert(ctx: click.Context, username: str, account_type: str, **kwargs):

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

if account_type == "alumni":
# call the alumni command
ctx.forward(alumni)

elif account_type == "contractor":
if account_type == "contractor":
if user_is_partner(account):
remove_user_from_group(account, GROUP_PARTNERS)
remove_user_from_group(account, GROUP_STAFF)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
move_user_ou,
user_account_name,
user_exists,
user_is_deactivated,
)


Expand All @@ -17,34 +18,40 @@
@click.option(
"-e",
"--recovery-email",
default="",
help="An email address to use as the new recovery email. Without a value, clears the recovery email.",
)
@click.option(
"-p",
"--recovery-phone",
default="",
help="A phone number to use as the new recovery phone number. Without a value, clears the recovery phone number.",
)
@click.argument("username")
@click.pass_context
def alumni(
def deactivate(
ctx: click.Context, username: str, force: bool = False, recovery_email: str = "", recovery_phone: str = "", **kwargs
):
"""
Convert a user to a Compiler alumni.
Deactivate (but do not delete) a user.
"""
account = user_account_name(username)

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

if user_is_deactivated(account):
click.echo("User is already deactivated")
raise SystemExit(RESULT_FAILURE)

if not force:
cont = input(f"Convert account to alumni for {account}? (Y/n): ")
cont = input(f"Deactivate account {account}? (Y/n): ")
if not cont.lower().startswith("y"):
click.echo("Aborting conversion.")
click.echo("Aborting deactivation")
raise SystemExit(RESULT_SUCCESS)

click.echo(f"User exists, converting to alumni: {account}")
click.echo(f"User exists, deactivating: {account}")

click.echo("Removing from groups")
CallGAMCommand(("user", account, "delete", "groups"))
Expand All @@ -71,3 +78,5 @@ def alumni(
click.echo("Turning off 2FA")
command = ("user", account, "turnoff2sv")
CallGAMCommand(command)

click.echo(f"User is deactivated: {account}")
2 changes: 1 addition & 1 deletion compiler_admin/commands/user/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@click.command()
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
@click.argument("username")
def delete(username: str, force: bool = False):
def delete(username: str, force: bool = False, **kwargs):
"""
Delete a user account.
"""
Expand Down
24 changes: 12 additions & 12 deletions compiler_admin/commands/user/offboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import click

from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS
from compiler_admin.commands.user.alumni import alumni
from compiler_admin.commands.user.deactivate import deactivate
from compiler_admin.commands.user.delete import delete
from compiler_admin.services.google import (
USER_ARCHIVE,
Expand All @@ -16,19 +16,16 @@

@click.command()
@click.option("-a", "--alias", help="Another account to assign username as an alias.")
@click.option("-d", "--delete", "_delete", is_flag=True, help="Also delete the account.")
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
@click.option("-n", "--notify", help="An email address to send the new password notification.")
@click.argument("username")
@click.pass_context
def offboard(ctx: click.Context, username: str, alias: str = "", force: bool = False, **kwargs):
"""Fully offboard a user from Compiler.

Args:
username (str): The user account to offboard.
def offboard(ctx: click.Context, username: str, alias: str = "", _delete: bool = False, force: bool = False, **kwargs):
"""
Fully offboard a user from Compiler.

alias (str): [Optional] account to assign username as an alias
Returns:
A value indicating if the operation succeeded or failed.
Deactivate, back up email, transfer Calendar/Drive, and optionally delete.
"""
account = user_account_name(username)

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

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

# call the alumni command
ctx.forward(alumni)
# call the deactivate command
ctx.forward(deactivate)

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

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

# call the delete command
ctx.forward(delete)
if _delete:
ctx.forward(delete)

if alias_account:
click.echo(f"Adding an alias to account: {alias_account}")
Expand Down
93 changes: 93 additions & 0 deletions compiler_admin/commands/user/reactivate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import click

from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS
from compiler_admin.commands.user.backupcodes import backupcodes
from compiler_admin.commands.user.reset import reset
from compiler_admin.services.google import (
GROUP_STAFF,
GROUP_TEAM,
OU_CONTRACTORS,
OU_STAFF,
CallGAMCommand,
add_user_to_group,
move_user_ou,
user_account_name,
user_exists,
user_is_deactivated,
)


@click.command()
@click.option("-f", "--force", is_flag=True, help="Don't ask for confirmation.")
@click.option("-n", "--notify", help="An email address to send the new password notification.")
@click.option(
"-e",
"--recovery-email",
default="",
help="An email address to use as the new recovery email.",
)
@click.option(
"-p",
"--recovery-phone",
default="",
help="A phone number to use as the new recovery phone number.",
)
@click.option("-s", "--staff", is_flag=True, help="Reactivate the user as a staff member. The default is contractor.")
@click.argument("username")
@click.pass_context
def reactivate(
ctx: click.Context,
username: str,
force: bool = False,
recovery_email: str = "",
recovery_phone: str = "",
staff: bool = False,
**kwargs,
):
"""
Reactivate a previously deactivated user.
"""
account = user_account_name(username)

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

if not user_is_deactivated(account):
click.echo("User is not deactivated, cannot reactivate")
raise SystemExit(RESULT_FAILURE)

if not force:
cont = input(f"Reactivate account {account}? (Y/n): ")
if not cont.lower().startswith("y"):
click.echo("Aborting reactivation")
raise SystemExit(RESULT_SUCCESS)

click.echo(f"User exists, reactivating: {account}")

click.echo(f"Adding to group: {GROUP_TEAM}")
add_user_to_group(account, GROUP_TEAM)

if staff:
click.echo(f"Moving to OU: {OU_STAFF}")
move_user_ou(account, OU_STAFF)
click.echo(f"Adding to group: {GROUP_STAFF}")
add_user_to_group(account, GROUP_STAFF)
else:
click.echo(f"Moving to OU: {OU_CONTRACTORS}")
move_user_ou(account, OU_CONTRACTORS)

# reset password, sign out
ctx.forward(reset)

click.echo("Update user profile info")
profile = dict(recoveryemail=recovery_email, recoveryphone=recovery_phone)
profile = {k: v for k, v in profile.items() if v}
for prop, val in profile.items():
command = ("update", "user", account, prop, val)
CallGAMCommand(command)

# get the user's backup codes
ctx.forward(backupcodes)

click.echo(f"User is reactivated: {account}")
Loading
Loading