Skip to content

Commit

Permalink
remove legacy authentication mechanism
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Davidyuk <igor.davidyuk@intel.com>
  • Loading branch information
igor-davidyuk committed Apr 22, 2024
1 parent 3ea25b0 commit c931572
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 145 deletions.
154 changes: 26 additions & 128 deletions geti_sdk/http_session/geti_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
INITIAL_HEADERS = {"Upgrade-Insecure-Requests": "1"}
SUCCESS_STATUS_CODES = [200, 201, 202]

AUTHENTICATION_DEX_OLD = "dex-old"
AUTHENTICATION_DEX_NEW = "dex-new"
AUTHENTICATION_CIDAAS = "cidaas"


Expand Down Expand Up @@ -93,13 +91,9 @@ def __init__(
self.use_token = False
else:
self.use_token = True
if self.authentication_service == AUTHENTICATION_DEX_OLD:
# The old token mechanism
access_token = self._acquire_access_token()
self.headers.update({"Authorization": f"Bearer {access_token}"})
else:
# New mechanism, from Geti v1.14
self.headers.update({"x-api-key": f"{server_config.token}"})

# New mechanism, from Geti v1.14
self.headers.update({"x-api-key": f"{server_config.token}"})
self.headers.pop("Connection")

# Get server version
Expand Down Expand Up @@ -128,83 +122,35 @@ def organization_id(self) -> str:
self._organization_id = self._get_organization_id()
return self._organization_id

def _acquire_access_token(self) -> str:
"""
Request an access token from the server, in exchange for the
PersonalAccessToken.
"""
try:
response = self.get_rest_response(
url="service_accounts/access_token",
method="POST",
data={"service_id": self.config.token},
contenttype="json",
allow_reauthentication=False,
include_organization_id=False,
)
except GetiRequestException as error:
if error.status_code == 401:
raise ValueError(
"Token authorization failed. Please make sure to provide a valid "
"Personal Access Token."
)
raise
logging.info(f"Personal access token validated on host {self.config.host}")
return response.get("access_token")

def _follow_login_redirects(self, response: Response, use_legacy_dex: bool) -> str:
def _follow_login_redirects(self, response: Response) -> str:
"""
Recursively follow redirects in the initial login request. Updates the
session._cookies with the cookie and the login uri.
:param response: REST response to follow redirects for
:param use_legacy_dex: True to use the old authentication method, used
for DEX in Geti v1.8 and below. False to use the new method, which
is valid for more recent Geti versions
:return: url to the redirected location
"""
if use_legacy_dex:
if response.status_code in [302, 303]:
redirect_url = response.next.url
redirected = self.get(
redirect_url, allow_redirects=False, **self._proxies
)
proxy_csrf = redirected.cookies.get(CSRF_COOKIE_NAME, None)
if proxy_csrf is None:
proxy_csrf = response.cookies.get(CSRF_COOKIE_NAME, None)
if proxy_csrf is not None:
self._cookies[CSRF_COOKIE_NAME] = proxy_csrf
return self._follow_login_redirects(redirected, use_legacy_dex)
else:
return response.url
else:
if response.status_code in [302, 303]:
return response.next.url
if response.status_code in [302, 303]:
return response.next.url

def _get_initial_login_url(self, use_legacy_dex: bool) -> str:
def _get_initial_login_url(self) -> str:
"""
Retrieve the initial login url by making a request to the login page, and
following the redirects.
:param use_legacy_dex: True to use the old authentication method, used
for DEX in Geti v1.8 and below. False to use the new method, which
is valid for more recent Geti versions
:return: string containing the URL to the login page
"""
if use_legacy_dex:
url = self.config.host
else:
params = (
"client_id=web_ui"
"&redirect_uri=/callback"
"&code_challenge_method=S256"
"&response_type=code"
"&response_mode=query"
"&scope=openid+profile+groups+email+offline_access"
)
url = f"{self.config.host}/dex/auth/regular_users?{params}"
params = (
"client_id=web_ui"
"&redirect_uri=/callback"
"&code_challenge_method=S256"
"&response_type=code"
"&response_mode=query"
"&scope=openid+profile+groups+email+offline_access"
)
url = f"{self.config.host}/dex/auth/regular_users?{params}"
response = self.get(url, allow_redirects=False, **self._proxies)
login_page_url = self._follow_login_redirects(response, use_legacy_dex)
login_page_url = self._follow_login_redirects(response)
return login_page_url

def authenticate(self, verbose: bool = True):
Expand All @@ -216,19 +162,9 @@ def authenticate(self, verbose: bool = True):
if self.logged_in:
logging.info("Already logged in, authentication is skipped")
return
if self.authentication_service == AUTHENTICATION_DEX_OLD:
use_legacy_dex = True
elif self.authentication_service == AUTHENTICATION_DEX_NEW:
use_legacy_dex = False
else:
raise ValueError(
f"Unable to start the authentication process: The Geti server "
f"requires an authentication service that this version of the "
f"geti-sdk does not support: {self.authentication_service}"
)
self.cookies.clear()
try:
login_path = self._get_initial_login_url(use_legacy_dex)
login_path = self._get_initial_login_url()
except requests.exceptions.SSLError as error:
raise requests.exceptions.SSLError(
f"Connection to Intel® Geti™ server at '{self.config.host}' failed, "
Expand All @@ -250,12 +186,8 @@ def authenticate(self, verbose: bool = True):
self.headers.update({"Content-Type": "application/x-www-form-urlencoded"})
if verbose:
logging.info(f"Authenticating on host {self.config.host}...")
if use_legacy_dex:
cookies = {CSRF_COOKIE_NAME: self._cookies.get(CSRF_COOKIE_NAME, None)}
headers = {"Cookie": self._cookies.get(CSRF_COOKIE_NAME, None)}
else:
cookies = {}
headers = self.headers
cookies = {}
headers = self.headers
response = self.post(
url=login_path,
data={"login": self.config.username, "password": self.config.password},
Expand All @@ -264,10 +196,7 @@ def authenticate(self, verbose: bool = True):
allow_redirects=True,
**self._proxies,
)
if use_legacy_dex:
self._handle_legacy_dex_response(response)
else:
self._handle_dex_response(response)
self._handle_dex_response(response)
if verbose:
logging.info("Authentication successful. Cookie received.")
self.logged_in = True
Expand Down Expand Up @@ -515,16 +444,11 @@ def _handle_error_response(
logging.info("Authentication complete.")

else:
if self.authentication_service == AUTHENTICATION_DEX_OLD:
access_token = self._acquire_access_token()
logging.info("New bearer token obtained.")
self.headers.update({"Authorization": f"Bearer {access_token}"})
else:
raise ValueError(
"Authentication via your personal access token has failed, "
"most likely the token has expired. Please verify that you "
"have provided a valid token."
)
raise ValueError(
"Authentication via your personal access token has failed, "
"most likely the token has expired. Please verify that you "
"have provided a valid token."
)

retry_request = True

Expand Down Expand Up @@ -588,31 +512,6 @@ def base_url(self) -> str:
"""
return f"{self.config.base_url}organizations/{self.organization_id}/"

@property
def authentication_service(self) -> str:
"""
Return the type of authentication service used by the server to which
the GetiSession is tied.
"""
if self._auth_service is None:
deployment_config_response = self.request(
url=f"{self.config.host}/deployment-config.json",
method="GET",
**self._proxies,
)
try:
authentication_info = deployment_config_response.json().get(
"auth", {"type": "dex"}
)
except requests.exceptions.JSONDecodeError:
self._auth_service = AUTHENTICATION_DEX_OLD
return self._auth_service
if authentication_info.get("type") == "dex":
self._auth_service = AUTHENTICATION_DEX_NEW
else:
self._auth_service = AUTHENTICATION_CIDAAS
return self._auth_service

def _get_organization_id(self) -> str:
"""
Return the organization ID associated with the user and host information configured
Expand Down Expand Up @@ -689,7 +588,6 @@ def _handle_dex_response(self, response: Response) -> None:
"error_code": response.reason,
},
)
# state = login_path.split("state=")[1].split("&")[0]
data = {
"grant_type": "authorization_code",
"redirect_uri": "/callback",
Expand Down
17 changes: 0 additions & 17 deletions tests/pre-merge/unit/test_geti_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,9 @@ def test_exception_flow_init(
):
# Arrange
mocker.patch("geti_sdk.geti.GetiSession", new=fxt_mocked_session_factory)
mock_acquire_token = mocker.patch(
"geti_sdk.http_session.geti_session.GetiSession._acquire_access_token",
return_value=DUMMY_TOKEN,
)
mock_get_workspace_id = mocker.patch(
"geti_sdk.geti.get_default_workspace_id", return_value=1
)
mock_authentication_service = mocker.patch(
"geti_sdk.http_session.geti_session.GetiSession.authentication_service",
return_value="dex-old",
new_callable=mocker.PropertyMock,
)

# Act and assert
# host is None and server_config is None
Expand All @@ -64,8 +55,6 @@ def test_exception_flow_init(
password=DUMMY_PASSWORD,
)
assert isinstance(geti.session.config, ServerTokenConfig)
mock_acquire_token.assert_called_once()
mock_authentication_service.assert_called_once()
mock_get_workspace_id.assert_called_once()

# Both host and server_config specified, host will be ignored
Expand All @@ -80,13 +69,7 @@ def test_exception_flow_init(

# When the new authentication mechanism is detected (Geti v1.15 and up), do
# not acquire token
mock_authentication_service = mocker.patch(
"geti_sdk.http_session.geti_session.GetiSession.authentication_service",
return_value="dex-new",
new_callable=mocker.PropertyMock,
)
geti = Geti(host=DUMMY_HOST, token=DUMMY_TOKEN)
mock_authentication_service.assert_called_once()
assert "x-api-key" in geti.session.headers.keys()

def test_logout(self, mocker: MockerFixture, fxt_mocked_geti: Geti):
Expand Down

0 comments on commit c931572

Please sign in to comment.