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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,8 @@ cython_debug/
#.idea/

# End of https://www.toptal.com/developers/gitignore/api/python

doc/source/api/schemas
test_results.xml
mapdl_motorbike_frame.zip
examples/mapdl_motorbike_frame/task_input_file*
1 change: 1 addition & 0 deletions ansys/rep/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
__version__,
__version_no_dots__,
)
from .client import Client
from .exceptions import APIError, ClientError, REPError
2 changes: 1 addition & 1 deletion ansys/rep/client/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
#
# Author(s): F.Negri O.Koenig
# ----------------------------------------------------------
from .api import AuthApi
from .authenticate import authenticate
from .client import Client
from .resource import User
1 change: 1 addition & 0 deletions ansys/rep/client/auth/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .auth_api import AuthApi
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@
# Author(s): F.Negri, O.Koenig
# ----------------------------------------------------------

from ansys.rep.client.connection import create_session
from ..resource.user import create_user, delete_user, get_users, update_user

from ..exceptions import raise_for_status
from .authenticate import authenticate
from .resource.user import create_user, delete_user, get_users, update_user


class Client(object):
class AuthApi:
"""A python interface to the Authorization Service API.

Users with admin rights (such as the default ``repadmin`` user) can create new
Expand All @@ -37,47 +33,54 @@ class Client(object):

"""

def __init__(
self,
rep_url,
*,
realm: str = "rep",
username: str = "repadmin",
password: str = "repadmin",
grant_type: str = "password",
scope="openid",
client_id: str = "rep-cli",
client_secret: str = None,
):

self.rep_url = rep_url
self.auth_api_url = self.rep_url + f"/auth/"

self.username = username
self.password = password
self.realm = realm
self.grant_type = grant_type
self.scope = scope
self.client_id = client_id
self.client_secret = client_secret

tokens = authenticate(
url=self.rep_url,
realm=realm,
grant_type=grant_type,
scope=scope,
client_id=client_id,
client_secret=client_secret,
username=username,
password=password,
)
self.access_token = tokens["access_token"]

self.session = create_session(self.access_token)
self.session.headers["content-type"] = "application/json"

# register hook to handle expiring of the refresh token
self.session.hooks["response"] = [raise_for_status]
def __init__(self, client):
self.client = client

@property
def url(self):
return f"{self.client.rep_url}/auth/"

# def __init__(
# self,
# rep_url,
# *,
# realm: str = "rep",
# username: str = "repadmin",
# password: str = "repadmin",
# grant_type: str = "password",
# scope="openid",
# client_id: str = "rep-cli",
# client_secret: str = None,
# ):

# self.rep_url = rep_url
# self.auth_api_url = self.rep_url + f"/auth/"

# self.username = username
# self.password = password
# self.realm = realm
# self.grant_type = grant_type
# self.scope = scope
# self.client_id = client_id
# self.client_secret = client_secret

# tokens = authenticate(
# url=self.rep_url,
# realm=realm,
# grant_type=grant_type,
# scope=scope,
# client_id=client_id,
# client_secret=client_secret,
# username=username,
# password=password,
# )
# self.access_token = tokens["access_token"]

# self.session = create_session(self.access_token)
# self.session.headers["content-type"] = "application/json"

# # register hook to handle expiring of the refresh token
# self.session.hooks["response"] = [raise_for_status]

