Skip to content
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

feat: SSO Improvement - alter user_sessions table to include access token, implement CRUD ops, GET, POST, PATCH APIs and det token CLIs #9867

Merged
merged 57 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
75fbfda
create table and crud ops
ShreyaLnuHpe Aug 26, 2024
fee92d7
add crud ops in go
ShreyaLnuHpe Aug 28, 2024
5eaa712
integ test wip
ShreyaLnuHpe Aug 28, 2024
0e78f52
integ testing
ShreyaLnuHpe Aug 28, 2024
a93a64a
based on comments
ShreyaLnuHpe Aug 28, 2024
4a85904
API structure
ShreyaLnuHpe Aug 29, 2024
a8031a3
lint go corrections
ShreyaLnuHpe Aug 30, 2024
a8a5ea9
typo
ShreyaLnuHpe Aug 30, 2024
1f689b1
lint check
ShreyaLnuHpe Aug 30, 2024
33c874f
changes per swagger
ShreyaLnuHpe Aug 30, 2024
99b79cf
lint proto
ShreyaLnuHpe Aug 30, 2024
10e2648
add admin nonadmin user auth
ShreyaLnuHpe Aug 31, 2024
feb7d09
add E2E test for POST API
ShreyaLnuHpe Sep 4, 2024
c9ea59b
add GET CRUD ops
ShreyaLnuHpe Sep 4, 2024
293a21d
add GET API structure
ShreyaLnuHpe Sep 4, 2024
a139398
add test-intg for GET API
ShreyaLnuHpe Sep 4, 2024
bc3b7d9
based on comments
ShreyaLnuHpe Sep 6, 2024
cd7dbeb
DELETE API and tests
ShreyaLnuHpe Sep 6, 2024
2c50362
based on brainstoring and building op1
ShreyaLnuHpe Sep 10, 2024
607f0e4
remove llt table
ShreyaLnuHpe Sep 10, 2024
d432aa0
mased on comments
ShreyaLnuHpe Sep 12, 2024
736a9f5
add CLIs
ShreyaLnuHpe Sep 16, 2024
6000477
chore: add revoke token permission
corban-beaird Sep 16, 2024
47d87af
chore: drop WorkspaceCreator from set of roles able to revoke tokens
corban-beaird Sep 16, 2024
7bae276
chore: add rbac token permissions
ShreyaLnuHpe Sep 17, 2024
c2427ba
chore: correct check errors & remove token filter from GetALL
ShreyaLnuHpe Sep 17, 2024
6d9c3ff
refactor: clean up migration for readability
corban-beaird Sep 17, 2024
825af40
Merge branch 'main' into shreya/createTable
ShreyaLnuHpe Sep 18, 2024
05e05a5
fix: lint errors
ShreyaLnuHpe Sep 18, 2024
152f3d3
feat: token description in CLI and authentication using token
corban-beaird Sep 18, 2024
13c4a57
chore: clean up linter issues
corban-beaird Sep 18, 2024
3515ffb
changes to revoke in postgres
ShreyaLnuHpe Sep 18, 2024
a28fbf7
feat: added API support for token description updates & unified revok…
corban-beaird Sep 18, 2024
4d9f9f8
chore: clean up logging
corban-beaird Sep 18, 2024
6f305b5
chore: update table, update cli, getAccessToken, getAllAccessTokens, …
ShreyaLnuHpe Sep 19, 2024
ef03c2a
chore: pretty print cli and unify post api and permissions
ShreyaLnuHpe Sep 19, 2024
c49657e
feat: describe cli to take multiple usernames
ShreyaLnuHpe Sep 20, 2024
c0262e6
fix: authentication of multi login, add tokenType while rendering
ShreyaLnuHpe Sep 24, 2024
f67cffe
fix: change name from long-lived to access token and RBAC access only…
ShreyaLnuHpe Sep 24, 2024
784e69d
chore: refactor Get and Create AccessToken API, add filter option, up…
ShreyaLnuHpe Oct 2, 2024
3fd0e76
chore: Revoke Access Tokens when a User is Deactivated (#10013)
ShreyaLnuHpe Oct 4, 2024
7bd852b
chore: refactor access tokens CLI commands (#10012)
ShreyaLnuHpe Oct 10, 2024
ae46bb6
Merge branch 'main' into shreya/createTable
ShreyaLnuHpe Oct 11, 2024
6d70ed4
chore: changes per merge with main
ShreyaLnuHpe Oct 11, 2024
5d887c4
chore: un-nest CLIs, APIs, permissions and DB layer (#10041)
ShreyaLnuHpe Oct 11, 2024
bde92d2
chore: remove unwanted part from user.py
ShreyaLnuHpe Oct 11, 2024
dcbf014
chore: add release note, minor changes per comments
ShreyaLnuHpe Oct 11, 2024
95e60f2
chore: changes per discussion with ModelDev
ShreyaLnuHpe Oct 14, 2024
b385f1e
remove commented lines
ShreyaLnuHpe Oct 14, 2024
51e1e9d
chore: fix as per comments
ShreyaLnuHpe Oct 15, 2024
72596e7
Merge branch 'main' into shreya/createTable
ShreyaLnuHpe Oct 15, 2024
1b8f9e6
chore: add proto files
ShreyaLnuHpe Oct 15, 2024
23b265a
chore: minor fixes
ShreyaLnuHpe Oct 15, 2024
6af1292
chore: change docs and expiration days
ShreyaLnuHpe Oct 15, 2024
0370c6b
chore: changes as per comments
ShreyaLnuHpe Oct 17, 2024
2e464a3
fix: consider till seconds during lifespan comparison
ShreyaLnuHpe Oct 17, 2024
e386613
chore: create `TokenCreator` role with permissions to VIEW / CREATE /…
ShreyaLnuHpe Oct 17, 2024
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
176 changes: 173 additions & 3 deletions harness/determined/cli/user.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import argparse
import collections
import functools
import getpass
from typing import Any, List
import json
from typing import Any, List, Sequence

from determined import cli
from determined.cli import errors, render
from determined.common import api
from determined.common import api, util
from determined.common.api import authentication, bindings
from determined.experimental import client

TOKEN_HEADERS = [
"ID",
"User ID",
"Description",
"Created At",
"Expires At",
"Revoked",
"Token Type",
]

FullUser = collections.namedtuple(
"FullUser",
[
Expand Down Expand Up @@ -71,6 +83,19 @@ def log_in_user(args: argparse.Namespace) -> None:
else:
username = args.username

if args.token is not None:
token_store = authentication.TokenStore(args.master)
token_store.set_token(username, args.token)
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
token_store.set_active(username)

d = client.Determined._from_session(
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
api.Session(master=args.master, username=username, token=args.token, cert=cli.cert)
)
user = d.whoami()
if user.username != username:
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
raise errors.CliError("Token does not match the provided username")
return
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved

message = "Password for user '{}': ".format(username)
password = getpass.getpass(message)

Expand Down Expand Up @@ -218,6 +243,110 @@ def edit(args: argparse.Namespace) -> None:
raise errors.CliError("No field provided. Use 'det user edit -h' for usage.")


def render_token_info(token_info: Sequence[bindings.v1TokenInfo]) -> None:
values = []
for ti in token_info:
value = [
ti.id,
ti.userId,
ti.description,
ti.createdAt,
ti.expiry,
ti.revoked,
ti.tokenType,
azhou-determined marked this conversation as resolved.
Show resolved Hide resolved
]
values.append(value)
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
render.tabulate_or_csv(TOKEN_HEADERS, values, False)


def print_token_info(data: Sequence[bindings.v1TokenInfo], args: argparse.Namespace) -> None:
if data:
json_data = [elem.to_json() for elem in data]
if len(data) == 1:
json_data = json_data[0]
else:
json_data = {}
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
if args.yaml:
print(util.yaml_safe_dump(json_data, default_flow_style=False))
else:
render.print_json(json_data)


def describe_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
result = []
for arg in args.username:
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
userID = bindings.get_GetUserByUsername(session=sess, username=arg).user.id
resp = bindings.get_GetAccessToken(session=sess, userId=userID)
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
result.append(resp.tokenInfo)
render_token_info(result)


def list_tokens(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
resp = bindings.get_GetAllAccessTokens(sess, includeInactive=args.all)
if args.json or args.yaml:
print_token_info(resp.tokenInfo, args)
return
render_token_info(resp.tokenInfo)


def revoke_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
try:
request = bindings.v1PatchAccessTokenRequest(
tokenId=args.token_id, description=None, setRevoked=True
)
resp = bindings.patch_PatchAccessToken(sess, body=request, tokenId=args.token_id)
print(json.dumps(resp.to_json(), indent=2))
except api.errors.NotFoundException:
raise errors.CliError("Token not found")
print("Successfully updated token with ID: {}".format(args.token_id))


def create_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
current_user, token_user = sess.username, args.username
request, handler = None, None
if token_user is None or token_user == current_user:
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
user_id = bindings.get_GetUserByUsername(session=sess, username=current_user).user.id
else:
user_id = bindings.get_GetUserByUsername(session=sess, username=token_user).user.id
request = bindings.v1PostAccessTokenRequest(
lifespan=args.lifespan, userId=user_id, description=args.description
)
handler = functools.partial(bindings.post_PostAccessToken, userId=user_id)
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
resp = handler(session=sess, body=request).to_json()

outputString = None
if args.yaml:
outputString = util.yaml_safe_dump(resp, default_flow_style=False)
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
elif args.json:
outputString = json.dumps(resp, indent=2)
else:
outputString = resp["token"]

if args.file_path:
with open(args.file_path, "w") as file:
file.write(outputString)
else:
print(outputString)


def update_token(args: argparse.Namespace) -> None:
Copy link
Contributor

@azhou-determined azhou-determined Sep 25, 2024

Choose a reason for hiding this comment

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

i don't really see the value in this functionality, tbh. the only thing you can do is update the description.

if we were to enable updating other fields on the token in the future, we should go with:
det user edit-token <token-id> --description=description

but currently with just description, not sure it's worth having as a separate CLI command

sess = cli.setup_session(args)
try:
if args.description and args.token_id:
request = bindings.v1PatchAccessTokenRequest(
tokenId=args.token_id, description=args.description, setRevoked=False
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
)
resp = bindings.patch_PatchAccessToken(sess, body=request, tokenId=args.token_id)
print(json.dumps(resp.to_json(), indent=2))
except api.errors.NotFoundException:
raise errors.CliError("Token not found")
print("Successfully updated token with ID: {}".format(args.token_id))


AGENT_USER_GROUP_ARGS = [
cli.Arg("--agent-uid", type=int, help="UID on the agent to run tasks as"),
cli.Arg("--agent-user", help="user on the agent to run tasks as"),
Expand All @@ -238,7 +367,8 @@ def edit(args: argparse.Namespace) -> None:
),
], is_default=True),
cli.Cmd("login", log_in_user, "log in user", [
cli.Arg("username", nargs="?", default=None, help="name of user to log in as")
cli.Arg("username", nargs="?", default=None, help="name of user to log in as"),
cli.Arg("--token", default=None, help="token to use for authentication"),
]),
cli.Cmd("rename", rename, "change username for user", [
cli.Arg(
Expand Down Expand Up @@ -313,6 +443,46 @@ def edit(args: argparse.Namespace) -> None:
help="grant/remove user admin permissions",
),
]),
cli.Cmd("token", None, "manage access tokens", [
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
cli.Cmd("describe", describe_token, "describe token info", [
cli.Arg("username", nargs=argparse.ONE_OR_MORE, default=None,
help="name of user to describe token"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
),
]),
cli.Cmd("list ls", list_tokens, "list all active access tokens", [
cli.Arg("--all", "-a", action="store_true", default=None,
help="list all access tokens, including revoked & expired tokens"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
cli.Cmd("revoke", revoke_token, "revoke token", [
cli.Arg("token_id", help="revoke given access token"),
]),
cli.Cmd("create", create_token, "create token", [
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
cli.Arg("--username", type=str, help="name of user to create token"),
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
cli.Arg("--lifespan", type=str, help="give expiry lifespan"),
cli.Arg("--description", type=str, default=None, help="description of new token"),
cli.Arg("--file-path", type=str, help="write token to file"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
cli.Cmd("update", update_token, "update token info", [
cli.Arg("token_id", help="revoke given access token"),
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
cli.Arg("description", type=str, default=None,
help="description of token"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
]),
])
] # type: List[Any]

Expand Down
Loading
Loading