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 54 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
25 changes: 25 additions & 0 deletions docs/release-notes/api-cli-access-token.rst
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
:orphan:

**New Features**

- API/CLI: Add support for access tokens. Add the ability create and administer access tokens for
users to authenticate in automated workflows. Users can define the lifespan of these tokens,
making it easier to securely authenticate and run processes. This feature enhances automation
while maintaining strong security protocols by allowing tighter control over token usage and
expiration.

- CLI:

- ``det token create``: Create a new access token.
- ``det token login``: Sign in with an access token.
- ``det token edit``: Update an access token's description.
- ``det token list``: List all active access tokens, with options for displaying revoked
tokens.
- ``det token describe``: Show details of specific access tokens.
- ``det token revoke``: Revoke an access token.

- API:

- ``POST /api/v1/tokens``: Create a new access token.
- ``GET /api/v1/tokens``: Retrieve a list of access tokens.
- ``PATCH /api/v1/tokens/{token_id}``: Edit an existing access token.
2 changes: 2 additions & 0 deletions harness/determined/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
task,
template,
tensorboard,
token,
top_arg_descriptions,
trial,
user,
Expand Down Expand Up @@ -165,6 +166,7 @@ def render_sequence(sequence: List[str]) -> str:
+ oauth.args_description
+ dev.args_description
+ config_policies.args_description
+ token.args_description
)


Expand Down
200 changes: 200 additions & 0 deletions harness/determined/cli/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import argparse
import json
from typing import Any, List, Sequence

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

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


def render_token_info(token_info: Sequence[bindings.v1TokenInfo]) -> None:
values = [
[t.id, t.userId, t.description, t.createdAt, t.expiry, t.revoked, t.tokenType]
for t in token_info
]
render.tabulate_or_csv(TOKEN_HEADERS, values, False)


def describe_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
try:
resp = bindings.get_GetAccessTokens(session=sess, tokenIds=args.token_id)
if args.json or args.yaml:
json_data = [t.to_json() for t in resp.tokenInfo]
if args.json:
render.print_json(json_data)
else:
print(util.yaml_safe_dump(json_data, default_flow_style=False))
else:
render_token_info(resp.tokenInfo)
except api.errors.APIException as e:
raise errors.CliError(f"Caught APIException: {str(e)}")
except Exception as e:
raise errors.CliError(f"Error fetching tokens: {e}")


def list_tokens(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
try:
username = args.username if args.username else None
show_inactive = True if args.show_inactive else False
resp = bindings.get_GetAccessTokens(sess, username=username, showInactive=show_inactive)
if args.json or args.yaml:
json_data = [t.to_json() for t in resp.tokenInfo]
if args.json:
render.print_json(json_data)
else:
print(util.yaml_safe_dump(json_data, default_flow_style=False))
else:
render_token_info(resp.tokenInfo)
except Exception as e:
raise errors.CliError(f"Error fetching tokens: {e}")


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))
print(f"Successfully revoked token {args.token_id}.")
except api.errors.NotFoundException:
raise errors.CliError("Token not found")


def create_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
try:
username = args.username or sess.username
user = bindings.get_GetUserByUsername(session=sess, username=username).user

if user is None or user.id is None:
raise errors.CliError(f"User '{username}' not found or does not have an ID")

# convert days into hours Go duration format
if args.expiration_days:
expiration_in_hours = str(24 * args.expiration_days) + "h"

request = bindings.v1PostAccessTokenRequest(
userId=user.id, lifespan=expiration_in_hours, description=args.description
)
resp = bindings.post_PostAccessToken(sess, body=request).to_json()

output_string = None
if args.yaml:
output_string = util.yaml_safe_dump(resp, default_flow_style=False)
elif args.json:
output_string = json.dumps(resp, indent=2)
else:
output_string = f'TokenID: {resp["tokenId"]}\nAccess-Token: {resp["token"]}'

print(output_string)
except api.errors.APIException as e:
raise errors.CliError(f"Caught APIException: {str(e)}")
except api.errors.NotFoundException as e:
raise errors.CliError(f"Caught NotFoundException: {str(e)}")


def edit_token(args: argparse.Namespace) -> None:
sess = cli.setup_session(args)
try:
if args.token_id:
request = bindings.v1PatchAccessTokenRequest(
tokenId=args.token_id,
description=args.description if args.description else None,
setRevoked=False,
)
resp = bindings.patch_PatchAccessToken(sess, body=request, tokenId=args.token_id)
print(json.dumps(resp.to_json(), indent=2))
print(f"Successfully updated token with ID: {args.token_id}.")
except api.errors.APIException as e:
raise errors.CliError(f"Caught APIException: {str(e)}")
except api.errors.NotFoundException:
raise errors.CliError("Token not found")


def login_with_token(args: argparse.Namespace) -> None:
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
try:
unauth_session = api.UnauthSession(master=args.master, cert=cli.cert)
auth_headers = {"Authorization": f"Bearer {args.token}"}
user_data = unauth_session.get("/api/v1/me", headers=auth_headers).json()
username = user_data.get("user").get("username")
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved

token_store = authentication.TokenStore(args.master)
token_store.set_token(username, args.token)
token_store.set_active(username)
print(f"Authenticated as {username}.")
except api.errors.APIException as e:
raise errors.CliError(f"Caught APIException: {str(e)}")
except api.errors.UnauthenticatedException as e:
raise errors.CliError(f"Caught UnauthenticatedException: {str(e)}")
except api.errors.NotFoundException as e:
raise errors.CliError(f"Caught NotFoundException: {str(e)}")


# fmt: off

args_description = [
cli.Cmd("token tkn", None, "manage access tokens", [
cli.Cmd("describe", describe_token, "describe token info", [
cli.Arg("token_id", type=int, nargs=argparse.ONE_OR_MORE, default=None,
help="token id(s) specifying access tokens to describe"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
cli.Cmd("list ls", list_tokens, "list access tokens accessible to users", [
cli.Arg("username", type=str, nargs=argparse.OPTIONAL,
help="list access tokens for the given username", default=None),
cli.Arg("--show-inactive", action="store_true", default=None,
help="list all access tokens accessible to the current user"),
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 id"),
]),
cli.Cmd("create", create_token, "create token", [
cli.Arg("username", type=str, nargs=argparse.OPTIONAL,
ShreyaLnuHpe marked this conversation as resolved.
Show resolved Hide resolved
help="name of user to create token", default=None),
cli.Arg("--expiration-days", "-e", type=int,
help="specify the token expiration in days. '-e 2' sets it to 2 days."),
cli.Arg("--description", "-d", type=str, default=None,
help="description of new token"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
cli.Cmd("edit", edit_token, "edit token info", [
cli.Arg("token_id", help="edit given access token info"),
cli.Arg("--description", "-d", type=str, default=None,
help="description of token to edit"),
cli.Group(
cli.output_format_args["json"],
cli.output_format_args["yaml"],
),
]),
cli.Cmd("login", login_with_token, "log in with token", [
cli.Arg("token", help="token to use for authentication", default=None),
]),
])
] # type: List[Any]

# fmt: on
Loading
Loading