# def get_api_info(self):
# """Return info like version, build date etc of the Auth API the client is connected to."""
Expand All @@ -86,7 +89,7 @@ def __init__(

def get_users(self, as_objects=True):
"""Return a list of users."""
return get_users(self, as_objects=as_objects)
return get_users(self.client, as_objects=as_objects)

def create_user(self, user, as_objects=True):
"""Create a new user.
Expand All @@ -95,7 +98,7 @@ def create_user(self, user, as_objects=True):
user (:class:`ansys.rep.client.auth.User`): A User object. Defaults to None.
as_objects (bool, optional): Defaults to True.
"""
return create_user(self, user, as_objects=as_objects)
return create_user(self.client, user, as_objects=as_objects)

def update_user(self, user, as_objects=True):
"""Modify an existing user.
Expand All @@ -112,4 +115,4 @@ def delete_user(self, user):
Args:
user (:class:`ansys.rep.client.auth.User`): A User object. Defaults to None.
"""
return delete_user(self, user)
return delete_user(self.client, user)
8 changes: 5 additions & 3 deletions ansys/rep/client/auth/resource/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ def __init__(self, **kwargs):


def _admin_client(client):
raise NotImplementedError("KeycloakAdmin currently doesn't support a token auth workflow. TODO")
keycloak_admin = KeycloakAdmin(
server_url=client.auth_api_url,
username=client.username,
password=client.password,
username=None,
password=None,
realm_name=client.realm,
client_secret_key=client.client_secret,
# refresh_token=client.refresh_token,
# access_token=client.access_token,
client_id=client.client_id,
verify=False,
)
Expand Down
120 changes: 120 additions & 0 deletions ansys/rep/client/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# ----------------------------------------------------------
# Copyright (C) 2019 by
# ANSYS Switzerland GmbH
# www.ansys.com
#
# Author(s): F.Negri, O.Koenig
# ----------------------------------------------------------

from .auth.authenticate import authenticate
from .connection import create_session
from .exceptions import raise_for_status


class Client(object):
"""A python interface to the Remote Execution Platform (REP) API.

Uses the provided credentials to create and store
an authorized :class:`requests.Session` object.

The following authentication workflows are supported:

- Username and password: the client connects to the OAuth server and
requests access and refresh tokens.
- Refresh token: the client connects to the OAuth server and
requests a new access token.
- Access token: no authentication needed.

Args:
rep_url (str): The base path for the server to call,
e.g. "https://127.0.0.1/dcs".
username (str): Username (Optional)
password (str): Password (Optional)
refresh_token (str): Refresh Token (Optional)
access_token (str): Access Token (Optional)

Example:

>>> from ansys.rep.client.jms import Client
>>> # Create client object and connect to DCS with username & password
>>> cl = Client(rep_url="https://127.0.0.1/dcs", username="dcadmin", password="dcadmin")
>>> # Extract refresh token to eventually store it
>>> refresh_token = cl.refresh_token
>>> # Alternative: Create client object and connect to DCS with refresh token
>>> cl = Client(rep_url="https://127.0.0.1/dcs", refresh_token=refresh_token)

"""

def __init__(
self,
rep_url: str = "https://127.0.0.1:8443/rep",
username: str = "repadmin",
password: str = "repadmin",
*,
realm: str = "rep",
grant_type: str = "password",
scope="openid",
client_id: str = "rep-cli",
client_secret: str = None,
access_token: str = None,
refresh_token: str = None,
auth_url: str = None,
):

self.rep_url = rep_url
self.auth_url = auth_url
self.auth_api_url = (auth_url or rep_url) + f"/auth/"
self.refresh_token = None
self.username = username
self.realm = realm
self.grant_type = grant_type
self.scope = scope
self.client_id = client_id
self.client_secret = client_secret

if access_token:
self.access_token = access_token
else:
tokens = authenticate(
url=auth_url or rep_url,
realm=realm,
grant_type=grant_type,
scope=scope,
client_id=client_id,
client_secret=client_secret,
username=username,
password=password,
refresh_token=refresh_token,
)
self.access_token = tokens["access_token"]
self.refresh_token = tokens["refresh_token"]

self.session = create_session(self.access_token)
# register hook to handle expiring of the refresh token
self.session.hooks["response"] = [self._auto_refresh_token, raise_for_status]
self._unauthorized_num_retry = 0
self._unauthorized_max_retry = 1

def _auto_refresh_token(self, response, *args, **kwargs):
"""Hook function to automatically refresh the access token and
re-send the request in case of unauthorized error"""
if (
response.status_code == 401
and self._unauthorized_num_retry < self._unauthorized_max_retry
):
self._unauthorized_num_retry += 1
self.refresh_access_token()
response.request.headers.update(
{"Authorization": self.session.headers["Authorization"]}
)
return self.session.send(response.request)

self._unauthorized_num_retry = 0
return response

def refresh_access_token(self):
"""Use the refresh token to obtain a new access token"""
tokens = authenticate(url=self.auth_url or self.rep_url, refresh_token=self.refresh_token)
self.access_token = tokens["access_token"]
self.refresh_token = tokens["refresh_token"]
self.session.headers.update({"Authorization": "Bearer %s" % tokens["access_token"]})
4 changes: 2 additions & 2 deletions ansys/rep/client/jms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Author(s): O.Koenig
# ----------------------------------------------------------

from .client import Client
from .api import JmsApi, ProjectApi
from .resource import (
Algorithm,
BoolParameterDefinition,
Expand All @@ -18,12 +18,12 @@
IntParameterDefinition,
Job,
JobDefinition,
JobSelection,
Licensing,
ParameterMapping,
Project,
ProjectPermission,
ResourceRequirements,
Selection,
Software,
StringParameterDefinition,
SuccessCriteria,
Expand Down
2 changes: 2 additions & 0 deletions ansys/rep/client/jms/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .jms_api import JmsApi
from .project_api import ProjectApi
Loading