-
Notifications
You must be signed in to change notification settings - Fork 368
chore: add SDK functionality for SSO Improvements #10096
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| metrics, | ||
| model, | ||
| oauth2_scim_client, | ||
| token, | ||
| trial, | ||
| user, | ||
| workspace, | ||
|
|
@@ -582,3 +583,48 @@ def stream_trials_validation_metrics( | |
| stacklevel=2, | ||
| ) | ||
| return trial._stream_validation_metrics(self._session, trial_ids) | ||
|
|
||
| def describe_token(self, token_id: int) -> token.AccessToken: | ||
| """ | ||
| Get the :class:`~determined.experimental.Token` representing the | ||
| token info with the provided token ID. | ||
| """ | ||
| resp = bindings.get_GetAccessTokens( | ||
| session=self._session, tokenIds=[token_id], showInactive=True | ||
| ) | ||
| return token.AccessToken._from_bindings(resp.tokenInfo, self._session) | ||
|
|
||
| def describe_tokens(self, token_ids: List[int]) -> token.AccessToken: | ||
| """ | ||
| Get the :class:`~determined.experimental.Token` representing list of | ||
| token info with the provided token IDs. | ||
| """ | ||
| resp = bindings.get_GetAccessTokens( | ||
| session=self._session, tokenIds=token_ids, showInactive=False | ||
| ) | ||
| return token.AccessToken._from_bindings(resp.tokenInfo, self._session) | ||
|
|
||
| def list_tokens( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think all 3 of these new methods: the CLI and SDK roughly overlap in functionality, but the methods shouldn't be an exact replica. in the CLI, we have to be careful about which methods to expose and how because the UI is limited to the command line, so we generally value convenience and modularity. but the SDK has a bit of a different philosophy. it's in code and made for developers, so the methods we expose can be more powerful and more robust. (see the functionality in def list_tokens(
self,
username: Optional[str],
token_ids: Optional[List[int]],
include_inactive: bool,
sort_by: token.TokenSortBy = token.TokenSortBy.NAME,
order_by: OrderBy = OrderBy.ASCENDING,
) -> List[token.Token]:this is nice because it basically mirrors the actual bindings API call, the user isn't limited by separate methods, and the method is representative of the actual query we make in the system. we should also include sort/order as accepted parameters. |
||
| self, username: Optional[str] = None, show_inactive: Optional[bool] = None | ||
| ) -> token.AccessToken: | ||
| """ | ||
| Get the :class:`~determined.experimental.Token` representing list of | ||
| token info with the provided username. | ||
| """ | ||
| resp = bindings.get_GetAccessTokens( | ||
| session=self._session, username=username, showInactive=show_inactive | ||
| ) | ||
| return token.AccessToken._from_bindings(resp.tokenInfo, self._session) | ||
|
|
||
| def create_token( | ||
| self, user_id: int, lifespan: Optional[str] = None, description: Optional[str] = None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's make
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, we've decided to accept days, so let's just make it |
||
| ) -> bindings.v1PostAccessTokenResponse: | ||
| """ | ||
| Get the :`bindings.v1PostAccessTokenResponse` representing the | ||
| token and token ID with the provided user ID. | ||
| """ | ||
| post_create_token = bindings.v1PostAccessTokenRequest( | ||
| userId=user_id, description=description, lifespan=lifespan | ||
| ) | ||
| resp = bindings.post_PostAccessToken(session=self._session, body=post_create_token) | ||
| return resp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import enum | ||
| from typing import List, Optional | ||
|
|
||
| from determined.common import api | ||
| from determined.common.api import bindings | ||
|
|
||
|
|
||
| class TokenType(enum.Enum): | ||
| # UNSPECIFIED is internal to the bound API and is not be exposed to the front end | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this comment seems to be leftover from copy/paste? |
||
| USER_SESSION = bindings.v1TokenType.USER_SESSION.name | ||
| ACCESS_TOKEN = bindings.v1TokenType.ACCESS_TOKEN.name | ||
|
|
||
|
|
||
| class AccessToken: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's just call this |
||
| """ | ||
| A class representing a AccessToken object that contains user session token info and | ||
| access token info. | ||
| It can be obtained from :func:`determined.experimental.client.list_access_tokens` | ||
| Attributes: | ||
| session: HTTP request session. | ||
| token_id: (int) The ID of the access token in user sessions table. | ||
| user_id: (int) Unique ID for the user. | ||
| expiry: (str) Timestamp expires at reported. | ||
| created_at: (str) Timestamp created at reported. | ||
| token_type: (TokenType) Token type of the token. | ||
| revoked: (Mutable, Optional[bool]) The datetime when the token was revoked. | ||
| Null if the token is still active. | ||
| description: (Mutable, Optional[str]) Human-friendly description of token. | ||
| Note: | ||
| Mutable properties may be changed by methods that update these values either automatically | ||
| (eg. `revoke_tokens`, `edit_tokens`) or explicitly with :meth:`reload()`. | ||
| """ | ||
|
|
||
| def __init__(self, token_id: int, session: api.Session): | ||
| self.token_id = token_id | ||
| self._session = session | ||
|
|
||
| self.user_id: Optional[int] = None | ||
| self.expiry: Optional[str] = None | ||
| self.created_at: Optional[str] = None | ||
| self.token_type: Optional[TokenType] = None | ||
| self.revoked: Optional[bool] = None | ||
| self.description: Optional[str] = None | ||
|
|
||
| def _hydrate(self, tokenInfo: bindings.v1TokenInfo) -> None: | ||
| self.user_id = tokenInfo.userId | ||
| self.expiry = tokenInfo.expiry | ||
| self.created_at = tokenInfo.createdAt | ||
| self.token_type = tokenInfo.tokenType | ||
| self.revoked = tokenInfo.revoked if tokenInfo.revoked is not None else False | ||
| self.description = tokenInfo.description if tokenInfo.description is not None else "" | ||
|
|
||
| def reload(self) -> None: | ||
| resp = bindings.get_GetAccessTokens( | ||
| session=self._session, tokenIds=[self.id], showInactive=True | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does |
||
| ).tokenInfo | ||
| self._hydrate(resp[0]) | ||
|
|
||
| def edit_token(self, desc) -> None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's make this |
||
| patch_token_description = bindings.v1PatchAccessTokenRequest( | ||
| tokenId=self.token_id, description=desc | ||
| ) | ||
| bindings.patch_PatchAccessToken( | ||
| self._session, body=patch_token_description, tokenId=self.token_id | ||
| ) | ||
| self.reload() | ||
|
|
||
| def revoke_token(self) -> None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's call this |
||
| patch_revoke_token = bindings.v1PatchAccessTokenRequest( | ||
| tokenId=self.token_id, description=None, setRevoked=True | ||
| ) | ||
| bindings.patch_PatchAccessToken( | ||
| self._session, body=patch_revoke_token, tokenId=self.token_id | ||
| ) | ||
| self.reload() | ||
|
|
||
| def to_json(self): | ||
| return { | ||
| "token_id": self.token_id, | ||
| "user_id": self.user_id, | ||
| "description": self.description, | ||
| "created_at": self.created_at if self.created_at else None, | ||
| "expiry": self.expiry if self.expiry else None, | ||
| "revoked": self.revoked if self.revoked else None, | ||
| "token_type": self.token_type.name | ||
| if isinstance(self.token_type, enum.Enum) | ||
| else self.token_type, | ||
| } | ||
|
|
||
| @classmethod | ||
| def _from_bindings( | ||
| cls, AccessToken_bindings: List[bindings.v1TokenInfo], session: api.Session | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. casing is weird with |
||
| ) -> "AccessToken | List[AccessToken]": | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method should always just return a single |
||
| assert len(AccessToken_bindings) > 0 | ||
|
|
||
| access_token_infos = [] | ||
| for binding in AccessToken_bindings: | ||
| assert binding.token_id | ||
| AccessTokenInfo = cls(session=session, token_id=binding.token_id) | ||
| AccessTokenInfo._hydrate(binding) | ||
| access_token_infos.append(AccessTokenInfo) | ||
|
|
||
| # Return a single instance if only one tokenInfo is provided | ||
| if len(access_token_infos) == 1: | ||
| return access_token_infos | ||
|
|
||
| # Otherwise, return the list of AccessToken instances | ||
| return access_token_infos | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think it'd be great if the
DeterminedSDK client class accepted atoken: Optional[str]parameter as well to be able to use the SDK with these access tokens.