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
8 changes: 5 additions & 3 deletions ayon_api/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
SERVER_URL_ENV_KEY,
SERVER_API_ENV_KEY,
)
from .exceptions import FailedServiceInit
from .exceptions import FailedServiceInit, UnauthorizedError
from ._api_helpers.base import _PLACEHOLDER
from .utils import (
NOT_SET,
Expand Down Expand Up @@ -262,8 +262,10 @@ def init_service(
if connect:
print("Connecting to server \"{}\"".format(server_url))
con = GlobalContext.get_server_api_connection()
user = con.get_user()
print("Logged in as user \"{}\"".format(user["name"]))
username = con.access_token_username
if username is None:
raise UnauthorizedError("User is not authorized.")
print("Logged in as user \"{}\"".format(username))


def init_service(*args, **kwargs):
Expand Down
34 changes: 26 additions & 8 deletions ayon_api/server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ class TokenInfo:
token: str | None = None
is_valid: bool | None = None
is_service: bool | None = None
username: str | None = None
unauthorized_response: requests.Response | None = None


Expand Down Expand Up @@ -615,6 +616,24 @@ def access_token(self) -> str | None:
"""
return self._token_info.token

@property
def access_token_username(self) -> str | None:
"""Name of user who owns the access token.

The value is stored on token validation, so it does not require
additional request. It is not affected by 'as_username', use
'get_user' to get user the requests are made as.

Returns:
str | None: Username or None if token is not valid.

"""
if not self.has_valid_token:
return None
if self._token_info.username is None:
self.validate_token()
return self._token_info.username

def is_service_user(self) -> bool:
"""Check if connection is using service API key.

Expand Down Expand Up @@ -925,10 +944,12 @@ def _request_token_user_info(self) -> tuple[UserInfo, bool]:
def _store_token_validation(self, user_info: UserInfo) -> None:
self._token_info.is_valid = user_info.is_valid
self._token_info.unauthorized_response = user_info.response
is_service = None
is_service = username = None
if user_info.is_valid:
is_service = user_info.is_service
username = user_info.username
self._token_info.is_service = is_service
self._token_info.username = username

def set_token(self, token: str | None) -> None:
self._reset_token()
Expand All @@ -943,6 +964,7 @@ def reset_token(self) -> None:
def _reset_token(self) -> None:
self._token_info.token = None
self._token_info.is_service = None
self._token_info.username = None
self._token_info.is_valid = None
self._token_info.unauthorized_response = None
self.close_session()
Expand Down Expand Up @@ -1440,13 +1462,7 @@ def login(

"""
if self.has_valid_token:
try:
user_info = self.get_user(fill_attrib=False)
except UnauthorizedError:
user_info = {}

current_username = user_info.get("name")
if current_username == username:
if self.access_token_username == username:
self.close_session()
if create_session:
self.create_session()
Expand Down Expand Up @@ -1478,6 +1494,8 @@ def login(
# Service token can't be obtained by login, so it is not
# service token
self._token_info.is_service = False
user = response.data.get("user") or {}
self._token_info.username = user.get("name")

if not self.has_valid_token:
raise AuthenticationError("Invalid credentials")
Expand Down
2 changes: 2 additions & 0 deletions ayon_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ class UserInfo:
"""User information."""
is_valid: bool = False
is_service: bool = False
username: str | None = None
response: requests.Response | None = None


Expand Down Expand Up @@ -779,6 +780,7 @@ def get_user_info_by_token(
response=response,
)
if output.is_valid:
output.username = response.json().get("name")
break
return output

Expand Down
20 changes: 20 additions & 0 deletions tests/test_token_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def test_user_info_by_token(token, expected_requests, is_service):
)
assert user_info.is_valid
assert user_info.is_service is is_service
assert user_info.username == "user"
assert server.requests == expected_requests


Expand All @@ -138,6 +139,25 @@ def test_connection_validates_with_single_request(server, token, auth):
assert con._session is server.sessions[0]


@pytest.mark.parametrize(
"token, auth", [(USER_TOKEN, "bearer"), (API_KEY, "x-api-key")]
)
def test_access_token_username_from_validation(server, token, auth):
con = ServerAPI(URL, token)

assert con.access_token_username == "user"
assert server.requests == [("/api/users/me", auth)]

con.reset_token()
assert con.access_token_username is None


def test_invalid_token_has_no_username(server):
con = ServerAPI(URL, "d" * 32)

assert con.access_token_username is None


def test_global_connection_validates_with_single_request(
server, monkeypatch
):
Expand Down
Loading